@polyguard/sdk 1.3.2 → 1.4.1

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
@@ -10868,8 +10868,9 @@ function buildModal() {
10868
10868
  modal.style.justifyContent = "center";
10869
10869
  modal.style.overflowY = "auto";
10870
10870
  modal.style.paddingTop = "24px";
10871
+ modal.style.zIndex = "2147483647";
10871
10872
  modal.innerHTML = `
10872
- <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
10873
+ <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; z-index: 2147483647; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
10873
10874
  <button id="polyguard-modal-close" style="position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; color: #222; cursor: pointer; z-index: 2;">&times;</button>
10874
10875
  <h2 style="margin-top: 0; font-size: 1.04rem; font-weight: 700; color: #222;">Quick Identity Verification</h2>
10875
10876
  <div style="font-size: 10px; color: #888; margin-bottom: 10px; font-weight: 500;">Powered by Polyguard</div>
@@ -10979,6 +10980,35 @@ function handleWebSocketMessage(event, ctx) {
10979
10980
  return;
10980
10981
  }
10981
10982
  if (data.jwt) {
10983
+ const redirectUrl = data.jwt.redirect_url;
10984
+ const { sidebarUrl, link_uuid } = ctx;
10985
+ if (sidebarUrl && redirectUrl) {
10986
+ const hasMsftSession = document.cookie.includes("pg_msft_session");
10987
+ cleanup();
10988
+ ws.close();
10989
+ if (hasMsftSession) {
10990
+ window.location.assign(redirectUrl);
10991
+ resolve(rawJwt ? data : data.jwt);
10992
+ } else {
10993
+ const targetEl = qrDiv || document.body;
10994
+ const btn = document.createElement("button");
10995
+ btn.id = "polyguard-join-meeting";
10996
+ btn.textContent = "Join Meeting";
10997
+ btn.style.cssText = "padding:12px 24px;font-size:16px;cursor:pointer;border:none;border-radius:8px;background:#4CAF50;color:white;margin:16px auto;display:block;";
10998
+ targetEl.appendChild(btn);
10999
+ btn.addEventListener("click", () => {
11000
+ const sidebarFullUrl = link_uuid ? `${sidebarUrl}?linkUuid=${link_uuid}` : sidebarUrl;
11001
+ window.open(
11002
+ sidebarFullUrl,
11003
+ "polyguard-sidebar",
11004
+ "width=320,height=900,top=0,left=" + (window.screen.availWidth - 320)
11005
+ );
11006
+ window.location.assign(redirectUrl);
11007
+ resolve(rawJwt ? data : data.jwt);
11008
+ });
11009
+ }
11010
+ return;
11011
+ }
10982
11012
  cleanup();
10983
11013
  ws.close();
10984
11014
  resolve(rawJwt ? data : data.jwt);
@@ -11002,7 +11032,8 @@ function handleWebSocketMessage(event, ctx) {
11002
11032
  var PolyguardWebsocketClientImpl = class {
11003
11033
  constructor(params = {}) {
11004
11034
  this.apiClient = new ApiClient_default();
11005
- const { apiKey, baseUrl, appId, apiServer, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid } = params;
11035
+ const { apiKey, baseUrl, appId, apiServer, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid, sidebarUrl } = params;
11036
+ this.sidebarUrl = sidebarUrl;
11006
11037
  if (baseUrl) {
11007
11038
  this.apiClient.basePath = baseUrl;
11008
11039
  }
@@ -11114,7 +11145,7 @@ var PolyguardWebsocketClientImpl = class {
11114
11145
  reconnectionDelayGrowFactor: 1.5
11115
11146
  };
11116
11147
  ws = new reconnecting_websocket_mjs_default(wsUrl, [], options);
11117
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt };
11148
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11118
11149
  ws.addEventListener("message", (event) => handleWebSocketMessage(event, ctx));
11119
11150
  ws.addEventListener("error", () => {
11120
11151
  console.error("WebSocket error");
package/dist/sdk.js CHANGED
@@ -10901,8 +10901,9 @@ var Polyguard = (() => {
10901
10901
  modal.style.justifyContent = "center";
10902
10902
  modal.style.overflowY = "auto";
10903
10903
  modal.style.paddingTop = "24px";
10904
+ modal.style.zIndex = "2147483647";
10904
10905
  modal.innerHTML = `
10905
- <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
10906
+ <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; z-index: 2147483647; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
10906
10907
  <button id="polyguard-modal-close" style="position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; color: #222; cursor: pointer; z-index: 2;">&times;</button>
10907
10908
  <h2 style="margin-top: 0; font-size: 1.04rem; font-weight: 700; color: #222;">Quick Identity Verification</h2>
10908
10909
  <div style="font-size: 10px; color: #888; margin-bottom: 10px; font-weight: 500;">Powered by Polyguard</div>
@@ -11012,6 +11013,35 @@ var Polyguard = (() => {
11012
11013
  return;
11013
11014
  }
11014
11015
  if (data.jwt) {
11016
+ const redirectUrl = data.jwt.redirect_url;
11017
+ const { sidebarUrl, link_uuid } = ctx;
11018
+ if (sidebarUrl && redirectUrl) {
11019
+ const hasMsftSession = document.cookie.includes("pg_msft_session");
11020
+ cleanup();
11021
+ ws.close();
11022
+ if (hasMsftSession) {
11023
+ window.location.assign(redirectUrl);
11024
+ resolve(rawJwt ? data : data.jwt);
11025
+ } else {
11026
+ const targetEl = qrDiv || document.body;
11027
+ const btn = document.createElement("button");
11028
+ btn.id = "polyguard-join-meeting";
11029
+ btn.textContent = "Join Meeting";
11030
+ btn.style.cssText = "padding:12px 24px;font-size:16px;cursor:pointer;border:none;border-radius:8px;background:#4CAF50;color:white;margin:16px auto;display:block;";
11031
+ targetEl.appendChild(btn);
11032
+ btn.addEventListener("click", () => {
11033
+ const sidebarFullUrl = link_uuid ? `${sidebarUrl}?linkUuid=${link_uuid}` : sidebarUrl;
11034
+ window.open(
11035
+ sidebarFullUrl,
11036
+ "polyguard-sidebar",
11037
+ "width=320,height=900,top=0,left=" + (window.screen.availWidth - 320)
11038
+ );
11039
+ window.location.assign(redirectUrl);
11040
+ resolve(rawJwt ? data : data.jwt);
11041
+ });
11042
+ }
11043
+ return;
11044
+ }
11015
11045
  cleanup();
11016
11046
  ws.close();
11017
11047
  resolve(rawJwt ? data : data.jwt);
@@ -11035,7 +11065,8 @@ var Polyguard = (() => {
11035
11065
  var PolyguardWebsocketClientImpl = class {
11036
11066
  constructor(params = {}) {
11037
11067
  this.apiClient = new ApiClient_default();
11038
- const { apiKey, baseUrl, appId, apiServer, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid } = params;
11068
+ const { apiKey, baseUrl, appId, apiServer, requiredProofs = ["Full Name"], scanType = "single", redirectUrl, callbackUrl, cookieName, link_uuid, sidebarUrl } = params;
11069
+ this.sidebarUrl = sidebarUrl;
11039
11070
  if (baseUrl) {
11040
11071
  this.apiClient.basePath = baseUrl;
11041
11072
  }
@@ -11147,7 +11178,7 @@ var Polyguard = (() => {
11147
11178
  reconnectionDelayGrowFactor: 1.5
11148
11179
  };
11149
11180
  ws = new reconnecting_websocket_mjs_default(wsUrl, [], options);
11150
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt };
11181
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
11151
11182
  ws.addEventListener("message", (event) => handleWebSocketMessage(event, ctx));
11152
11183
  ws.addEventListener("error", () => {
11153
11184
  console.error("WebSocket error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyguard/sdk",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "main": "dist/sdk.esm.js",
6
6
  "module": "dist/sdk.esm.js",
@@ -8,7 +8,8 @@ import { handleWebSocketMessage } from './messageHandler.js';
8
8
  export class PolyguardWebsocketClientImpl {
9
9
  constructor(params = {}) {
10
10
  this.apiClient = new PolyguardApi.ApiClient();
11
- const { apiKey, baseUrl, appId, apiServer, requiredProofs = ['Full Name'], scanType = 'single', redirectUrl, callbackUrl, cookieName, link_uuid } = params;
11
+ const { apiKey, baseUrl, appId, apiServer, requiredProofs = ['Full Name'], scanType = 'single', redirectUrl, callbackUrl, cookieName, link_uuid, sidebarUrl } = params;
12
+ this.sidebarUrl = sidebarUrl;
12
13
  if (baseUrl) {
13
14
  this.apiClient.basePath = baseUrl;
14
15
  }
@@ -135,7 +136,7 @@ export class PolyguardWebsocketClientImpl {
135
136
  };
136
137
  ws = new ReconnectingWebSocket(wsUrl, [], options);
137
138
 
138
- const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt };
139
+ const ctx = { ws, qrDiv, isTargetMode, modal, cleanup, returnError, clearError, resolve, rawJwt, sidebarUrl: this.sidebarUrl, link_uuid: this.link_uuid };
139
140
  ws.addEventListener('message', (event) => handleWebSocketMessage(event, ctx));
140
141
 
141
142
  ws.addEventListener('error', () => {
@@ -0,0 +1,265 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { buildFakeJwt, DEFAULT_PARAMS, mockTicketResponse } from './helpers/fixtures.js';
3
+ import { MockReconnectingWebSocket } from './helpers/mockWebSocket.js';
4
+
5
+ // ---- Module mocks ----
6
+
7
+ const { MockApiClient, generatedMock, mockQRCode } = vi.hoisted(() => {
8
+ class MockApiClient {
9
+ constructor() {
10
+ this.basePath = '';
11
+ this.authentications = { bearerAuth: { apiKey: null } };
12
+ }
13
+ }
14
+
15
+ const generatedMock = { ApiClient: MockApiClient };
16
+ const apiClassNames = [
17
+ 'BlockingApi', 'CallsApi', 'DefaultApi', 'LinksApi', 'SdkApi',
18
+ 'SecureLinksApi', 'StirApi', 'TwilioApi', 'UsersApi', 'VeriffApi',
19
+ 'WellKnownApi', 'ZoomApi',
20
+ ];
21
+ for (const name of apiClassNames) {
22
+ generatedMock[name] = class {
23
+ constructor(apiClient) { this._apiClient = apiClient; }
24
+ };
25
+ }
26
+
27
+ const mockQRCode = {
28
+ toString: null,
29
+ };
30
+
31
+ return { MockApiClient, generatedMock, mockQRCode };
32
+ });
33
+
34
+ vi.mock('../generated/src', () => generatedMock);
35
+
36
+ vi.mock('reconnecting-websocket', async () => {
37
+ const { MockReconnectingWebSocket } = await import('./helpers/mockWebSocket.js');
38
+ return { default: MockReconnectingWebSocket };
39
+ });
40
+
41
+ vi.mock('qrcode', () => ({
42
+ default: mockQRCode,
43
+ }));
44
+
45
+ const { PolyguardWebsocketClientImpl } = await import('../PolyguardWebsocketClientImpl.js');
46
+
47
+ // ---- Helpers ----
48
+
49
+ async function flushMicrotasks() {
50
+ await new Promise(resolve => setTimeout(resolve, 0));
51
+ }
52
+
53
+ function stubFetch(ticket = 'test-ticket-123', ok = true) {
54
+ const mock = vi.fn().mockResolvedValue(mockTicketResponse(ticket, ok));
55
+ vi.stubGlobal('fetch', mock);
56
+ return mock;
57
+ }
58
+
59
+ function setProtocol(protocol) {
60
+ Object.defineProperty(window, 'location', {
61
+ value: { protocol, assign: vi.fn(), href: '' },
62
+ writable: true,
63
+ configurable: true,
64
+ });
65
+ }
66
+
67
+ function setUserAgent(ua) {
68
+ Object.defineProperty(navigator, 'userAgent', {
69
+ value: ua,
70
+ writable: true,
71
+ configurable: true,
72
+ });
73
+ }
74
+
75
+ async function startVerifyAndGetWs(client, target = null, rawJwt = false) {
76
+ const promise = client.verify(target, rawJwt);
77
+ await flushMicrotasks();
78
+ const ws = MockReconnectingWebSocket.latest();
79
+ return { promise, ws };
80
+ }
81
+
82
+ function setCookie(name, value) {
83
+ Object.defineProperty(document, 'cookie', {
84
+ value: `${name}=${value}; other=stuff`,
85
+ writable: true,
86
+ configurable: true,
87
+ });
88
+ }
89
+
90
+ function clearCookies() {
91
+ Object.defineProperty(document, 'cookie', {
92
+ value: '',
93
+ writable: true,
94
+ configurable: true,
95
+ });
96
+ }
97
+
98
+ // ---- Tests ----
99
+
100
+ describe('sidebarUrl feature', () => {
101
+ beforeEach(() => {
102
+ vi.clearAllMocks();
103
+ MockReconnectingWebSocket.reset();
104
+ setProtocol('https:');
105
+ setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)');
106
+ stubFetch();
107
+ mockQRCode.toString = vi.fn((data, opts, cb) => cb(null, '<svg>mock-qr</svg>'));
108
+ clearCookies();
109
+ });
110
+
111
+ afterEach(() => {
112
+ vi.restoreAllMocks();
113
+ document.body.innerHTML = '';
114
+ });
115
+
116
+ describe('constructor', () => {
117
+ it('stores sidebarUrl when provided', () => {
118
+ const client = new PolyguardWebsocketClientImpl({
119
+ ...DEFAULT_PARAMS,
120
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
121
+ });
122
+ expect(client.sidebarUrl).toBe('https://teams.polyguard.ai/ms-teams/sidebar-standalone');
123
+ });
124
+
125
+ it('sidebarUrl is undefined when not provided', () => {
126
+ const client = new PolyguardWebsocketClientImpl(DEFAULT_PARAMS);
127
+ expect(client.sidebarUrl).toBeUndefined();
128
+ });
129
+ });
130
+
131
+ describe('without sidebarUrl - existing behavior unchanged', () => {
132
+ it('jwt with redirect_url resolves normally', async () => {
133
+ const client = new PolyguardWebsocketClientImpl(DEFAULT_PARAMS);
134
+ const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
135
+ const { promise, ws } = await startVerifyAndGetWs(client);
136
+ ws.simulateMessage({ jwt: fakeJwt });
137
+ const result = await promise;
138
+ expect(result).toBe(fakeJwt);
139
+ });
140
+
141
+ it('does not call window.open', async () => {
142
+ const openSpy = vi.fn();
143
+ vi.stubGlobal('open', openSpy);
144
+ const client = new PolyguardWebsocketClientImpl(DEFAULT_PARAMS);
145
+ const fakeJwt = buildFakeJwt({ redirect_url: 'https://teams.microsoft.com/meet/123' });
146
+ const { promise, ws } = await startVerifyAndGetWs(client);
147
+ ws.simulateMessage({ jwt: fakeJwt });
148
+ await promise;
149
+ expect(openSpy).not.toHaveBeenCalled();
150
+ });
151
+ });
152
+
153
+ describe('with sidebarUrl and pg_msft_session cookie (authenticated user)', () => {
154
+ it('auto-redirects without showing a button', async () => {
155
+ setCookie('pg_msft_session', '1');
156
+ const client = new PolyguardWebsocketClientImpl({
157
+ ...DEFAULT_PARAMS,
158
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
159
+ link_uuid: 'link-abc',
160
+ });
161
+ document.body.innerHTML = '<div id="test-target"></div>';
162
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
163
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
164
+ ws.simulateMessage({ jwt: jwtClaims });
165
+ await promise;
166
+ expect(window.location.assign).toHaveBeenCalledWith('https://teams.microsoft.com/meet/123');
167
+ });
168
+
169
+ it('does not render a join button', async () => {
170
+ setCookie('pg_msft_session', '1');
171
+ const client = new PolyguardWebsocketClientImpl({
172
+ ...DEFAULT_PARAMS,
173
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
174
+ link_uuid: 'link-abc',
175
+ });
176
+ document.body.innerHTML = '<div id="test-target"></div>';
177
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
178
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
179
+ ws.simulateMessage({ jwt: jwtClaims });
180
+ await promise;
181
+ expect(document.querySelector('#polyguard-join-meeting')).toBeNull();
182
+ });
183
+ });
184
+
185
+ describe('with sidebarUrl and NO cookie (anonymous user)', () => {
186
+ it('renders a join button instead of auto-redirecting', async () => {
187
+ clearCookies();
188
+ const client = new PolyguardWebsocketClientImpl({
189
+ ...DEFAULT_PARAMS,
190
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
191
+ link_uuid: 'link-abc',
192
+ });
193
+ document.body.innerHTML = '<div id="test-target"></div>';
194
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
195
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
196
+ ws.simulateMessage({ jwt: jwtClaims });
197
+ // Should not have redirected yet
198
+ expect(window.location.assign).not.toHaveBeenCalled();
199
+ // Should show join button
200
+ const btn = document.querySelector('#polyguard-join-meeting');
201
+ expect(btn).not.toBeNull();
202
+ // Resolve by clicking the button
203
+ btn.click();
204
+ await promise;
205
+ });
206
+
207
+ it('clicking join button opens sidebar popup', async () => {
208
+ clearCookies();
209
+ const openSpy = vi.fn().mockReturnValue({});
210
+ vi.stubGlobal('open', openSpy);
211
+ const client = new PolyguardWebsocketClientImpl({
212
+ ...DEFAULT_PARAMS,
213
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
214
+ link_uuid: 'link-abc',
215
+ });
216
+ document.body.innerHTML = '<div id="test-target"></div>';
217
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
218
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
219
+ ws.simulateMessage({ jwt: jwtClaims });
220
+ const btn = document.querySelector('#polyguard-join-meeting');
221
+ btn.click();
222
+ await promise;
223
+ expect(openSpy).toHaveBeenCalledWith(
224
+ 'https://teams.polyguard.ai/ms-teams/sidebar-standalone?linkUuid=link-abc',
225
+ 'polyguard-sidebar',
226
+ expect.stringContaining('width=320')
227
+ );
228
+ });
229
+
230
+ it('clicking join button redirects to meeting', async () => {
231
+ clearCookies();
232
+ vi.stubGlobal('open', vi.fn().mockReturnValue({}));
233
+ const client = new PolyguardWebsocketClientImpl({
234
+ ...DEFAULT_PARAMS,
235
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
236
+ link_uuid: 'link-abc',
237
+ });
238
+ document.body.innerHTML = '<div id="test-target"></div>';
239
+ const jwtClaims = { redirect_url: 'https://teams.microsoft.com/meet/123' };
240
+ const { promise, ws } = await startVerifyAndGetWs(client, 'test-target');
241
+ ws.simulateMessage({ jwt: jwtClaims });
242
+ document.querySelector('#polyguard-join-meeting').click();
243
+ await promise;
244
+ expect(window.location.assign).toHaveBeenCalledWith('https://teams.microsoft.com/meet/123');
245
+ });
246
+ });
247
+
248
+ describe('with sidebarUrl but no redirect_url in JWT (non-meeting links)', () => {
249
+ it('resolves normally without button or redirect', async () => {
250
+ clearCookies();
251
+ const client = new PolyguardWebsocketClientImpl({
252
+ ...DEFAULT_PARAMS,
253
+ sidebarUrl: 'https://teams.polyguard.ai/ms-teams/sidebar-standalone',
254
+ link_uuid: 'link-abc',
255
+ });
256
+ const jwtClaims = { confirmation_code: 'ABC123' };
257
+ const { promise, ws } = await startVerifyAndGetWs(client);
258
+ // No redirect_url in the JWT claims
259
+ ws.simulateMessage({ jwt: jwtClaims });
260
+ const result = await promise;
261
+ expect(result).toEqual(jwtClaims);
262
+ expect(window.location.assign).not.toHaveBeenCalled();
263
+ });
264
+ });
265
+ });
@@ -54,6 +54,45 @@ export function handleWebSocketMessage(event, ctx) {
54
54
  }
55
55
 
56
56
  if (data.jwt) {
57
+ const redirectUrl = data.jwt.redirect_url;
58
+ const { sidebarUrl, link_uuid } = ctx;
59
+
60
+ // When sidebarUrl is configured and the server provides a redirect_url,
61
+ // the SDK handles the redirect (and optionally opens a sidebar popup).
62
+ if (sidebarUrl && redirectUrl) {
63
+ const hasMsftSession = document.cookie.includes('pg_msft_session');
64
+ cleanup();
65
+ ws.close();
66
+
67
+ if (hasMsftSession) {
68
+ // Authenticated user — redirect directly, they'll see the in-Teams sidepanel
69
+ window.location.assign(redirectUrl);
70
+ resolve(rawJwt ? data : data.jwt);
71
+ } else {
72
+ // Anonymous user — show a "Join Meeting" button so the click provides
73
+ // the user gesture Chrome requires for window.open()
74
+ const targetEl = qrDiv || document.body;
75
+ const btn = document.createElement('button');
76
+ btn.id = 'polyguard-join-meeting';
77
+ btn.textContent = 'Join Meeting';
78
+ btn.style.cssText = 'padding:12px 24px;font-size:16px;cursor:pointer;border:none;border-radius:8px;background:#4CAF50;color:white;margin:16px auto;display:block;';
79
+ targetEl.appendChild(btn);
80
+ btn.addEventListener('click', () => {
81
+ const sidebarFullUrl = link_uuid
82
+ ? `${sidebarUrl}?linkUuid=${link_uuid}`
83
+ : sidebarUrl;
84
+ window.open(
85
+ sidebarFullUrl,
86
+ 'polyguard-sidebar',
87
+ 'width=320,height=900,top=0,left=' + (window.screen.availWidth - 320)
88
+ );
89
+ window.location.assign(redirectUrl);
90
+ resolve(rawJwt ? data : data.jwt);
91
+ });
92
+ }
93
+ return;
94
+ }
95
+
57
96
  cleanup();
58
97
  ws.close();
59
98
  resolve(rawJwt ? data : data.jwt);
package/src/ui.js CHANGED
@@ -31,8 +31,9 @@ export function buildModal() {
31
31
  modal.style.justifyContent = 'center';
32
32
  modal.style.overflowY = 'auto';
33
33
  modal.style.paddingTop = '24px';
34
+ modal.style.zIndex = '2147483647';
34
35
  modal.innerHTML = `
35
- <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
36
+ <div id="polyguard-modal-content" style="background: #fff; color: #222; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 26px 19px 19px 19px; max-width: 312px; width: 100%; text-align: center; position: relative; z-index: 2147483647; font-family: 'IBM Plex Sans', 'Inter', 'Helvetica', 'Arial', sans-serif; margin: 0 auto; box-sizing: border-box;">
36
37
  <button id="polyguard-modal-close" style="position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; color: #222; cursor: pointer; z-index: 2;">&times;</button>
37
38
  <h2 style="margin-top: 0; font-size: 1.04rem; font-weight: 700; color: #222;">Quick Identity Verification</h2>
38
39
  <div style="font-size: 10px; color: #888; margin-bottom: 10px; font-weight: 500;">Powered by Polyguard</div>