@oxyhq/core 7.0.0 → 7.1.0
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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +17 -2
- package/dist/cjs/session/SessionClient.js +181 -10
- package/dist/cjs/session/accountDialogController.js +541 -0
- package/dist/cjs/session/accountProjection.js +131 -0
- package/dist/cjs/session/createSessionClient.js +9 -2
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +11 -0
- package/dist/esm/session/SessionClient.js +181 -10
- package/dist/esm/session/accountDialogController.js +536 -0
- package/dist/esm/session/accountProjection.js +127 -0
- package/dist/esm/session/createSessionClient.js +9 -2
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/session/SessionClient.d.ts +51 -0
- package/dist/types/session/accountDialogController.d.ts +246 -0
- package/dist/types/session/accountProjection.d.ts +142 -0
- package/dist/types/session/createSessionClient.d.ts +9 -2
- package/package.json +1 -1
- package/src/index.ts +31 -0
- package/src/session/SessionClient.ts +201 -11
- package/src/session/__tests__/SessionClient.signedOut.test.ts +224 -0
- package/src/session/__tests__/accountDialogController.test.ts +469 -0
- package/src/session/__tests__/accountProjection.test.ts +181 -0
- package/src/session/accountDialogController.ts +682 -0
- package/src/session/accountProjection.ts +263 -0
- package/src/session/createSessionClient.ts +9 -2
package/dist/cjs/index.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.lightenColor = exports.darkenColor = exports.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
22
|
exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = void 0;
|
|
23
|
-
exports.
|
|
24
|
-
exports.packageInfo = exports.BOOT_STATE_SESSION_KEY = exports.BOOT_FRAGMENT_PARAM = exports.hashHasBootFragment = exports.parseDeviceBootFragment = exports.consumeDeviceBootReturn = exports.BOOT_ATTEMPTED_KEY = exports.isSameApex = exports.createBrowserColdBootDom = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = void 0;
|
|
23
|
+
exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.runColdBoot = exports.SSO_CALLBACK_PATH = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidDisplayName = void 0;
|
|
24
|
+
exports.packageInfo = exports.BOOT_STATE_SESSION_KEY = exports.BOOT_FRAGMENT_PARAM = exports.hashHasBootFragment = exports.parseDeviceBootFragment = exports.consumeDeviceBootReturn = exports.BOOT_ATTEMPTED_KEY = exports.isSameApex = exports.createBrowserColdBootDom = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.DEVICE_TOKEN_STORAGE_KEY = void 0;
|
|
25
25
|
// Ensure crypto polyfills are loaded before anything else
|
|
26
26
|
require("./crypto/polyfill");
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -294,6 +294,21 @@ Object.defineProperty(exports, "deviceStateToClientSessions", { enumerable: true
|
|
|
294
294
|
Object.defineProperty(exports, "activeSessionIdOf", { enumerable: true, get: function () { return projectSessionState_1.activeSessionIdOf; } });
|
|
295
295
|
Object.defineProperty(exports, "activeUserOf", { enumerable: true, get: function () { return projectSessionState_1.activeUserOf; } });
|
|
296
296
|
Object.defineProperty(exports, "accountIdsOf", { enumerable: true, get: function () { return projectSessionState_1.accountIdsOf; } });
|
|
297
|
+
// Unified account-list projection (THE single source of truth for the account
|
|
298
|
+
// chooser: device sign-ins ∪ account graph, deduped by accountId). Pure +
|
|
299
|
+
// I/O-free — the caller hydrates profiles via `getUsersByIds`. Shared by
|
|
300
|
+
// `@oxyhq/services`, `@oxyhq/auth`, and auth.oxy.so so the list can't diverge.
|
|
301
|
+
var accountProjection_1 = require("./session/accountProjection");
|
|
302
|
+
Object.defineProperty(exports, "projectSwitchableAccounts", { enumerable: true, get: function () { return accountProjection_1.projectSwitchableAccounts; } });
|
|
303
|
+
Object.defineProperty(exports, "switchableAccountIds", { enumerable: true, get: function () { return accountProjection_1.switchableAccountIds; } });
|
|
304
|
+
// Headless controller for the unified account dialog. Framework-agnostic
|
|
305
|
+
// state machine + subscribe/getSnapshot store (bind via `useSyncExternalStore`)
|
|
306
|
+
// — no password/2FA logic (that lives at the IdP; `openPasswordAtOxyAuth` only
|
|
307
|
+
// hands off). Reuses `SessionClient.switchAccount` / `oxyServices.switchToAccount`
|
|
308
|
+
// for the uniform switch and the existing device-flow methods for sign-in.
|
|
309
|
+
var accountDialogController_1 = require("./session/accountDialogController");
|
|
310
|
+
Object.defineProperty(exports, "AccountDialogController", { enumerable: true, get: function () { return accountDialogController_1.AccountDialogController; } });
|
|
311
|
+
Object.defineProperty(exports, "createAccountDialogController", { enumerable: true, get: function () { return accountDialogController_1.createAccountDialogController; } });
|
|
297
312
|
// ---------------------------------------------------------------------------
|
|
298
313
|
// Device-first session machinery (auth centralization, wave 1) — additive.
|
|
299
314
|
// Persisted auth-state store, the unified refresh handler + scheduler, the
|
|
@@ -4,6 +4,14 @@ exports.SessionClient = void 0;
|
|
|
4
4
|
const contracts_1 = require("@oxyhq/contracts");
|
|
5
5
|
const loggerUtils_1 = require("../utils/loggerUtils");
|
|
6
6
|
const socketLoader_1 = require("./socketLoader");
|
|
7
|
+
/**
|
|
8
|
+
* Same-origin `BroadcastChannel` name for instant, network-free session wake
|
|
9
|
+
* across tabs of the SAME origin (e.g. two `accounts.oxy.so` tabs). Complements
|
|
10
|
+
* the cookie-authed device socket, which covers same-apex cross-origin
|
|
11
|
+
* (`accounts` ↔ `console` ↔ `inbox`, all `*.oxy.so`). Cross-APEX (mention.earth)
|
|
12
|
+
* is covered by neither and relies on a reload — the documented limitation.
|
|
13
|
+
*/
|
|
14
|
+
const SESSION_BROADCAST_CHANNEL = 'oxy.session';
|
|
7
15
|
class SessionClient {
|
|
8
16
|
constructor(host, options = {}) {
|
|
9
17
|
this.host = host;
|
|
@@ -13,6 +21,12 @@ class SessionClient {
|
|
|
13
21
|
this.socket = null;
|
|
14
22
|
this.tokenUnsub = null;
|
|
15
23
|
this.started = false;
|
|
24
|
+
/** In-flight guard so a burst of pushes triggers at most ONE acquisition. */
|
|
25
|
+
this.acquiring = false;
|
|
26
|
+
/** True while the live socket is an anonymous (signed-out) device connection. */
|
|
27
|
+
this.socketAnonymous = false;
|
|
28
|
+
/** Same-origin cross-tab wake channel; null on platforms without BroadcastChannel. */
|
|
29
|
+
this.channel = null;
|
|
16
30
|
}
|
|
17
31
|
getState() {
|
|
18
32
|
return this.state;
|
|
@@ -105,14 +119,17 @@ class SessionClient {
|
|
|
105
119
|
async switchAccount(accountId) {
|
|
106
120
|
const res = await this.host.makeRequest('POST', '/session/device/switch', { accountId }, { cache: false });
|
|
107
121
|
this.applySync(res);
|
|
122
|
+
this.postCommitPing();
|
|
108
123
|
}
|
|
109
124
|
async signOut(target) {
|
|
110
125
|
const res = await this.host.makeRequest('POST', '/session/device/signout', target, { cache: false });
|
|
111
126
|
this.applySync(res);
|
|
127
|
+
this.postCommitPing();
|
|
112
128
|
}
|
|
113
129
|
async addCurrentAccount() {
|
|
114
130
|
const res = await this.host.makeRequest('POST', '/session/device/add', undefined, { cache: false });
|
|
115
131
|
this.applySync(res);
|
|
132
|
+
this.postCommitPing();
|
|
116
133
|
}
|
|
117
134
|
/**
|
|
118
135
|
* Register the just-signed-in account into the device set AND make it the
|
|
@@ -140,19 +157,58 @@ class SessionClient {
|
|
|
140
157
|
return;
|
|
141
158
|
this.started = true;
|
|
142
159
|
this.tokenUnsub = this.host.onTokensChanged((token) => {
|
|
143
|
-
if (token
|
|
160
|
+
if (!token)
|
|
161
|
+
return;
|
|
162
|
+
// A bearer just landed: an in-flight acquisition (if any) succeeded — clear
|
|
163
|
+
// the guard so a later sign-out can re-acquire.
|
|
164
|
+
this.acquiring = false;
|
|
165
|
+
if (!this.socket)
|
|
166
|
+
return;
|
|
167
|
+
if (this.socketAnonymous) {
|
|
168
|
+
// The live socket was an anonymous (device-room-only) connection. Force a
|
|
169
|
+
// reconnect so the handshake re-runs authenticated and also joins the
|
|
170
|
+
// `user:<id>` notification room.
|
|
171
|
+
this.socketAnonymous = false;
|
|
172
|
+
this.socket.disconnect();
|
|
173
|
+
this.socket.connect();
|
|
174
|
+
}
|
|
175
|
+
else if (!this.socket.connected) {
|
|
144
176
|
this.socket.connect();
|
|
145
177
|
}
|
|
146
178
|
});
|
|
147
|
-
|
|
179
|
+
this.openBroadcastChannel();
|
|
180
|
+
// `bootstrap` (`GET /session/device/state`) is bearer-authenticated: skip it
|
|
181
|
+
// when signed out (the signed-out socket below still joins the device room to
|
|
182
|
+
// receive pushes). A bootstrap failure is non-fatal — the socket must still
|
|
183
|
+
// connect so realtime sync survives a transient state-fetch error.
|
|
184
|
+
if (this.host.getAccessToken()) {
|
|
185
|
+
try {
|
|
186
|
+
await this.bootstrap();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
loggerUtils_1.logger.warn('[SessionClient] bootstrap during start failed (non-fatal)', { component: 'SessionClient' }, error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
148
192
|
await this.connectSocket();
|
|
149
193
|
}
|
|
150
194
|
stop() {
|
|
151
195
|
this.started = false;
|
|
196
|
+
this.acquiring = false;
|
|
197
|
+
this.socketAnonymous = false;
|
|
152
198
|
if (this.tokenUnsub) {
|
|
153
199
|
this.tokenUnsub();
|
|
154
200
|
this.tokenUnsub = null;
|
|
155
201
|
}
|
|
202
|
+
if (this.channel) {
|
|
203
|
+
this.channel.onmessage = null;
|
|
204
|
+
try {
|
|
205
|
+
this.channel.close();
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
loggerUtils_1.logger.debug('[SessionClient] BroadcastChannel close failed', { component: 'SessionClient' }, error);
|
|
209
|
+
}
|
|
210
|
+
this.channel = null;
|
|
211
|
+
}
|
|
156
212
|
if (this.socket) {
|
|
157
213
|
this.socket.disconnect();
|
|
158
214
|
this.socket = null;
|
|
@@ -170,25 +226,140 @@ class SessionClient {
|
|
|
170
226
|
if (!this.started)
|
|
171
227
|
return; // stopped while the dynamic import was in flight
|
|
172
228
|
const hasToken = Boolean(this.host.getAccessToken());
|
|
229
|
+
// Signed-out connect gate: when there is no bearer, only open the socket if
|
|
230
|
+
// the consumer supplies a device anchor (web cookie → `true`; native token →
|
|
231
|
+
// string). This lets an idle tab receive its device's `session_state` pushes
|
|
232
|
+
// and self-acquire when a sibling signs in.
|
|
233
|
+
let signedOutAuth = false;
|
|
234
|
+
if (!hasToken && this.options.signedOutSocketAuth) {
|
|
235
|
+
try {
|
|
236
|
+
signedOutAuth = await this.options.signedOutSocketAuth();
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
loggerUtils_1.logger.warn('[SessionClient] signedOutSocketAuth failed', { component: 'SessionClient' }, error);
|
|
240
|
+
signedOutAuth = false;
|
|
241
|
+
}
|
|
242
|
+
if (!this.started)
|
|
243
|
+
return; // stopped while the async gate was in flight
|
|
244
|
+
}
|
|
245
|
+
const signedOutConnect = signedOutAuth !== false && signedOutAuth != null;
|
|
246
|
+
const signedOutDeviceToken = typeof signedOutAuth === 'string' ? signedOutAuth : undefined;
|
|
247
|
+
this.socketAnonymous = !hasToken && signedOutConnect;
|
|
173
248
|
const socket = io(this.host.getBaseURL(), {
|
|
174
249
|
transports: ['websocket'],
|
|
175
|
-
|
|
250
|
+
// Send the first-party `oxy_device` cookie on the same-site handshake so a
|
|
251
|
+
// signed-out browser tab can be resolved to its device room server-side.
|
|
252
|
+
withCredentials: true,
|
|
253
|
+
autoConnect: hasToken || signedOutConnect,
|
|
176
254
|
auth: (cb) => {
|
|
177
|
-
|
|
255
|
+
const token = this.host.getAccessToken() ?? '';
|
|
256
|
+
// Present the native device token only while signed out (no bearer). Once
|
|
257
|
+
// a token is held the bearer path wins and the deviceToken is redundant.
|
|
258
|
+
cb(token || !signedOutDeviceToken ? { token } : { token, deviceToken: signedOutDeviceToken });
|
|
178
259
|
},
|
|
179
260
|
});
|
|
180
261
|
socket.on('session_state', (payload) => {
|
|
181
262
|
const applied = this.applyState(payload);
|
|
182
|
-
if (applied)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
263
|
+
if (!applied)
|
|
264
|
+
return;
|
|
265
|
+
// Signed-out tab: a session appeared on this device (a sibling signed in).
|
|
266
|
+
// Acquire it so this tab flips to signed-in; the planted token then
|
|
267
|
+
// reconnects the socket on the authenticated path.
|
|
268
|
+
if (!this.host.getAccessToken()) {
|
|
269
|
+
if ((this.state?.accounts.length ?? 0) > 0) {
|
|
270
|
+
this.requestAcquisition();
|
|
188
271
|
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const active = this.state?.activeAccountId ?? null;
|
|
275
|
+
if (active && active !== this.host.getCurrentAccountId()) {
|
|
276
|
+
void this.bootstrap().catch((error) => {
|
|
277
|
+
loggerUtils_1.logger.warn('[SessionClient] post-push token fetch failed', { component: 'SessionClient' }, error);
|
|
278
|
+
});
|
|
189
279
|
}
|
|
190
280
|
});
|
|
191
281
|
this.socket = socket;
|
|
192
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Run the consumer's session acquisition at most once at a time. A returned
|
|
285
|
+
* promise gates the next attempt (reset on settle), so a failed acquisition
|
|
286
|
+
* can retry on the NEXT push while a burst of identical pushes cannot pile up.
|
|
287
|
+
*/
|
|
288
|
+
requestAcquisition() {
|
|
289
|
+
if (this.acquiring || !this.options.onSessionAppeared)
|
|
290
|
+
return;
|
|
291
|
+
this.acquiring = true;
|
|
292
|
+
let result;
|
|
293
|
+
try {
|
|
294
|
+
result = this.options.onSessionAppeared();
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
this.acquiring = false;
|
|
298
|
+
loggerUtils_1.logger.error('[SessionClient] onSessionAppeared threw', error);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
void Promise.resolve(result).catch((error) => {
|
|
302
|
+
loggerUtils_1.logger.warn('[SessionClient] onSessionAppeared rejected', { component: 'SessionClient' }, error);
|
|
303
|
+
}).finally(() => {
|
|
304
|
+
this.acquiring = false;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Open the same-origin `BroadcastChannel` (web only). A sibling tab that
|
|
309
|
+
* commits a session posts a wake ping; on receipt a signed-in tab re-syncs its
|
|
310
|
+
* device state and a signed-out tab self-acquires — instant + network-free for
|
|
311
|
+
* the common "two tabs of the same origin" case, with no state (and no tokens)
|
|
312
|
+
* ever crossing the channel. No-op on native (no BroadcastChannel).
|
|
313
|
+
*/
|
|
314
|
+
openBroadcastChannel() {
|
|
315
|
+
if (this.channel)
|
|
316
|
+
return;
|
|
317
|
+
const Ctor = globalThis.BroadcastChannel;
|
|
318
|
+
if (typeof Ctor !== 'function')
|
|
319
|
+
return; // native RN / SSR — feature absent
|
|
320
|
+
let channel;
|
|
321
|
+
try {
|
|
322
|
+
channel = new Ctor(SESSION_BROADCAST_CHANNEL);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
loggerUtils_1.logger.debug('[SessionClient] BroadcastChannel unavailable', { component: 'SessionClient' }, error);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
channel.onmessage = (event) => {
|
|
329
|
+
if (!event || typeof event.data !== 'object' || event.data === null)
|
|
330
|
+
return;
|
|
331
|
+
if (event.data.type !== 'commit')
|
|
332
|
+
return;
|
|
333
|
+
// BroadcastChannel never echoes to the posting context, so this is a
|
|
334
|
+
// sibling's commit. Re-sync (signed-in) or acquire (signed-out). Neither
|
|
335
|
+
// path re-posts, so there is no cross-tab ping loop.
|
|
336
|
+
if (this.host.getAccessToken()) {
|
|
337
|
+
void this.bootstrap().catch((error) => {
|
|
338
|
+
loggerUtils_1.logger.warn('[SessionClient] broadcast re-sync failed', { component: 'SessionClient' }, error);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
this.requestAcquisition();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
this.channel = channel;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Wake same-origin sibling tabs after a locally-initiated session mutation.
|
|
349
|
+
* Opens the channel lazily: a sign-in registers the account (`addCurrentAccount`
|
|
350
|
+
* / `switchAccount`) BEFORE `start()` runs, so the ping must not depend on
|
|
351
|
+
* `start()` having opened the channel first.
|
|
352
|
+
*/
|
|
353
|
+
postCommitPing() {
|
|
354
|
+
this.openBroadcastChannel();
|
|
355
|
+
if (!this.channel)
|
|
356
|
+
return;
|
|
357
|
+
try {
|
|
358
|
+
this.channel.postMessage({ type: 'commit', at: Date.now() });
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
loggerUtils_1.logger.debug('[SessionClient] BroadcastChannel post failed', { component: 'SessionClient' }, error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
193
364
|
}
|
|
194
365
|
exports.SessionClient = SessionClient;
|