From bea3f60fd64a755bf7f913eb11a8f5140c0523d1 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 16:32:33 +0100 Subject: [PATCH 1/6] fix(ci): upgrade GitHub Actions from v3 to v4 to fix hanging CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade actions/checkout@v3 → v4 - Upgrade actions/setup-node@v3 → v4 - Upgrade actions/cache@v3 → v4 - Upgrade actions/github-script@v5/v6 → v7 - Fix yarn install flag: --immutable → --frozen-lockfile (correct for Yarn Classic) - Update Node.js test matrix: [16, 18] → [18, 20, 22] The v3 actions use the deprecated Node.js 16 runtime which causes CI jobs to hang indefinitely. --- .github/actions/setup-node/action.yml | 6 +++--- .github/workflows/browser.yml | 2 +- .github/workflows/cloud.yml | 2 +- .github/workflows/initiate_release.yml | 6 +++--- .github/workflows/integration.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/size.yml | 2 +- .github/workflows/type.yml | 2 +- .github/workflows/unit.yml | 4 ++-- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 8c141ac9..121dfc46 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -10,18 +10,18 @@ runs: using: 'composite' steps: - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} registry-url: 'https://registry.npmjs.org' - name: Cache Dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./node_modules key: ${{ runner.os }}-${{ inputs.node-version }}-modules-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-${{ inputs.node-version }}-modules- - name: Install Dependencies & Build - run: yarn install --immutable --ignore-engines + run: yarn install --frozen-lockfile --ignore-engines shell: bash diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml index b76e4b74..aefd48e5 100644 --- a/.github/workflows/browser.yml +++ b/.github/workflows/browser.yml @@ -10,7 +10,7 @@ jobs: browser: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node - name: Test env: diff --git a/.github/workflows/cloud.yml b/.github/workflows/cloud.yml index c00d18e9..70cc37a7 100644 --- a/.github/workflows/cloud.yml +++ b/.github/workflows/cloud.yml @@ -10,7 +10,7 @@ jobs: qa: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node - name: Test env: diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index 350a7eb9..96a291a4 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -12,7 +12,7 @@ jobs: name: 🚀 Create release PR runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # gives the changelog generator access to all previous commits @@ -21,7 +21,7 @@ jobs: uses: WyriHaximus/github-action-get-previous-tag@v1 - name: Ensure version number higher than current - uses: actions/github-script@v5 + uses: actions/github-script@v7 env: PREVIOUS_TAG: ${{ steps.previoustag.outputs.tag }} DESTINATION_TAG: ${{ github.event.inputs.version }} @@ -49,7 +49,7 @@ jobs: git push -q -u origin "release-$VERSION" - name: Get changelog diff - uses: actions/github-script@v5 + uses: actions/github-script@v7 with: script: | const get_change_log_diff = require('./scripts/get_changelog_diff.js') diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7c14ea2d..71611a6f 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -10,7 +10,7 @@ jobs: integration: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node - name: Test env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 95e9e2b4..2e332a85 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node - name: Lint run: yarn run lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85bc4810..420dd856 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,11 @@ jobs: if: github.event.pull_request.merged && startsWith(github.head_ref, 'release-') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: script: | const get_change_log_diff = require('./scripts/get_changelog_diff.js') diff --git a/.github/workflows/size.yml b/.github/workflows/size.yml index 7682715b..92a17819 100644 --- a/.github/workflows/size.yml +++ b/.github/workflows/size.yml @@ -12,7 +12,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: preactjs/compressed-size-action@v2 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/type.yml b/.github/workflows/type.yml index 03577b72..568dc1cb 100644 --- a/.github/workflows/type.yml +++ b/.github/workflows/type.yml @@ -10,7 +10,7 @@ jobs: types: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node - name: Type check run: yarn run types diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 0360ab64..37d0fe6a 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [16, 18] + node: [18, 20, 22] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node with: node-version: ${{ matrix.node }} From ab17c4564dba27e5555adfd416b223ce21d2830d Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 16:40:19 +0100 Subject: [PATCH 2/6] fix(ci): add Node 24 to unit test matrix --- .github/workflows/unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 37d0fe6a..c81e4aa2 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [18, 20, 22] + node: [18, 20, 22, 24] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node From 39770011a1125d9253f98ac5d97ffae0f6c0531e Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 16:42:24 +0100 Subject: [PATCH 3/6] fix(ci): remove Node 24 (unreleased), default to Node 22 LTS Node 24 is scheduled for April 2026 and isn't stable yet. Changed default node version to 22 (current active LTS). --- .github/actions/setup-node/action.yml | 2 +- .github/workflows/unit.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 121dfc46..c0afe563 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -4,7 +4,7 @@ description: Sets up Node and Build SDK inputs: node-version: required: false - default: '24' + default: '22' runs: using: 'composite' diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index c81e4aa2..37d0fe6a 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [18, 20, 22, 24] + node: [18, 20, 22] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node From 88b47d4494461440d968e801213c523311a9b990 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 16:45:02 +0100 Subject: [PATCH 4/6] fix(ci): restore Node 24 and fix url.parse() deprecation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node 24 runtime-deprecated url.parse() (DEP0169). Migrated all usages to the WHATWG URL API: - src/redirect_url.ts: Url.parse() → new URL() - test/unit/node/redirect_test.js: url.parse().query → new URL().search - test/integration/cloud/reaction.js: url.parse(x, true) → new URL() --- .github/actions/setup-node/action.yml | 2 +- .github/workflows/unit.yml | 2 +- src/redirect_url.ts | 10 +++++++--- test/integration/cloud/reaction.js | 4 ++-- test/unit/node/redirect_test.js | 5 ++--- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index c0afe563..121dfc46 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -4,7 +4,7 @@ description: Sets up Node and Build SDK inputs: node-version: required: false - default: '22' + default: '24' runs: using: 'composite' diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 37d0fe6a..c81e4aa2 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [18, 20, 22] + node: [18, 20, 22, 24] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node diff --git a/src/redirect_url.ts b/src/redirect_url.ts index 48f421f7..7c005454 100644 --- a/src/redirect_url.ts +++ b/src/redirect_url.ts @@ -1,4 +1,3 @@ -import Url from 'url'; import qs from 'qs'; import { StreamClient } from './client'; @@ -20,10 +19,15 @@ import { JWTScopeToken } from './signing'; * @return {string} The redirect url */ export default function createRedirectUrl(this: StreamClient, targetUrl: string, userId: string, events: unknown[]) { - const uri = Url.parse(targetUrl); + let uri: URL; + try { + uri = new URL(targetUrl); + } catch { + throw new MissingSchemaError(`Invalid URI: "${targetUrl}"`); + } if (!(uri.host || (uri.hostname && uri.port))) { - throw new MissingSchemaError(`Invalid URI: "${Url.format(uri)}"`); + throw new MissingSchemaError(`Invalid URI: "${targetUrl}"`); } const authToken = JWTScopeToken(this.apiSecret as string, 'redirect_and_track', '*', { diff --git a/test/integration/cloud/reaction.js b/test/integration/cloud/reaction.js index b5f8343c..4e2a7453 100644 --- a/test/integration/cloud/reaction.js +++ b/test/integration/cloud/reaction.js @@ -1,5 +1,4 @@ /* eslint-disable no-await-in-loop */ -import url from 'url'; import expect from 'expect.js'; import { CloudContext } from './utils'; @@ -144,7 +143,8 @@ describe('Reaction pagination', () => { expectedQuery.user_id = ctx.bob.userId; } - const { query } = url.parse(extra.next, true); + const parsedUrl = new URL(extra.next, 'http://localhost'); + const query = Object.fromEntries(parsedUrl.searchParams.entries()); latestExtra[kind].next.should.include('/activity_id/'); latestExtra[kind].next.should.include(`/${kind}/`); latestExtra[kind].next.should.include(`/${ctx.activity.id}/`); diff --git a/test/unit/node/redirect_test.js b/test/unit/node/redirect_test.js index 47ebbdd9..bb36bbd0 100644 --- a/test/unit/node/redirect_test.js +++ b/test/unit/node/redirect_test.js @@ -1,4 +1,3 @@ -import url from 'url'; import expect from 'expect.js'; import jwt from 'jsonwebtoken'; import qs from 'qs'; @@ -39,7 +38,7 @@ describe("[UNIT] Redirect URL's", function () { this.client = new StreamClient(config.API_KEY, config.API_SECRET); const redirectUrl = this.client.createRedirectUrl(targetUrl, userId, events); - const queryString = qs.parse(url.parse(redirectUrl).query); + const queryString = qs.parse(new URL(redirectUrl).search, { ignoreQueryPrefix: true }); const decoded = jwt.verify(queryString.authorization, config.API_SECRET); expect(decoded).to.eql({ @@ -82,7 +81,7 @@ describe("[UNIT] Redirect URL's", function () { this.client = new StreamClient(config.API_KEY, config.API_SECRET); const redirectUrl = this.client.createRedirectUrl(targetUrl, userId, events); - const queryString = qs.parse(url.parse(redirectUrl).query); + const queryString = qs.parse(new URL(redirectUrl).search, { ignoreQueryPrefix: true }); const decoded = jwt.verify(queryString.authorization, config.API_SECRET); expect(decoded).to.eql({ From af24f85d473c19d9f1877a473d686c1e2d6a1b9d Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 16:46:04 +0100 Subject: [PATCH 5/6] fix(ci): revert Node 24 changes, keep matrix at [18, 20, 22] Node 24 compatibility (url.parse deprecation) will be addressed in a separate PR. Default node version set to 22 (current active LTS). --- .github/actions/setup-node/action.yml | 2 +- .github/workflows/unit.yml | 2 +- src/redirect_url.ts | 10 +++------- test/integration/cloud/reaction.js | 4 ++-- test/unit/node/redirect_test.js | 5 +++-- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 121dfc46..c0afe563 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -4,7 +4,7 @@ description: Sets up Node and Build SDK inputs: node-version: required: false - default: '24' + default: '22' runs: using: 'composite' diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index c81e4aa2..37d0fe6a 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [18, 20, 22, 24] + node: [18, 20, 22] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node diff --git a/src/redirect_url.ts b/src/redirect_url.ts index 7c005454..48f421f7 100644 --- a/src/redirect_url.ts +++ b/src/redirect_url.ts @@ -1,3 +1,4 @@ +import Url from 'url'; import qs from 'qs'; import { StreamClient } from './client'; @@ -19,15 +20,10 @@ import { JWTScopeToken } from './signing'; * @return {string} The redirect url */ export default function createRedirectUrl(this: StreamClient, targetUrl: string, userId: string, events: unknown[]) { - let uri: URL; - try { - uri = new URL(targetUrl); - } catch { - throw new MissingSchemaError(`Invalid URI: "${targetUrl}"`); - } + const uri = Url.parse(targetUrl); if (!(uri.host || (uri.hostname && uri.port))) { - throw new MissingSchemaError(`Invalid URI: "${targetUrl}"`); + throw new MissingSchemaError(`Invalid URI: "${Url.format(uri)}"`); } const authToken = JWTScopeToken(this.apiSecret as string, 'redirect_and_track', '*', { diff --git a/test/integration/cloud/reaction.js b/test/integration/cloud/reaction.js index 4e2a7453..b5f8343c 100644 --- a/test/integration/cloud/reaction.js +++ b/test/integration/cloud/reaction.js @@ -1,4 +1,5 @@ /* eslint-disable no-await-in-loop */ +import url from 'url'; import expect from 'expect.js'; import { CloudContext } from './utils'; @@ -143,8 +144,7 @@ describe('Reaction pagination', () => { expectedQuery.user_id = ctx.bob.userId; } - const parsedUrl = new URL(extra.next, 'http://localhost'); - const query = Object.fromEntries(parsedUrl.searchParams.entries()); + const { query } = url.parse(extra.next, true); latestExtra[kind].next.should.include('/activity_id/'); latestExtra[kind].next.should.include(`/${kind}/`); latestExtra[kind].next.should.include(`/${ctx.activity.id}/`); diff --git a/test/unit/node/redirect_test.js b/test/unit/node/redirect_test.js index bb36bbd0..47ebbdd9 100644 --- a/test/unit/node/redirect_test.js +++ b/test/unit/node/redirect_test.js @@ -1,3 +1,4 @@ +import url from 'url'; import expect from 'expect.js'; import jwt from 'jsonwebtoken'; import qs from 'qs'; @@ -38,7 +39,7 @@ describe("[UNIT] Redirect URL's", function () { this.client = new StreamClient(config.API_KEY, config.API_SECRET); const redirectUrl = this.client.createRedirectUrl(targetUrl, userId, events); - const queryString = qs.parse(new URL(redirectUrl).search, { ignoreQueryPrefix: true }); + const queryString = qs.parse(url.parse(redirectUrl).query); const decoded = jwt.verify(queryString.authorization, config.API_SECRET); expect(decoded).to.eql({ @@ -81,7 +82,7 @@ describe("[UNIT] Redirect URL's", function () { this.client = new StreamClient(config.API_KEY, config.API_SECRET); const redirectUrl = this.client.createRedirectUrl(targetUrl, userId, events); - const queryString = qs.parse(new URL(redirectUrl).search, { ignoreQueryPrefix: true }); + const queryString = qs.parse(url.parse(redirectUrl).query); const decoded = jwt.verify(queryString.authorization, config.API_SECRET); expect(decoded).to.eql({ From f3104abc2aada9ac74a8fb7f8e300648a6f7cb84 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Tue, 10 Mar 2026 17:05:52 +0100 Subject: [PATCH 6/6] fix(ci): add --exit flag to test-cloud mocha command Without --exit, mocha hangs forever waiting for open async handles (sockets, Faye subscriptions) to close after tests complete. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d3c8e6dc..3b104917 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test-types": "tsc --skipLibCheck --target es2020 --esModuleInterop true --noEmit true test/typescript/*.ts", "test-unit-node": "mocha --require ./babel-register.js test/unit/common test/unit/node", "test-integration-node": "mocha --require ./babel-register.js test/integration/common test/integration/node --exit", - "test-cloud": "mocha --require ./babel-register.js test/integration/cloud --timeout 40000", + "test-cloud": "mocha --require ./babel-register.js test/integration/cloud --timeout 40000 --exit", "test-cloud-local": "LOCAL=true mocha --require ./babel-register.js test/integration/cloud --timeout 40000 --ignore 'test/integration/cloud/{personalized_feed,files,images}.js'", "test-browser": "karma start karma.config.js", "prepare": "yarn run build",