From f9c063525a56650e11dd1b10069c1cf825c43ce9 Mon Sep 17 00:00:00 2001 From: Sharon Natan Date: Wed, 29 Apr 2026 14:52:55 +0300 Subject: [PATCH 01/12] fix: migrate npm publish to OIDC Trusted Publisher - Add id-token: write permission required for OIDC token exchange - Upgrade npm to ^11.5.1 (minimum version required by Trusted Publisher) - Clear NODE_AUTH_TOKEN on publish step so npm uses OIDC instead of the old user token Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish-on-merge.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index dbf9b186..bcf2255f 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -12,6 +12,7 @@ on: type: boolean permissions: + id-token: write # Required for Trusted Publisher on NPMJS contents: write jobs: @@ -47,6 +48,9 @@ jobs: registry-url: "https://registry.npmjs.org" cache: "yarn" + - name: Upgrade npm + run: npm install -g npm@^11.5.1 + - name: Install dependencies run: | logger -l info -m "Installing dependencies" @@ -113,7 +117,7 @@ jobs: - name: Publish to NPM env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_PACKAGE_UPLOAD_TOKEN }} + NODE_AUTH_TOKEN: '' # unset in order to use OIDC run: | if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then logger -l info -m "🔍 DRY RUN MODE: Would publish version ${{ steps.version.outputs.version }} with tag ${{ steps.version.outputs.tag }}" From 3febcf6db9ecedbd5d30423f7a5a240fc9738ac3 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 15:18:52 +0300 Subject: [PATCH 02/12] Bump version from 1.1.63 to 1.1.64 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7490f2fe..61732993 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@d-id/client-sdk", "private": false, - "version": "1.1.63", + "version": "1.1.64", "type": "module", "description": "d-id client sdk", "repository": { From cbbb86741a5dc49374e91b612bcd7775659e3a88 Mon Sep 17 00:00:00 2001 From: Sharon Natan Date: Wed, 29 Apr 2026 15:24:19 +0300 Subject: [PATCH 03/12] fix: bypass broken npm arborist to install npm 11.5.1 The Node 22 tool-cache on GitHub Actions runners ships with npm 10.9.7 where promise-retry is missing from @npmcli/arborist bundled deps. This causes all npm install -g commands to fail regardless of flags. Fix by downloading the npm 11.5.1 tarball directly from the registry and extracting it into the install prefix, bypassing the broken CLI. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish-on-merge.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index bcf2255f..61c1ce9d 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -49,7 +49,13 @@ jobs: cache: "yarn" - name: Upgrade npm - run: npm install -g npm@^11.5.1 + run: | + # The Node 22 tool-cache ships with a broken npm (promise-retry missing + # from @npmcli/arborist), so npm install -g itself fails. Bypass by + # extracting the npm 11.5.1 tarball directly from the registry. + NPM_DIR=$(node -e "const p=require('path');process.stdout.write(p.join(p.dirname(p.dirname(process.execPath)),'lib','node_modules','npm'))") + curl -fsSL https://registry.npmjs.org/npm/-/npm-11.5.1.tgz | tar -xz --strip-components=1 -C "$NPM_DIR" + npm --version - name: Install dependencies run: | From 9a519fac13d3d522c932b7136b28868efc3a37c8 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 15:44:29 +0300 Subject: [PATCH 04/12] Update npm installation method in workflow Fix npm installation by extracting tarball directly. --- .github/workflows/publish-on-merge.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index 61c1ce9d..a09edd7c 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -54,8 +54,10 @@ jobs: # from @npmcli/arborist), so npm install -g itself fails. Bypass by # extracting the npm 11.5.1 tarball directly from the registry. NPM_DIR=$(node -e "const p=require('path');process.stdout.write(p.join(p.dirname(p.dirname(process.execPath)),'lib','node_modules','npm'))") + echo $NPM_DIR curl -fsSL https://registry.npmjs.org/npm/-/npm-11.5.1.tgz | tar -xz --strip-components=1 -C "$NPM_DIR" npm --version + exit - name: Install dependencies run: | From 94eb2fcf8e38a9d7e98b87f302fdb5daf6ffcbf0 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 15:58:45 +0300 Subject: [PATCH 05/12] Print which npm --- .github/workflows/publish-on-merge.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index a09edd7c..61041fd2 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -42,22 +42,24 @@ jobs: sudo chmod +x /usr/local/bin/logger - name: Setup Node.js + id: setup-node uses: actions/setup-node@v6 with: node-version: 22 registry-url: "https://registry.npmjs.org" cache: "yarn" - - name: Upgrade npm + - name: Upgrade NPM + env: + NPM_DIR: /opt/hostedtoolcache/node/${{ steps.setup-node.outputs.node-version }}/x64/lib/node_modules/npm + NPM_VERSION: 11.5.1 run: | - # The Node 22 tool-cache ships with a broken npm (promise-retry missing - # from @npmcli/arborist), so npm install -g itself fails. Bypass by - # extracting the npm 11.5.1 tarball directly from the registry. - NPM_DIR=$(node -e "const p=require('path');process.stdout.write(p.join(p.dirname(p.dirname(process.execPath)),'lib','node_modules','npm'))") - echo $NPM_DIR - curl -fsSL https://registry.npmjs.org/npm/-/npm-11.5.1.tgz | tar -xz --strip-components=1 -C "$NPM_DIR" - npm --version - exit + which npm + logger -l info -m "Upgrading NPM to version: $NPM_VERSION" + curl -fsSL https://registry.npmjs.org/npm/-/npm-$NPM_VERSION.tgz | tar -xz --strip-components=1 -C "$NPM_DIR" \ + || logger -l error -m "Failed to upgrade NPM" + + logger -l info -m "Upgrade NPM succeeded" - name: Install dependencies run: | From 35e04c260a000c926e58565a3122e511813215b5 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 16:09:55 +0300 Subject: [PATCH 06/12] Fixed upgrade npm --- .github/workflows/publish-on-merge.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index 61041fd2..d80a8f3c 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -51,10 +51,16 @@ jobs: - name: Upgrade NPM env: - NPM_DIR: /opt/hostedtoolcache/node/${{ steps.setup-node.outputs.node-version }}/x64/lib/node_modules/npm + NODE_VERSION: ${{ steps.setup-node.outputs.node-version }} NPM_VERSION: 11.5.1 run: | - which npm + NODE_VERSION_STRIPED=${NODE_VERSION#v} + NPM_DIR="/opt/hostedtoolcache/node/$NODE_VERSION_STRIPED/x64/lib/node_modules/npm" + + logger -l debug -m "NODE_VERSION_STRIPED: $NODE_VERSION_STRIPED" + logger -l debug -m "NPM_DIR: $NPM_DIR" + logger -l debug -m "NPM_VERSION: $NPM_VERSION" + logger -l info -m "Upgrading NPM to version: $NPM_VERSION" curl -fsSL https://registry.npmjs.org/npm/-/npm-$NPM_VERSION.tgz | tar -xz --strip-components=1 -C "$NPM_DIR" \ || logger -l error -m "Failed to upgrade NPM" From 61fa4e1293db7a6ffab27cc11c014af7045259b1 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 16:10:34 +0300 Subject: [PATCH 07/12] test npm version --- .github/workflows/publish-on-merge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index d80a8f3c..120918fd 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -66,6 +66,7 @@ jobs: || logger -l error -m "Failed to upgrade NPM" logger -l info -m "Upgrade NPM succeeded" + npm --version - name: Install dependencies run: | From 56106bcc336c50f5cefcb27bf0754ad6d31877d1 Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 16:13:36 +0300 Subject: [PATCH 08/12] Update publish-on-merge.yml with NPM upgrade notes Added comments regarding NPM Trusted Publisher requirements and workaround for Node 22 image. --- .github/workflows/publish-on-merge.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-on-merge.yml b/.github/workflows/publish-on-merge.yml index 120918fd..c8774b60 100644 --- a/.github/workflows/publish-on-merge.yml +++ b/.github/workflows/publish-on-merge.yml @@ -49,6 +49,8 @@ jobs: registry-url: "https://registry.npmjs.org" cache: "yarn" + # npm Trusted Publisher requires CLI >= 11.5.1 for OIDC token exchange. + # Bypass the broken arborist in the cached Node 22 image by extracting the tarball directly. - name: Upgrade NPM env: NODE_VERSION: ${{ steps.setup-node.outputs.node-version }} @@ -66,7 +68,6 @@ jobs: || logger -l error -m "Failed to upgrade NPM" logger -l info -m "Upgrade NPM succeeded" - npm --version - name: Install dependencies run: | From 3912cd4cf8740db9053562ce4cd86645449aa20b Mon Sep 17 00:00:00 2001 From: sharon-d-id Date: Wed, 29 Apr 2026 16:13:52 +0300 Subject: [PATCH 09/12] Downgrade version from 1.1.64 to 1.1.63 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61732993..7490f2fe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@d-id/client-sdk", "private": false, - "version": "1.1.64", + "version": "1.1.63", "type": "module", "description": "d-id client sdk", "repository": { From 328b4124db15fdd4d425f2cd8d6307d879032e67 Mon Sep 17 00:00:00 2001 From: Arik Sfaradi Date: Thu, 30 Apr 2026 16:30:34 +0300 Subject: [PATCH 10/12] Merge pull request #380 from de-id/fix/codec-undefined-mixpanel fix: ensure codec is captured in WebRTC stats report --- package.json | 2 +- .../streaming-manager/stats/report.test.ts | 157 ++++++++++++++++++ .../streaming-manager/stats/report.ts | 85 ++++++---- 3 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 src/services/streaming-manager/stats/report.test.ts diff --git a/package.json b/package.json index 7490f2fe..61732993 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@d-id/client-sdk", "private": false, - "version": "1.1.63", + "version": "1.1.64", "type": "module", "description": "d-id client sdk", "repository": { diff --git a/src/services/streaming-manager/stats/report.test.ts b/src/services/streaming-manager/stats/report.test.ts new file mode 100644 index 00000000..4f062ef3 --- /dev/null +++ b/src/services/streaming-manager/stats/report.test.ts @@ -0,0 +1,157 @@ +import { formatStats } from './report'; + +type StatEntry = Record; + +function buildStats(entries: StatEntry[]): RTCStatsReport { + const map = new Map(); + for (const entry of entries) { + map.set(entry.id, entry); + } + return map as unknown as RTCStatsReport; +} + +const inboundRtpVideo: StatEntry = { + id: 'IT_video', + type: 'inbound-rtp', + kind: 'video', + codecId: 'CIT_video_vp8', + timestamp: 1_700_000_000_000, + bytesReceived: 1_234_567, + packetsReceived: 1_000, + packetsLost: 2, + framesDropped: 1, + framesDecoded: 600, + jitter: 0.01, + jitterBufferDelay: 30, + jitterBufferEmittedCount: 600, + frameWidth: 1280, + frameHeight: 720, + framesPerSecond: 30, + freezeCount: 0, + totalFreezesDuration: 0, +}; + +const codecVp8: StatEntry = { id: 'CIT_video_vp8', type: 'codec', mimeType: 'video/VP8' }; +const codecH264: StatEntry = { id: 'CIT_video_h264', type: 'codec', mimeType: 'video/H264' }; +const codecAudio: StatEntry = { id: 'CIT_audio_opus', type: 'codec', mimeType: 'audio/opus' }; +const inboundRtpAudio: StatEntry = { id: 'IT_audio', type: 'inbound-rtp', kind: 'audio' }; + +const nominatedPair: StatEntry = { + id: 'CP_nominated', + type: 'candidate-pair', + nominated: true, + currentRoundTripTime: 0.05, +}; +const backupPair: StatEntry = { + id: 'CP_backup', + type: 'candidate-pair', + nominated: false, + currentRoundTripTime: 0.2, +}; + +describe('formatStats', () => { + describe('codec extraction', () => { + it.each([ + ['codec stat is iterated before inbound-rtp video', [codecVp8, inboundRtpVideo]], + ['codec stat is iterated after inbound-rtp video', [inboundRtpVideo, codecVp8]], + ])('returns the codec when %s', (_label, entries) => { + expect(formatStats(buildStats(entries)).codec).toBe('VP8'); + }); + + it('returns the codec linked by codecId when multiple video codecs exist', () => { + const inboundUsingH264 = { ...inboundRtpVideo, codecId: codecH264.id }; + expect(formatStats(buildStats([codecVp8, codecH264, inboundUsingH264])).codec).toBe('H264'); + }); + + it.each([ + ['codecId does not match any codec entry', [codecVp8, { ...inboundRtpVideo, codecId: 'unknown-id' }]], + [ + 'inbound-rtp omits codecId entirely', + [ + codecVp8, + (() => { + const { codecId, ...rest } = inboundRtpVideo; + return rest; + })(), + ], + ], + ])('falls back to any video codec when %s', (_label, entries) => { + expect(formatStats(buildStats(entries)).codec).toBe('VP8'); + }); + + it('ignores audio codec entries when picking a video codec', () => { + expect(formatStats(buildStats([codecAudio, codecVp8, inboundRtpVideo])).codec).toBe('VP8'); + }); + + it('returns an empty codec string when no video codec is present', () => { + const inboundNoLink = { ...inboundRtpVideo, codecId: undefined }; + expect(formatStats(buildStats([codecAudio, inboundNoLink])).codec).toBe(''); + }); + + it('does not throw when a codec entry has no mimeType', () => { + const malformed = { id: 'CIT_bad', type: 'codec' }; + expect(() => formatStats(buildStats([malformed, inboundRtpVideo]))).not.toThrow(); + }); + }); + + describe('inbound-rtp routing', () => { + it('returns an empty report when no video inbound-rtp is present', () => { + expect(formatStats(buildStats([codecVp8, inboundRtpAudio]))).toEqual({}); + }); + + it('passes inbound-rtp fields through to the result', () => { + const result = formatStats(buildStats([codecVp8, inboundRtpVideo])); + expect(result).toMatchObject({ + codec: 'VP8', + timestamp: inboundRtpVideo.timestamp, + bytesReceived: inboundRtpVideo.bytesReceived, + packetsReceived: inboundRtpVideo.packetsReceived, + packetsLost: inboundRtpVideo.packetsLost, + framesDropped: inboundRtpVideo.framesDropped, + framesDecoded: inboundRtpVideo.framesDecoded, + jitter: inboundRtpVideo.jitter, + jitterBufferDelay: inboundRtpVideo.jitterBufferDelay, + jitterBufferEmittedCount: inboundRtpVideo.jitterBufferEmittedCount, + frameWidth: inboundRtpVideo.frameWidth, + frameHeight: inboundRtpVideo.frameHeight, + framesPerSecond: inboundRtpVideo.framesPerSecond, + freezeCount: inboundRtpVideo.freezeCount, + freezeDuration: inboundRtpVideo.totalFreezesDuration, + }); + }); + + it('derives avgJitterDelayInInterval as jitterBufferDelay / jitterBufferEmittedCount', () => { + const result = formatStats(buildStats([codecVp8, inboundRtpVideo])); + expect(result.avgJitterDelayInInterval).toBeCloseTo( + inboundRtpVideo.jitterBufferDelay / inboundRtpVideo.jitterBufferEmittedCount + ); + }); + }); + + describe('RTT priority', () => { + function rttFromPairs(pairs: StatEntry[]): number { + return formatStats(buildStats([...pairs, codecVp8, inboundRtpVideo])).rtt; + } + + it('uses the nominated pair when no other pair is present', () => { + expect(rttFromPairs([nominatedPair])).toBe(nominatedPair.currentRoundTripTime); + }); + + it('uses the nominated pair when a backup pair appears before it', () => { + expect(rttFromPairs([backupPair, nominatedPair])).toBe(nominatedPair.currentRoundTripTime); + }); + + it('keeps the nominated pair when a backup pair appears after it', () => { + expect(rttFromPairs([nominatedPair, backupPair])).toBe(nominatedPair.currentRoundTripTime); + }); + + it('uses the backup pair when no nominated pair is present', () => { + expect(rttFromPairs([backupPair])).toBe(backupPair.currentRoundTripTime); + }); + + it('ignores candidate-pair entries with non-positive RTT', () => { + const zeroRttPair = { ...backupPair, currentRoundTripTime: 0 }; + expect(rttFromPairs([zeroRttPair])).toBe(0); + }); + }); +}); diff --git a/src/services/streaming-manager/stats/report.ts b/src/services/streaming-manager/stats/report.ts index 2c615891..6bd76f4e 100644 --- a/src/services/streaming-manager/stats/report.ts +++ b/src/services/streaming-manager/stats/report.ts @@ -77,50 +77,67 @@ function extractAnomalies(stats: AnalyticsRTCStatsReport[]): AnalyticsRTCStatsRe export function formatStats(stats: RTCStatsReport): SlimRTCStatsReport { let codec = ''; let currRtt: number = 0; + let videoInboundRtp: RTCInboundRtpStreamStats | null = null; + const codecIdToMime = new Map(); + // RTCStatsReport iteration order is not guaranteed across browsers. + // Walk the full report once to collect codec/rtt/inbound-rtp before returning, + // otherwise we may return before the codec entry is seen and emit codec=''. for (const report of stats.values()) { - if (report && report.type === 'codec' && report.mimeType.startsWith('video')) { - codec = report.mimeType.split('/')[1]; - } - if (report && report.type === 'candidate-pair') { - const rtt = report.currentRoundTripTime; - const candidatePair = report as any; - const isNominated = candidatePair.nominated === true; - - // Prioritize RTT from the nominated candidate-pair (the active connection path). - // This ensures we capture the actual network latency being used, not just any candidate. - // Only update if we have a valid positive RTT value to avoid overwriting with invalid data. + if (!report) continue; + + if (report.type === 'codec' && report.mimeType?.startsWith('video')) { + codecIdToMime.set(report.id, report.mimeType.split('/')[1]); + } else if (report.type === 'candidate-pair') { + const pair = report as RTCIceCandidatePairStats; + const rtt = pair.currentRoundTripTime ?? 0; + // Prefer RTT from the nominated candidate-pair (the active connection path). + // Fall back to the first valid pair only until a nominated value arrives. if (rtt > 0) { - if (isNominated) { + if (pair.nominated === true) { currRtt = rtt; } else if (currRtt === 0) { currRtt = rtt; } } - } - if (report && report.type === 'inbound-rtp' && report.kind === 'video') { - return { - codec, - rtt: currRtt, - timestamp: report.timestamp, - bytesReceived: report.bytesReceived, - packetsReceived: report.packetsReceived, - packetsLost: report.packetsLost, - framesDropped: report.framesDropped, - framesDecoded: report.framesDecoded, - jitter: report.jitter, - jitterBufferDelay: report.jitterBufferDelay, - jitterBufferEmittedCount: report.jitterBufferEmittedCount, - avgJitterDelayInInterval: report.jitterBufferDelay / report.jitterBufferEmittedCount, - frameWidth: report.frameWidth, - frameHeight: report.frameHeight, - framesPerSecond: report.framesPerSecond, - freezeCount: report.freezeCount, - freezeDuration: report.totalFreezesDuration, - } as SlimRTCStatsReport; + } else if (report.type === 'inbound-rtp' && report.kind === 'video') { + videoInboundRtp = report as RTCInboundRtpStreamStats; } } - return {} as SlimRTCStatsReport; + + if (!videoInboundRtp) { + return {} as SlimRTCStatsReport; + } + + // WebRTC marks every numeric field optional, but SlimRTCStatsReport expects + // required values. Single boundary cast avoids per-field null checks below. + const inbound = videoInboundRtp as Required; + + if (inbound.codecId && codecIdToMime.has(inbound.codecId)) { + codec = codecIdToMime.get(inbound.codecId)!; + } else if (codecIdToMime.size > 0) { + codec = codecIdToMime.values().next().value ?? ''; + } + + return { + codec, + rtt: currRtt, + timestamp: inbound.timestamp, + bytesReceived: inbound.bytesReceived, + packetsReceived: inbound.packetsReceived, + packetsLost: inbound.packetsLost, + framesDropped: inbound.framesDropped, + framesDecoded: inbound.framesDecoded, + jitter: inbound.jitter, + jitterBufferDelay: inbound.jitterBufferDelay, + jitterBufferEmittedCount: inbound.jitterBufferEmittedCount, + avgJitterDelayInInterval: inbound.jitterBufferDelay / inbound.jitterBufferEmittedCount, + frameWidth: inbound.frameWidth, + frameHeight: inbound.frameHeight, + framesPerSecond: inbound.framesPerSecond, + freezeCount: inbound.freezeCount, + freezeDuration: inbound.totalFreezesDuration, + } as SlimRTCStatsReport; } export function createVideoStatsReport( From df5bc90ce59867075c7ccf17dd3808b25b3ebf99 Mon Sep 17 00:00:00 2001 From: dor-eitan <164745144+dor-eitan@users.noreply.github.com> Date: Mon, 4 May 2026 10:54:52 +0300 Subject: [PATCH 11/12] Revert "bugfix: remove random session-key suffix from Client-Key auth header (#377)" (#382) Restores the per-page-load random suffix needed by the notifications WebSocket adapter for per-tab routing. Backend will split this into a clean external_id (for analytics/memory_id) and external_id_connect_id (for WS routing) so the analytics regression PR #377 fixed does not return. This reverts commit 7c678d4e59a9a75c3bb9647198fe57c285bcc79f. Co-authored-by: Claude Opus 4.7 (1M context) --- src/auth/get-auth-header.test.ts | 11 +++++++---- src/auth/get-auth-header.ts | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/auth/get-auth-header.test.ts b/src/auth/get-auth-header.test.ts index 1fd48b36..7608ab96 100644 --- a/src/auth/get-auth-header.test.ts +++ b/src/auth/get-auth-header.test.ts @@ -128,7 +128,7 @@ describe('getAuthHeader', () => { const auth: Auth = { type: 'key', clientKey: 'test-client-key' }; const result = getAuthHeader(auth); - expect(result).toBe('Client-Key test-client-key.generated-external-id'); + expect(result).toBe('Client-Key test-client-key.generated-external-id_mocked-random-id'); }); it('should use provided externalId in Client-Key header', () => { @@ -136,7 +136,8 @@ describe('getAuthHeader', () => { const externalId = 'user-123'; const result = getAuthHeader(auth, externalId); - expect(result).toBe('Client-Key test-client-key.user-123'); + expect(result).toBe('Client-Key test-client-key.user-123_mocked-random-id'); + expect(result).toContain('user-123'); }); it('should use externalId from localStorage when not provided', () => { @@ -146,7 +147,8 @@ describe('getAuthHeader', () => { const auth: Auth = { type: 'key', clientKey: 'test-client-key' }; const result = getAuthHeader(auth); - expect(result).toBe('Client-Key test-client-key.stored-user-id'); + expect(result).toBe('Client-Key test-client-key.stored-user-id_mocked-random-id'); + expect(result).toContain('stored-user-id'); }); it('should generate new externalId and store it when localStorage is empty', () => { @@ -156,7 +158,8 @@ describe('getAuthHeader', () => { const auth: Auth = { type: 'key', clientKey: 'test-client-key' }; const result = getAuthHeader(auth); - expect(result).toBe('Client-Key test-client-key.new-generated-id'); + expect(result).toBe('Client-Key test-client-key.new-generated-id_mocked-random-id'); + expect(result).toContain('new-generated-id'); expect(window.localStorage.getItem('did_external_key_id')).toBe(mockRandomId); }); diff --git a/src/auth/get-auth-header.ts b/src/auth/get-auth-header.ts index 7959b887..2e3eddc1 100644 --- a/src/auth/get-auth-header.ts +++ b/src/auth/get-auth-header.ts @@ -18,13 +18,14 @@ export function getExternalId(externalId?: string): string { return key; } +let sessionKey = getRandom(); export function getAuthHeader(auth: Auth, externalId?: string) { if (auth.type === 'bearer') { return `Bearer ${auth.token}`; } else if (auth.type === 'basic') { return `Basic ${'token' in auth ? auth.token : btoa(`${auth.username}:${auth.password}`)}`; } else if (auth.type === 'key') { - return `Client-Key ${auth.clientKey}.${getExternalId(externalId)}`; + return `Client-Key ${auth.clientKey}.${getExternalId(externalId)}_${sessionKey}`; } else { throw new Error(`Unknown auth type: ${auth}`); } From 822755be0a3eb669f27f2f5b95e162c95aba2f0c Mon Sep 17 00:00:00 2001 From: Dor Eitan Date: Mon, 4 May 2026 11:15:04 +0300 Subject: [PATCH 12/12] bump to 1.1.65 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61732993..894d7e33 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@d-id/client-sdk", "private": false, - "version": "1.1.64", + "version": "1.1.65", "type": "module", "description": "d-id client sdk", "repository": {