@polyguard/sdk 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sdk.esm.js CHANGED
@@ -10980,10 +10980,11 @@ function handleWebSocketMessage(event, ctx) {
10980
10980
  return;
10981
10981
  }
10982
10982
  if (data.jwt) {
10983
- const redirectUrl = data.redirect_url;
10983
+ const redirectUrl = data.jwt.redirect_url;
10984
10984
  const { sidebarUrl, link_uuid } = ctx;
10985
10985
  if (sidebarUrl && redirectUrl) {
10986
10986
  const hasMsftSession = document.cookie.includes("pg_msft_session");
10987
+ ctx.markClosed();
10987
10988
  cleanup();
10988
10989
  ws.close();
10989
10990
  if (hasMsftSession) {
@@ -11145,7 +11146,10 @@ var PolyguardWebsocketClientImpl = class {
11145
11146
  reconnectionDelayGrowFactor: 1.5
11146
11147
  };
11147
11148
  ws = new reconnecting_websocket_mjs_default(wsUrl, [], options);
11148
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11149
+ const markClosed = () => {
11150
+ closed = true;
11151
+ };
11152
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, markClosed, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11149
11153
  ws.addEventListener("message", (event) => handleWebSocketMessage(event, ctx));
11150
11154
  ws.addEventListener("error", () => {
11151
11155
  console.error("WebSocket error");
package/dist/sdk.js CHANGED
@@ -11013,10 +11013,11 @@ var Polyguard = (() => {
11013
11013
  return;
11014
11014
  }
11015
11015
  if (data.jwt) {
11016
- const redirectUrl = data.redirect_url;
11016
+ const redirectUrl = data.jwt.redirect_url;
11017
11017
  const { sidebarUrl, link_uuid } = ctx;
11018
11018
  if (sidebarUrl && redirectUrl) {
11019
11019
  const hasMsftSession = document.cookie.includes("pg_msft_session");
11020
+ ctx.markClosed();
11020
11021
  cleanup();
11021
11022
  ws.close();
11022
11023
  if (hasMsftSession) {
@@ -11178,7 +11179,10 @@ var Polyguard = (() => {
11178
11179
  reconnectionDelayGrowFactor: 1.5
11179
11180
  };
11180
11181
  ws = new reconnecting_websocket_mjs_default(wsUrl, [], options);
11181
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11182
+ const markClosed = () => {
11183
+ closed = true;
11184
+ };
11185
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, markClosed, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11182
11186
  ws.addEventListener("message", (event) => handleWebSocketMessage(event, ctx));
11183
11187
  ws.addEventListener("error", () => {
11184
11188
  console.error("WebSocket error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyguard/sdk",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "main": "dist/sdk.esm.js",
6
6
  "module": "dist/sdk.esm.js",
@@ -136,7 +136,8 @@ export class PolyguardWebsocketClientImpl {
136
136
  };
137
137
  ws = new ReconnectingWebSocket(wsUrl, [], options);
138
138
 
139
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
139
+ const markClosed = () => { closed = true; };
140
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, markClosed, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
140
141
  ws.addEventListener('message', (event) => handleWebSocketMessage(event, ctx));
141
142
 
142
143
  ws.addEventListener('error', () => {
@@ -159,9 +159,9 @@ describe('sidebarUrl feature', () => {
159
159
  link_uuid: 'link-abc',
160
160
  });
161
161
  document.body.innerHTML = '<div id="test-target"></div>';
162
- const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
162
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
163
163
  const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
164
- ws.simulateMessage({ jwt: fakeJwt, redirect_url: 'https://teams.microsoft.com/meet/123' });
164
+ ws.simulateMessage({ jwt: jwtClaims });
165
165
  await promise;
166
166
  expect(window.location.assign).toHaveBeenCalledWith('https://teams.microsoft.com/meet/123');
167
167
  });
@@ -174,9 +174,9 @@ describe('sidebarUrl feature', () => {
174
174
  link_uuid: 'link-abc',
175
175
  });
176
176
  document.body.innerHTML = '<div id="test-target"></div>';
177
- const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
177
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
178
178
  const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
179
- ws.simulateMessage({ jwt: fakeJwt, redirect_url: 'https://teams.microsoft.com/meet/123' });
179
+ ws.simulateMessage({ jwt: jwtClaims });
180
180
  await promise;
181
181
  expect(document.querySelector('#polyguard-join-meeting')).toBeNull();
182
182
  });
@@ -191,9 +191,9 @@ describe('sidebarUrl feature', () => {
191
191
  link_uuid: 'link-abc',
192
192
  });
193
193
  document.body.innerHTML = '<div id="test-target"></div>';
194
- const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
194
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
195
195
  const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
196
- ws.simulateMessage({ jwt: fakeJwt, redirect_url: 'https://teams.microsoft.com/meet/123' });
196
+ ws.simulateMessage({ jwt: jwtClaims });
197
197
  // Should not have redirected yet
198
198
  expect(window.location.assign).not.toHaveBeenCalled();
199
199
  // Should show join button
@@ -214,9 +214,9 @@ describe('sidebarUrl feature', () => {
214
214
  link_uuid: 'link-abc',
215
215
  });
216
216
  document.body.innerHTML = '<div id="test-target"></div>';
217
- const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
217
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
218
218
  const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
219
- ws.simulateMessage({ jwt: fakeJwt, redirect_url: 'https://teams.microsoft.com/meet/123' });
219
+ ws.simulateMessage({ jwt: jwtClaims });
220
220
  const btn = document.querySelector('#polyguard-join-meeting');
221
221
  btn.click();
222
222
  await promise;
@@ -236,13 +236,37 @@ describe('sidebarUrl feature', () => {
236
236
  link_uuid: 'link-abc',
237
237
  });
238
238
  document.body.innerHTML = '<div id="test-target"></div>';
239
- const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
239
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
240
240
  const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
241
- ws.simulateMessage({ jwt: fakeJwt, redirect_url: 'https://teams.microsoft.com/meet/123' });
241
+ ws.simulateMessage({ jwt: jwtClaims });
242
242
  document.querySelector('#polyguard-join-meeting').click();
243
243
  await promise;
244
244
  expect(window.location.assign).toHaveBeenCalledWith('https://teams.microsoft.com/meet/123');
245
245
  });
246
+
247
+ it('button survives a late WebSocket close event (race regression)', async () => {
248
+ // Real browsers dispatch the 'close' event asynchronously after ws.close(),
249
+ // so it lands after the synchronous button append. If the close listener's
250
+ // cleanup() runs in that window, it wipes the qrDiv — and the button with it.
251
+ // markClosed() in the message handler must defuse the close listener.
252
+ clearCookies();
253
+ vi.stubGlobal('open', vi.fn().mockReturnValue({}));
254
+ const client = new PolyguardWebsocketClientImpl({
255
+ ...DEFAULT_PARAMS,
256
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
257
+ link_uuid: 'link-abc',
258
+ });
259
+ document.body.innerHTML = '<div id="test-target"></div>';
260
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
261
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
262
+ ws.simulateMessage({ jwt: jwtClaims });
263
+ expect(document.querySelector('#polyguard-join-meeting')).not.toBeNull();
264
+ // Mimic a real browser dispatching close on the next tick (after the message handler returned).
265
+ ws.simulateClose();
266
+ expect(document.querySelector('#polyguard-join-meeting')).not.toBeNull();
267
+ document.querySelector('#polyguard-join-meeting').click();
268
+ await promise;
269
+ });
246
270
  });
247
271
 
248
272
  describe('with sidebarUrl but no redirect_url in JWT (non-meeting links)', () => {
@@ -253,12 +277,12 @@ describe('sidebarUrl feature', () => {
253
277
  sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
254
278
  link_uuid: 'link-abc',
255
279
  });
256
- const fakeJwt = buildFakeJwt({ confirmation_code: 'ABC123' });
280
+ const jwtClaims = { confirmation_code: 'ABC123' };
257
281
  const { promise, ws } = await startVerifyAndGetWs(client);
258
- // No redirect_url in the message data
259
- ws.simulateMessage({ jwt: fakeJwt });
282
+ // No redirect_url in the JWT claims
283
+ ws.simulateMessage({ jwt: jwtClaims });
260
284
  const result = await promise;
261
- expect(result).toBe(fakeJwt);
285
+ expect(result).toEqual(jwtClaims);
262
286
  expect(window.location.assign).not.toHaveBeenCalled();
263
287
  });
264
288
  });
@@ -54,13 +54,18 @@ export function handleWebSocketMessage(event, ctx) {
54
54
  }
55
55
 
56
56
  if (data.jwt) {
57
- const redirectUrl = data.redirect_url;
57
+ const redirectUrl = data.jwt.redirect_url;
58
58
  const { sidebarUrl, link_uuid } = ctx;
59
59
 
60
60
  // When sidebarUrl is configured and the server provides a redirect_url,
61
61
  // the SDK handles the redirect (and optionally opens a sidebar popup).
62
62
  if (sidebarUrl && redirectUrl) {
63
63
  const hasMsftSession = document.cookie.includes('pg_msft_session');
64
+ // Mark the socket as intentionally closed BEFORE ws.close() so the
65
+ // 'close' event listener in verify() skips its own cleanup(). Otherwise
66
+ // the async close handler races with the synchronous button append below
67
+ // and wipes the qrDiv after we've put the button in it.
68
+ ctx.markClosed();
64
69
  cleanup();
65
70
  ws.close();
66
71