@bounded-sh/core 0.0.14 → 0.0.16
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/client/live.d.ts +3 -0
- package/dist/client/realtime-store.d.ts +8 -0
- package/dist/index.js +304 -192
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +304 -192
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -3780,24 +3780,19 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
|
|
|
3780
3780
|
/* ------------------------------------------------------------------ */
|
|
3781
3781
|
/* ENV helpers */
|
|
3782
3782
|
/* ------------------------------------------------------------------ */
|
|
3783
|
-
// Canonical `BOUNDED_PRIVATE_KEY` (matches the CLI)
|
|
3784
|
-
//
|
|
3785
|
-
|
|
3786
|
-
const
|
|
3783
|
+
// Canonical `BOUNDED_PRIVATE_KEY` (matches the CLI). Only consulted when no
|
|
3784
|
+
// explicit keypair was provided (createWalletClient passes one).
|
|
3785
|
+
const ENV_KEYPAIR = "BOUNDED_PRIVATE_KEY";
|
|
3786
|
+
const LEGACY_ENV_KEYPAIR = "BOUNDED_SOLANA_KEYPAIR";
|
|
3787
3787
|
function loadKeypairFromEnv() {
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
const v = process.env[name];
|
|
3792
|
-
if (v) {
|
|
3793
|
-
secret = v;
|
|
3794
|
-
found = name;
|
|
3795
|
-
break;
|
|
3796
|
-
}
|
|
3788
|
+
if (process.env[LEGACY_ENV_KEYPAIR]) {
|
|
3789
|
+
throw new Error(`${LEGACY_ENV_KEYPAIR} is no longer supported. Set ${ENV_KEYPAIR} instead, ` +
|
|
3790
|
+
`or pass an explicit keypair via createWalletClient({ keypair }).`);
|
|
3797
3791
|
}
|
|
3792
|
+
const secret = process.env[ENV_KEYPAIR];
|
|
3798
3793
|
if (!secret) {
|
|
3799
3794
|
throw new Error(`No server keypair for this top-level call. The top-level get/set/subscribe/etc. use an ` +
|
|
3800
|
-
`AMBIENT session — set ${
|
|
3795
|
+
`AMBIENT session — set ${ENV_KEYPAIR} to a base-58 secret key (or JSON array) to provide one. ` +
|
|
3801
3796
|
`If you already created a wallet with createWalletClient({ keypair }), call ITS methods instead ` +
|
|
3802
3797
|
`(client.subscribe / client.set / client.get): that client is self-contained and deliberately does ` +
|
|
3803
3798
|
`not set the ambient session, so the top-level functions can't see it.`);
|
|
@@ -3809,7 +3804,7 @@ function loadKeypairFromEnv() {
|
|
|
3809
3804
|
return Keypair.fromSecretKey(secretKey);
|
|
3810
3805
|
}
|
|
3811
3806
|
catch (err) {
|
|
3812
|
-
throw new Error(`Unable to parse ${
|
|
3807
|
+
throw new Error(`Unable to parse ${ENV_KEYPAIR}. Ensure it is valid base-58 or JSON.`);
|
|
3813
3808
|
}
|
|
3814
3809
|
}
|
|
3815
3810
|
/* ------------------------------------------------------------------ */
|
|
@@ -3876,7 +3871,7 @@ class ServerSessionManager {
|
|
|
3876
3871
|
this.session = session;
|
|
3877
3872
|
}
|
|
3878
3873
|
/* ---------------------------------------------- *
|
|
3879
|
-
* CLEAR (e.g. after logout
|
|
3874
|
+
* CLEAR (e.g. after explicit logout)
|
|
3880
3875
|
* ---------------------------------------------- */
|
|
3881
3876
|
clearSession() {
|
|
3882
3877
|
this.session = null;
|
|
@@ -3900,11 +3895,6 @@ class ServerSessionManager {
|
|
|
3900
3895
|
/* The default singleton instance (reads keypair from env) */
|
|
3901
3896
|
ServerSessionManager.instance = new ServerSessionManager();
|
|
3902
3897
|
|
|
3903
|
-
var serverSessionManager = /*#__PURE__*/Object.freeze({
|
|
3904
|
-
__proto__: null,
|
|
3905
|
-
ServerSessionManager: ServerSessionManager
|
|
3906
|
-
});
|
|
3907
|
-
|
|
3908
3898
|
/**
|
|
3909
3899
|
* Safe base64 helpers for bounded-core.
|
|
3910
3900
|
*
|
|
@@ -4129,13 +4119,6 @@ async function refreshAuthSessionOnce(appId, isServer) {
|
|
|
4129
4119
|
async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
4130
4120
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
4131
4121
|
const config = await getConfig();
|
|
4132
|
-
let hasRetriedAfterServerSessionReset = false;
|
|
4133
|
-
const clearServerSession = async () => {
|
|
4134
|
-
if (!config.isServer)
|
|
4135
|
-
return;
|
|
4136
|
-
const { ServerSessionManager } = await Promise.resolve().then(function () { return serverSessionManager; });
|
|
4137
|
-
ServerSessionManager.instance.clearSession();
|
|
4138
|
-
};
|
|
4139
4122
|
async function executeRequest() {
|
|
4140
4123
|
var _a;
|
|
4141
4124
|
// When _getAuthHeaders is provided (wallet client), use it as the sole auth source.
|
|
@@ -4185,13 +4168,7 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
4185
4168
|
}
|
|
4186
4169
|
return await executeRequest();
|
|
4187
4170
|
}
|
|
4188
|
-
catch (
|
|
4189
|
-
// Server-side fallback: clear global session and retry once
|
|
4190
|
-
if (config.isServer && !hasRetriedAfterServerSessionReset) {
|
|
4191
|
-
hasRetriedAfterServerSessionReset = true;
|
|
4192
|
-
await clearServerSession();
|
|
4193
|
-
return await executeRequest();
|
|
4194
|
-
}
|
|
4171
|
+
catch (_j) {
|
|
4195
4172
|
throw error;
|
|
4196
4173
|
}
|
|
4197
4174
|
}
|
|
@@ -4338,99 +4315,54 @@ function normalizeReadResult(responseData, pathIsDocument) {
|
|
|
4338
4315
|
}
|
|
4339
4316
|
return responseData;
|
|
4340
4317
|
}
|
|
4341
|
-
function hashForKey$
|
|
4318
|
+
function hashForKey$2(value) {
|
|
4342
4319
|
let h = 5381;
|
|
4343
4320
|
for (let i = 0; i < value.length; i++) {
|
|
4344
4321
|
h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;
|
|
4345
4322
|
}
|
|
4346
4323
|
return h.toString(36);
|
|
4347
4324
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4325
|
+
function authValueFromHeaders(headers) {
|
|
4326
|
+
return (headers === null || headers === void 0 ? void 0 : headers.Authorization) || (headers === null || headers === void 0 ? void 0 : headers.authorization) || '';
|
|
4327
|
+
}
|
|
4328
|
+
function principalFromAuthValue(authValue) {
|
|
4329
|
+
return authValue ? `h${hashForKey$2(authValue)}` : 'anon';
|
|
4330
|
+
}
|
|
4354
4331
|
function principalFromIdToken$1(idToken) {
|
|
4355
|
-
|
|
4356
|
-
return 'anon';
|
|
4357
|
-
try {
|
|
4358
|
-
const parts = idToken.split('.');
|
|
4359
|
-
if (parts.length < 2)
|
|
4360
|
-
return `t${hashForKey$1(idToken)}`;
|
|
4361
|
-
const payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
4362
|
-
const subject =
|
|
4363
|
-
// Prefer the universal identity (@user.id) — it is the stable principal a
|
|
4364
|
-
// request presents (equals the wallet for wallet logins; the account id for
|
|
4365
|
-
// email/social logins, which carry no walletAddress). Keying the read cache
|
|
4366
|
-
// on it keeps an email user's private snapshot scoped to that user.
|
|
4367
|
-
payload['custom:userId'] ||
|
|
4368
|
-
payload['custom:walletAddress'] ||
|
|
4369
|
-
payload.walletAddress ||
|
|
4370
|
-
payload.sub ||
|
|
4371
|
-
payload.address;
|
|
4372
|
-
if (subject)
|
|
4373
|
-
return `s${hashForKey$1(String(subject))}`;
|
|
4374
|
-
return `t${hashForKey$1(idToken)}`;
|
|
4375
|
-
}
|
|
4376
|
-
catch (_a) {
|
|
4377
|
-
return `t${hashForKey$1(idToken)}`;
|
|
4378
|
-
}
|
|
4332
|
+
return idToken ? `t${hashForKey$2(idToken)}` : 'anon';
|
|
4379
4333
|
}
|
|
4380
4334
|
/**
|
|
4381
4335
|
* SECURITY (H1): Read caches must be keyed by the caller's principal, not just
|
|
4382
4336
|
* by path/filter/shape. In a shared process / SSR worker / browser login-switch,
|
|
4383
4337
|
* keying by path alone lets User B receive User A's cached private read before
|
|
4384
4338
|
* any server read rule runs. This returns `appId:<principal>` for the identity a
|
|
4385
|
-
* given read will actually authenticate as
|
|
4386
|
-
*
|
|
4387
|
-
*
|
|
4388
|
-
*
|
|
4389
|
-
* - otherwise the ambient logged-in session's idToken subject (or `anon`)
|
|
4339
|
+
* given read will actually authenticate as. This intentionally treats JWTs as
|
|
4340
|
+
* opaque unverified bearer material — never decoded claims and never caller
|
|
4341
|
+
* identity hints such as `_walletAddress`. A forged token that merely claims
|
|
4342
|
+
* another user's `sub` must miss that user's cache and go to the server.
|
|
4390
4343
|
*/
|
|
4391
4344
|
async function getReadPrincipalKey(overrides) {
|
|
4392
|
-
var _a, _b;
|
|
4393
4345
|
const config = await getConfig();
|
|
4394
4346
|
const appId = config.appId || '';
|
|
4395
|
-
//
|
|
4396
|
-
|
|
4397
|
-
|
|
4347
|
+
// makeApiRequest applies overrides.headers AFTER its computed auth header, so
|
|
4348
|
+
// caller-supplied Authorization is the real request auth when present.
|
|
4349
|
+
const directAuth = authValueFromHeaders(overrides === null || overrides === void 0 ? void 0 : overrides.headers);
|
|
4350
|
+
if (directAuth) {
|
|
4351
|
+
return `${appId}:${principalFromAuthValue(directAuth)}`;
|
|
4398
4352
|
}
|
|
4399
|
-
// Per-request auth-header override (wallet client). Key by the
|
|
4400
|
-
// it produces
|
|
4353
|
+
// Per-request auth-header override (wallet client). Key by the exact opaque
|
|
4354
|
+
// header it produces, not decoded claims or the unverified _walletAddress hint.
|
|
4401
4355
|
if (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders) {
|
|
4402
4356
|
try {
|
|
4403
4357
|
const headers = await overrides._getAuthHeaders();
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
// Decode the bearer token's subject when present for a stable key across
|
|
4408
|
-
// refreshes; otherwise hash whatever header material we were given.
|
|
4409
|
-
const bearer = authValue.startsWith('Bearer ')
|
|
4410
|
-
? authValue.slice('Bearer '.length)
|
|
4411
|
-
: authValue;
|
|
4412
|
-
const principal = bearer ? principalFromIdToken$1(bearer) : 'anon';
|
|
4413
|
-
return `${appId}:o${principal}`;
|
|
4414
|
-
}
|
|
4415
|
-
catch (_c) {
|
|
4358
|
+
return `${appId}:o${principalFromAuthValue(authValueFromHeaders(headers))}`;
|
|
4359
|
+
}
|
|
4360
|
+
catch (_a) {
|
|
4416
4361
|
// If we can't resolve the override identity, use a unique-ish key so we
|
|
4417
4362
|
// never collide with (and serve) another principal's cached entry.
|
|
4418
|
-
return `${appId}:o${hashForKey$
|
|
4363
|
+
return `${appId}:o${hashForKey$2(String(Date.now()) + Math.random())}`;
|
|
4419
4364
|
}
|
|
4420
4365
|
}
|
|
4421
|
-
// Direct per-request header override. makeApiRequest applies overrides.headers
|
|
4422
|
-
// AFTER its computed auth header (api.ts), so a caller-supplied Authorization
|
|
4423
|
-
// is the REAL request auth — key the cache by it so an entry is never served
|
|
4424
|
-
// to a different principal under the ambient key. (Hardening: no in-repo read
|
|
4425
|
-
// caller passes this today, but the cache must never trail the actual auth.)
|
|
4426
|
-
const directAuth = ((_a = overrides === null || overrides === void 0 ? void 0 : overrides.headers) === null || _a === void 0 ? void 0 : _a.Authorization) ||
|
|
4427
|
-
((_b = overrides === null || overrides === void 0 ? void 0 : overrides.headers) === null || _b === void 0 ? void 0 : _b.authorization);
|
|
4428
|
-
if (directAuth) {
|
|
4429
|
-
const bearer = directAuth.startsWith('Bearer ')
|
|
4430
|
-
? directAuth.slice('Bearer '.length)
|
|
4431
|
-
: directAuth;
|
|
4432
|
-
return `${appId}:h${bearer ? principalFromIdToken$1(bearer) : hashForKey$1(directAuth)}`;
|
|
4433
|
-
}
|
|
4434
4366
|
// Ambient session principal.
|
|
4435
4367
|
const idToken = await getIdToken(config.isServer);
|
|
4436
4368
|
return `${appId}:${principalFromIdToken$1(idToken)}`;
|
|
@@ -4730,9 +4662,9 @@ async function get(path, opts = {}) {
|
|
|
4730
4662
|
const shapeKey = opts.shape ? JSON.stringify(opts.shape) : '';
|
|
4731
4663
|
const includeSubPathsKey = opts.includeSubPaths ? ':subpaths' : '';
|
|
4732
4664
|
const limitKey = opts.limit !== undefined ? `:l${opts.limit}` : '';
|
|
4733
|
-
const cursorKey = opts.cursor ? `:c${hashForKey$
|
|
4734
|
-
const filterKey = opts.filter ? `:f${hashForKey$
|
|
4735
|
-
const sortKey = opts.sort ? `:s${hashForKey$
|
|
4665
|
+
const cursorKey = opts.cursor ? `:c${hashForKey$2(opts.cursor)}` : '';
|
|
4666
|
+
const filterKey = opts.filter ? `:f${hashForKey$2(JSON.stringify(opts.filter))}` : '';
|
|
4667
|
+
const sortKey = opts.sort ? `:s${hashForKey$2(JSON.stringify(opts.sort))}` : '';
|
|
4736
4668
|
const principalKey = await getReadPrincipalKey(opts._overrides);
|
|
4737
4669
|
const cacheKey = `${principalKey}|${normalizedPath}:${opts.prompt || ''}${filterKey}${sortKey}${includeSubPathsKey}:${shapeKey}${limitKey}${cursorKey}`;
|
|
4738
4670
|
const now = Date.now();
|
|
@@ -5482,7 +5414,7 @@ let reconnectInProgress = null;
|
|
|
5482
5414
|
function generateSubscriptionId() {
|
|
5483
5415
|
return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
5484
5416
|
}
|
|
5485
|
-
function hashForKey(value) {
|
|
5417
|
+
function hashForKey$1(value) {
|
|
5486
5418
|
let h = 5381;
|
|
5487
5419
|
for (let i = 0; i < value.length; i++) {
|
|
5488
5420
|
h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;
|
|
@@ -5501,49 +5433,23 @@ function getCacheKey(path, prompt, shape, limit, cursor, filter, identity) {
|
|
|
5501
5433
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
5502
5434
|
const shapeKey = shape && Object.keys(shape).length > 0 ? JSON.stringify(shape) : '';
|
|
5503
5435
|
const limitKey = limit !== undefined ? `:l${limit}` : '';
|
|
5504
|
-
const cursorKey = cursor ? `:c${hashForKey(cursor)}` : '';
|
|
5505
|
-
const filterKey = filter && Object.keys(filter).length > 0 ? `:f${hashForKey(JSON.stringify(filter))}` : '';
|
|
5436
|
+
const cursorKey = cursor ? `:c${hashForKey$1(cursor)}` : '';
|
|
5437
|
+
const filterKey = filter && Object.keys(filter).length > 0 ? `:f${hashForKey$1(JSON.stringify(filter))}` : '';
|
|
5506
5438
|
const identityKey = identity || 'anon';
|
|
5507
5439
|
return `${identityKey}|${normalizedPath}:${prompt || 'default'}:${shapeKey}${limitKey}${cursorKey}${filterKey}`;
|
|
5508
5440
|
}
|
|
5509
5441
|
/**
|
|
5510
|
-
* Derive
|
|
5511
|
-
*
|
|
5512
|
-
*
|
|
5442
|
+
* Derive an opaque identity string for the bearer material a subscription sends.
|
|
5443
|
+
* JWT payloads are deliberately not trusted here: cache isolation must happen
|
|
5444
|
+
* before the server has verified any claims.
|
|
5513
5445
|
*/
|
|
5514
5446
|
function principalFromIdToken(idToken) {
|
|
5515
|
-
|
|
5516
|
-
return 'anon';
|
|
5517
|
-
try {
|
|
5518
|
-
const parts = idToken.split('.');
|
|
5519
|
-
if (parts.length < 2)
|
|
5520
|
-
return `t${hashForKey(idToken)}`;
|
|
5521
|
-
const payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
5522
|
-
const subject =
|
|
5523
|
-
// Universal identity (@user.id) first — the stable principal a request
|
|
5524
|
-
// presents (the account id for email/social logins, which carry no
|
|
5525
|
-
// walletAddress). Keeps the H1 response-cache scoping correct for them.
|
|
5526
|
-
payload['custom:userId'] ||
|
|
5527
|
-
payload['custom:walletAddress'] ||
|
|
5528
|
-
payload.walletAddress ||
|
|
5529
|
-
payload.sub ||
|
|
5530
|
-
payload.address;
|
|
5531
|
-
if (subject)
|
|
5532
|
-
return `s${hashForKey(String(subject))}`;
|
|
5533
|
-
return `t${hashForKey(idToken)}`;
|
|
5534
|
-
}
|
|
5535
|
-
catch (_a) {
|
|
5536
|
-
return `t${hashForKey(idToken)}`;
|
|
5537
|
-
}
|
|
5447
|
+
return idToken ? `t${hashForKey$1(idToken)}` : 'anon';
|
|
5538
5448
|
}
|
|
5539
5449
|
async function getSubscriptionIdentity(effectiveAppId, isServer, overrides) {
|
|
5540
|
-
// Per-subscription wallet override (server WalletClient.subscribe): key by
|
|
5541
|
-
// wallet's own token, mirroring getReadPrincipalKey
|
|
5542
|
-
//
|
|
5543
|
-
// cached snapshots are never crossed with another principal's.
|
|
5544
|
-
if (overrides === null || overrides === void 0 ? void 0 : overrides._walletAddress) {
|
|
5545
|
-
return `${effectiveAppId}:w${overrides._walletAddress}`;
|
|
5546
|
-
}
|
|
5450
|
+
// Per-subscription wallet override (server WalletClient.subscribe): key by
|
|
5451
|
+
// the wallet's own opaque token material, mirroring getReadPrincipalKey. Do
|
|
5452
|
+
// not trust decoded claims or the unverified _walletAddress hint.
|
|
5547
5453
|
if (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders) {
|
|
5548
5454
|
try {
|
|
5549
5455
|
const bearer = bearerFromAuthHeaders(await overrides._getAuthHeaders());
|
|
@@ -5914,7 +5820,7 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5914
5820
|
return connection;
|
|
5915
5821
|
}
|
|
5916
5822
|
function handleServerMessage(connection, message) {
|
|
5917
|
-
var _a
|
|
5823
|
+
var _a;
|
|
5918
5824
|
switch (message.type) {
|
|
5919
5825
|
case 'authenticated': {
|
|
5920
5826
|
connection.isAuthenticating = false;
|
|
@@ -6008,28 +5914,27 @@ function handleServerMessage(connection, message) {
|
|
|
6008
5914
|
err.subscriptionId = message.subscriptionId;
|
|
6009
5915
|
return err;
|
|
6010
5916
|
};
|
|
5917
|
+
const wsError = buildWsError();
|
|
6011
5918
|
// Handle CRUD request errors (requestId present)
|
|
6012
5919
|
if (message.requestId) {
|
|
6013
5920
|
const pendingReq = connection.pendingRequests.get(message.requestId);
|
|
6014
5921
|
if (pendingReq) {
|
|
6015
5922
|
connection.pendingRequests.delete(message.requestId);
|
|
6016
5923
|
clearTimeout(pendingReq.timer);
|
|
6017
|
-
pendingReq.reject(
|
|
5924
|
+
pendingReq.reject(wsError);
|
|
6018
5925
|
}
|
|
6019
5926
|
}
|
|
6020
5927
|
if (message.subscriptionId) {
|
|
6021
5928
|
// Reject pending subscription if this is a subscription error
|
|
6022
5929
|
const pending = connection.pendingSubscriptions.get(message.subscriptionId);
|
|
6023
5930
|
if (pending) {
|
|
6024
|
-
pending.reject(
|
|
5931
|
+
pending.reject(wsError);
|
|
6025
5932
|
connection.pendingSubscriptions.delete(message.subscriptionId);
|
|
6026
5933
|
}
|
|
6027
5934
|
// Notify error callbacks for this subscription
|
|
6028
5935
|
const subscription = connection.subscriptions.get(message.subscriptionId);
|
|
6029
5936
|
if (subscription) {
|
|
6030
|
-
|
|
6031
|
-
(_b = callback.onError) === null || _b === void 0 ? void 0 : _b.call(callback, buildWsError());
|
|
6032
|
-
}
|
|
5937
|
+
notifyErrorCallbacks(subscription, wsError);
|
|
6033
5938
|
}
|
|
6034
5939
|
}
|
|
6035
5940
|
break;
|
|
@@ -6082,6 +5987,25 @@ function notifyCallbacks(subscription, data) {
|
|
|
6082
5987
|
}
|
|
6083
5988
|
}
|
|
6084
5989
|
}
|
|
5990
|
+
function notifyErrorCallbacks(subscription, error) {
|
|
5991
|
+
let delivered = false;
|
|
5992
|
+
const callbacks = subscription.callbacks.slice();
|
|
5993
|
+
for (const callback of callbacks) {
|
|
5994
|
+
if (!callback.onError)
|
|
5995
|
+
continue;
|
|
5996
|
+
delivered = true;
|
|
5997
|
+
try {
|
|
5998
|
+
callback.onError(error);
|
|
5999
|
+
}
|
|
6000
|
+
catch (callbackError) {
|
|
6001
|
+
console.error('[WS v2] Error in subscription error callback:', callbackError);
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
if (delivered) {
|
|
6005
|
+
error.__boundedDeliveredToOnError = true;
|
|
6006
|
+
}
|
|
6007
|
+
return delivered;
|
|
6008
|
+
}
|
|
6085
6009
|
// WebSocket readyState constants
|
|
6086
6010
|
const WS_READY_STATE_OPEN = 1;
|
|
6087
6011
|
const WS_READY_STATE_CLOSED = 3;
|
|
@@ -6156,10 +6080,8 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
|
|
|
6156
6080
|
const authTokenProvider = (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders)
|
|
6157
6081
|
? async () => bearerFromAuthHeaders(await overrides._getAuthHeaders()) || null
|
|
6158
6082
|
: undefined;
|
|
6159
|
-
const principalKey = (overrides === null || overrides === void 0 ? void 0 : overrides._walletAddress)
|
|
6160
|
-
? `w${overrides._walletAddress}`
|
|
6161
|
-
: (authTokenProvider ? `o${principalFromIdToken(await authTokenProvider())}` : undefined);
|
|
6162
6083
|
const identity = await getSubscriptionIdentity(effectiveAppId, config.isServer, overrides);
|
|
6084
|
+
const principalKey = authTokenProvider ? identity : undefined;
|
|
6163
6085
|
const cacheKey = getCacheKey(normalizedPath, subscriptionOptions.prompt, subscriptionOptions.shape, subscriptionOptions.limit, subscriptionOptions.cursor, subscriptionOptions.filter, identity);
|
|
6164
6086
|
// Deliver cached data immediately if available
|
|
6165
6087
|
const cachedEntry = responseCache.get(cacheKey);
|
|
@@ -6238,7 +6160,18 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
|
|
|
6238
6160
|
await subscriptionPromise;
|
|
6239
6161
|
}
|
|
6240
6162
|
catch (error) {
|
|
6241
|
-
|
|
6163
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
6164
|
+
if (!err.__boundedDeliveredToOnError) {
|
|
6165
|
+
notifyErrorCallbacks(subscription, err);
|
|
6166
|
+
}
|
|
6167
|
+
if (!subscriptionOptions.onError) {
|
|
6168
|
+
connection.pendingSubscriptions.delete(subscriptionId);
|
|
6169
|
+
connection.subscriptions.delete(subscriptionId);
|
|
6170
|
+
throw err;
|
|
6171
|
+
}
|
|
6172
|
+
if (!err.__boundedDeliveredToOnError) {
|
|
6173
|
+
console.warn('[WS v2] Subscription confirmation failed, keeping for reconnect recovery:', error);
|
|
6174
|
+
}
|
|
6242
6175
|
}
|
|
6243
6176
|
}
|
|
6244
6177
|
// Return unsubscribe function
|
|
@@ -6401,6 +6334,13 @@ async function doReconnectWithNewAuth() {
|
|
|
6401
6334
|
catch (error) {
|
|
6402
6335
|
console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
|
|
6403
6336
|
}
|
|
6337
|
+
try {
|
|
6338
|
+
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return realtimeStore; });
|
|
6339
|
+
await reconnectRealtimeStoreWithNewAuth();
|
|
6340
|
+
}
|
|
6341
|
+
catch (error) {
|
|
6342
|
+
console.warn('[WS v2] Failed to reset legacy realtime store on auth change:', error);
|
|
6343
|
+
}
|
|
6404
6344
|
for (const [appId, connection] of connections) {
|
|
6405
6345
|
if (!connection.ws) {
|
|
6406
6346
|
continue;
|
|
@@ -6868,6 +6808,16 @@ async function idbSet(key, value) {
|
|
|
6868
6808
|
// RealtimeStore
|
|
6869
6809
|
// ---------------------------------------------------------------------------
|
|
6870
6810
|
let nextRequestId = 1;
|
|
6811
|
+
function hashForKey(value) {
|
|
6812
|
+
let h = 5381;
|
|
6813
|
+
for (let i = 0; i < value.length; i++) {
|
|
6814
|
+
h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;
|
|
6815
|
+
}
|
|
6816
|
+
return h.toString(36);
|
|
6817
|
+
}
|
|
6818
|
+
function principalFromToken(token) {
|
|
6819
|
+
return token ? `t${hashForKey(token)}` : 'anon';
|
|
6820
|
+
}
|
|
6871
6821
|
class RealtimeStore {
|
|
6872
6822
|
constructor() {
|
|
6873
6823
|
this.ws = null;
|
|
@@ -6883,7 +6833,9 @@ class RealtimeStore {
|
|
|
6883
6833
|
this.idbDirtyKeys = new Set();
|
|
6884
6834
|
this.closed = false;
|
|
6885
6835
|
this.authToken = null;
|
|
6836
|
+
this.authPrincipalKey = 'anon';
|
|
6886
6837
|
this.authenticating = false;
|
|
6838
|
+
this.suppressNextReconnect = false;
|
|
6887
6839
|
this.isServer = false;
|
|
6888
6840
|
this.tokenRefreshTimer = null;
|
|
6889
6841
|
// -----------------------------------------------------------------------
|
|
@@ -6903,34 +6855,98 @@ class RealtimeStore {
|
|
|
6903
6855
|
this.startTokenRefresh();
|
|
6904
6856
|
}
|
|
6905
6857
|
async refreshToken() {
|
|
6858
|
+
let token = null;
|
|
6906
6859
|
try {
|
|
6907
6860
|
const { getIdToken } = await Promise.resolve().then(function () { return utils; });
|
|
6908
|
-
|
|
6909
|
-
if (token)
|
|
6910
|
-
this.authToken = token;
|
|
6861
|
+
token = await getIdToken(this.isServer);
|
|
6911
6862
|
}
|
|
6912
6863
|
catch ( /* no auth available */_a) { /* no auth available */ }
|
|
6864
|
+
this.authToken = token !== null && token !== void 0 ? token : null;
|
|
6865
|
+
this.authPrincipalKey = principalFromToken(this.authToken);
|
|
6913
6866
|
}
|
|
6914
6867
|
startTokenRefresh() {
|
|
6915
6868
|
if (this.tokenRefreshTimer)
|
|
6916
6869
|
return;
|
|
6917
6870
|
this.tokenRefreshTimer = setInterval(async () => {
|
|
6918
|
-
|
|
6919
|
-
const prevToken = this.authToken;
|
|
6871
|
+
const prevPrincipal = this.authPrincipalKey;
|
|
6920
6872
|
await this.refreshToken();
|
|
6921
|
-
if (this.
|
|
6922
|
-
|
|
6923
|
-
this.
|
|
6873
|
+
if (this.authPrincipalKey !== prevPrincipal) {
|
|
6874
|
+
await this.applyAuthPrincipalChange();
|
|
6875
|
+
if (this.subscriptions.size > 0) {
|
|
6876
|
+
await this.ensureConnected().catch(() => {
|
|
6877
|
+
this.setAllSubscriptionStatus('error');
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6924
6880
|
}
|
|
6925
6881
|
}, 5 * 60 * 1000); // Check every 5 minutes
|
|
6926
6882
|
}
|
|
6883
|
+
async ensureInitialized() {
|
|
6884
|
+
if (this.appId)
|
|
6885
|
+
return;
|
|
6886
|
+
if (!this.initPromise)
|
|
6887
|
+
this.initPromise = this.init();
|
|
6888
|
+
await this.initPromise;
|
|
6889
|
+
}
|
|
6890
|
+
async ensureCurrentAuth() {
|
|
6891
|
+
await this.ensureInitialized();
|
|
6892
|
+
const prevPrincipal = this.authPrincipalKey;
|
|
6893
|
+
await this.refreshToken();
|
|
6894
|
+
if (this.authPrincipalKey !== prevPrincipal) {
|
|
6895
|
+
await this.applyAuthPrincipalChange();
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
rekeySubscriptionsForPrincipal() {
|
|
6899
|
+
const subs = Array.from(this.subscriptions.values());
|
|
6900
|
+
this.subscriptions.clear();
|
|
6901
|
+
for (const sub of subs) {
|
|
6902
|
+
this.subscriptions.set(this.getSubKey(sub.path, sub.options), sub);
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
async applyAuthPrincipalChange() {
|
|
6906
|
+
if (this.idbFlushTimer) {
|
|
6907
|
+
clearTimeout(this.idbFlushTimer);
|
|
6908
|
+
this.idbFlushTimer = null;
|
|
6909
|
+
}
|
|
6910
|
+
this.idbDirtyKeys.clear();
|
|
6911
|
+
this.rekeySubscriptionsForPrincipal();
|
|
6912
|
+
for (const sub of this.subscriptions.values()) {
|
|
6913
|
+
sub.docs.clear();
|
|
6914
|
+
sub.ref.current = sub.docs;
|
|
6915
|
+
sub.error = null;
|
|
6916
|
+
sub.isStale = false;
|
|
6917
|
+
let loaded = false;
|
|
6918
|
+
if (sub.tier !== 'ephemeral') {
|
|
6919
|
+
const cached = await idbGet(this.idbKey(sub.path));
|
|
6920
|
+
if (cached && Array.isArray(cached)) {
|
|
6921
|
+
for (const doc of cached) {
|
|
6922
|
+
if (doc && doc._id)
|
|
6923
|
+
sub.docs.set(doc._id, doc);
|
|
6924
|
+
}
|
|
6925
|
+
sub.ref.current = sub.docs;
|
|
6926
|
+
loaded = sub.docs.size > 0;
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6929
|
+
sub.status = loaded ? 'cached' : 'loading';
|
|
6930
|
+
sub.isStale = loaded;
|
|
6931
|
+
if (loaded)
|
|
6932
|
+
this.notifySubscription(sub);
|
|
6933
|
+
else
|
|
6934
|
+
this.notifyState(sub);
|
|
6935
|
+
}
|
|
6936
|
+
if (this.ws) {
|
|
6937
|
+
const ws = this.ws;
|
|
6938
|
+
this.ws = null;
|
|
6939
|
+
this.connectPromise = null;
|
|
6940
|
+
this.suppressNextReconnect = true;
|
|
6941
|
+
try {
|
|
6942
|
+
ws.close(1000, 'Auth changed');
|
|
6943
|
+
}
|
|
6944
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6927
6947
|
async ensureConnected() {
|
|
6928
6948
|
var _a;
|
|
6929
|
-
|
|
6930
|
-
if (!this.initPromise)
|
|
6931
|
-
this.initPromise = this.init();
|
|
6932
|
-
await this.initPromise;
|
|
6933
|
-
}
|
|
6949
|
+
await this.ensureCurrentAuth();
|
|
6934
6950
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)
|
|
6935
6951
|
return;
|
|
6936
6952
|
if (this.connectPromise)
|
|
@@ -7014,11 +7030,20 @@ class RealtimeStore {
|
|
|
7014
7030
|
ws.addEventListener('close', () => {
|
|
7015
7031
|
if (authTimer)
|
|
7016
7032
|
clearTimeout(authTimer);
|
|
7033
|
+
if (this.ws !== ws) {
|
|
7034
|
+
if (this.suppressNextReconnect)
|
|
7035
|
+
this.suppressNextReconnect = false;
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
7017
7038
|
this.authenticating = false;
|
|
7018
7039
|
this.ws = null;
|
|
7019
7040
|
this.connectPromise = null;
|
|
7020
7041
|
this.rejectAllPending('WebSocket closed');
|
|
7021
7042
|
this.setAllSubscriptionStatus('reconnecting');
|
|
7043
|
+
if (this.suppressNextReconnect) {
|
|
7044
|
+
this.suppressNextReconnect = false;
|
|
7045
|
+
return;
|
|
7046
|
+
}
|
|
7022
7047
|
this.scheduleReconnect();
|
|
7023
7048
|
});
|
|
7024
7049
|
});
|
|
@@ -7194,14 +7219,34 @@ class RealtimeStore {
|
|
|
7194
7219
|
}
|
|
7195
7220
|
}
|
|
7196
7221
|
handleError(msg) {
|
|
7197
|
-
var _a;
|
|
7222
|
+
var _a, _b, _c;
|
|
7223
|
+
const error = new Error((_a = msg.message) !== null && _a !== void 0 ? _a : (msg.code ? `${msg.code}: Server error` : 'Server error'));
|
|
7224
|
+
if (msg.code)
|
|
7225
|
+
error.code = msg.code;
|
|
7226
|
+
if (msg.subscriptionId || msg.id)
|
|
7227
|
+
error.subscriptionId = (_b = msg.subscriptionId) !== null && _b !== void 0 ? _b : msg.id;
|
|
7198
7228
|
const requestId = msg.requestId;
|
|
7199
7229
|
if (requestId) {
|
|
7200
7230
|
const pending = this.pendingRequests.get(requestId);
|
|
7201
7231
|
if (pending) {
|
|
7202
7232
|
this.pendingRequests.delete(requestId);
|
|
7203
7233
|
clearTimeout(pending.timeout);
|
|
7204
|
-
pending.reject(
|
|
7234
|
+
pending.reject(error);
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
const subId = (_c = msg.subscriptionId) !== null && _c !== void 0 ? _c : msg.id;
|
|
7238
|
+
if (subId) {
|
|
7239
|
+
const sub = this.findSubscriptionById(subId);
|
|
7240
|
+
if (sub) {
|
|
7241
|
+
sub.status = 'error';
|
|
7242
|
+
sub.error = error;
|
|
7243
|
+
this.notifyState(sub);
|
|
7244
|
+
for (const callback of Array.from(sub.errorCallbacks)) {
|
|
7245
|
+
try {
|
|
7246
|
+
callback(error);
|
|
7247
|
+
}
|
|
7248
|
+
catch ( /* swallow */_d) { /* swallow */ }
|
|
7249
|
+
}
|
|
7205
7250
|
}
|
|
7206
7251
|
}
|
|
7207
7252
|
}
|
|
@@ -7210,6 +7255,7 @@ class RealtimeStore {
|
|
|
7210
7255
|
// -----------------------------------------------------------------------
|
|
7211
7256
|
async subscribe(path, opts = {}) {
|
|
7212
7257
|
var _a;
|
|
7258
|
+
await this.ensureCurrentAuth();
|
|
7213
7259
|
const tier = (_a = opts.tier) !== null && _a !== void 0 ? _a : 'durable';
|
|
7214
7260
|
const subKey = this.getSubKey(path, opts);
|
|
7215
7261
|
let sub = this.subscriptions.get(subKey);
|
|
@@ -7219,6 +7265,8 @@ class RealtimeStore {
|
|
|
7219
7265
|
sub.callbacks.add(opts.onData);
|
|
7220
7266
|
if (opts.onState)
|
|
7221
7267
|
sub.stateCallbacks.add(opts.onState);
|
|
7268
|
+
if (opts.onError)
|
|
7269
|
+
sub.errorCallbacks.add(opts.onError);
|
|
7222
7270
|
// Immediately deliver current state
|
|
7223
7271
|
if (opts.onData && sub.docs.size > 0) {
|
|
7224
7272
|
opts.onData(this.docsToArray(sub));
|
|
@@ -7226,7 +7274,7 @@ class RealtimeStore {
|
|
|
7226
7274
|
if (opts.onState) {
|
|
7227
7275
|
opts.onState(this.getState(sub));
|
|
7228
7276
|
}
|
|
7229
|
-
return this.createUnsubscribe(subKey, opts.onData, opts.onState);
|
|
7277
|
+
return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);
|
|
7230
7278
|
}
|
|
7231
7279
|
// New subscription
|
|
7232
7280
|
const subId = `sub_${nextRequestId++}`;
|
|
@@ -7241,6 +7289,7 @@ class RealtimeStore {
|
|
|
7241
7289
|
error: null,
|
|
7242
7290
|
callbacks: new Set(opts.onData ? [opts.onData] : []),
|
|
7243
7291
|
stateCallbacks: new Set(opts.onState ? [opts.onState] : []),
|
|
7292
|
+
errorCallbacks: new Set(opts.onError ? [opts.onError] : []),
|
|
7244
7293
|
ref: { current: new Map() },
|
|
7245
7294
|
};
|
|
7246
7295
|
this.subscriptions.set(subKey, sub);
|
|
@@ -7270,7 +7319,7 @@ class RealtimeStore {
|
|
|
7270
7319
|
sub.error = new Error('Connection failed');
|
|
7271
7320
|
this.notifyState(sub);
|
|
7272
7321
|
}
|
|
7273
|
-
return this.createUnsubscribe(subKey, opts.onData, opts.onState);
|
|
7322
|
+
return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);
|
|
7274
7323
|
}
|
|
7275
7324
|
getRef(path, opts = {}) {
|
|
7276
7325
|
var _a;
|
|
@@ -7344,6 +7393,7 @@ class RealtimeStore {
|
|
|
7344
7393
|
}
|
|
7345
7394
|
}
|
|
7346
7395
|
async get(path) {
|
|
7396
|
+
await this.ensureCurrentAuth();
|
|
7347
7397
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
7348
7398
|
// Check local subscriptions first
|
|
7349
7399
|
const collectionPath = this.getCollectionPath(normalizedPath);
|
|
@@ -7524,7 +7574,7 @@ class RealtimeStore {
|
|
|
7524
7574
|
return docPath;
|
|
7525
7575
|
}
|
|
7526
7576
|
getSubKey(path, opts) {
|
|
7527
|
-
const parts = [path];
|
|
7577
|
+
const parts = [this.appId, this.authPrincipalKey, path];
|
|
7528
7578
|
if (opts.filter)
|
|
7529
7579
|
parts.push(JSON.stringify(opts.filter));
|
|
7530
7580
|
if (opts.prompt)
|
|
@@ -7534,7 +7584,7 @@ class RealtimeStore {
|
|
|
7534
7584
|
return parts.join('::');
|
|
7535
7585
|
}
|
|
7536
7586
|
idbKey(path) {
|
|
7537
|
-
return `${this.appId}:${path}`;
|
|
7587
|
+
return `${this.appId}:${this.authPrincipalKey}:${path}`;
|
|
7538
7588
|
}
|
|
7539
7589
|
markIdbDirty(path) {
|
|
7540
7590
|
const sub = this.findSubscriptionByPath(path);
|
|
@@ -7559,18 +7609,23 @@ class RealtimeStore {
|
|
|
7559
7609
|
}
|
|
7560
7610
|
}
|
|
7561
7611
|
}
|
|
7562
|
-
createUnsubscribe(subKey, onData, onState) {
|
|
7612
|
+
createUnsubscribe(subKey, subId, onData, onState, onError) {
|
|
7563
7613
|
return async () => {
|
|
7564
|
-
|
|
7614
|
+
var _a;
|
|
7615
|
+
const sub = (_a = this.subscriptions.get(subKey)) !== null && _a !== void 0 ? _a : this.findSubscriptionById(subId);
|
|
7565
7616
|
if (!sub)
|
|
7566
7617
|
return;
|
|
7618
|
+
const currentSubKey = this.getSubKey(sub.path, sub.options);
|
|
7567
7619
|
if (onData)
|
|
7568
7620
|
sub.callbacks.delete(onData);
|
|
7569
7621
|
if (onState)
|
|
7570
7622
|
sub.stateCallbacks.delete(onState);
|
|
7623
|
+
if (onError)
|
|
7624
|
+
sub.errorCallbacks.delete(onError);
|
|
7571
7625
|
// If no more callbacks, unsubscribe entirely
|
|
7572
|
-
if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0) {
|
|
7626
|
+
if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0 && sub.errorCallbacks.size === 0) {
|
|
7573
7627
|
this.subscriptions.delete(subKey);
|
|
7628
|
+
this.subscriptions.delete(currentSubKey);
|
|
7574
7629
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
7575
7630
|
this.ws.send(JSON.stringify({
|
|
7576
7631
|
type: 'unsubscribe',
|
|
@@ -7642,6 +7697,22 @@ class RealtimeStore {
|
|
|
7642
7697
|
this.rejectAllPending('Store closed');
|
|
7643
7698
|
this.subscriptions.clear();
|
|
7644
7699
|
}
|
|
7700
|
+
async reconnectWithNewAuth() {
|
|
7701
|
+
if (this.closed)
|
|
7702
|
+
return;
|
|
7703
|
+
await this.ensureInitialized();
|
|
7704
|
+
await this.refreshToken();
|
|
7705
|
+
await this.applyAuthPrincipalChange();
|
|
7706
|
+
if (this.subscriptions.size > 0) {
|
|
7707
|
+
await this.ensureConnected().catch((error) => {
|
|
7708
|
+
this.setAllSubscriptionStatus('error');
|
|
7709
|
+
for (const sub of this.subscriptions.values()) {
|
|
7710
|
+
sub.error = error instanceof Error ? error : new Error(String(error));
|
|
7711
|
+
this.notifyState(sub);
|
|
7712
|
+
}
|
|
7713
|
+
});
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7645
7716
|
}
|
|
7646
7717
|
// ---------------------------------------------------------------------------
|
|
7647
7718
|
// Singleton instance
|
|
@@ -7659,6 +7730,19 @@ function resetRealtimeStore() {
|
|
|
7659
7730
|
storeInstance = null;
|
|
7660
7731
|
}
|
|
7661
7732
|
}
|
|
7733
|
+
async function reconnectRealtimeStoreWithNewAuth() {
|
|
7734
|
+
if (storeInstance) {
|
|
7735
|
+
await storeInstance.reconnectWithNewAuth();
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
var realtimeStore = /*#__PURE__*/Object.freeze({
|
|
7740
|
+
__proto__: null,
|
|
7741
|
+
RealtimeStore: RealtimeStore,
|
|
7742
|
+
getRealtimeStore: getRealtimeStore,
|
|
7743
|
+
reconnectRealtimeStoreWithNewAuth: reconnectRealtimeStoreWithNewAuth,
|
|
7744
|
+
resetRealtimeStore: resetRealtimeStore
|
|
7745
|
+
});
|
|
7662
7746
|
|
|
7663
7747
|
// ---------------------------------------------------------------------------
|
|
7664
7748
|
// functions.ts -- Bounded Functions client (the imperative escape hatch).
|
|
@@ -7814,6 +7898,23 @@ function realtimeHttpBase(wsApiUrl) {
|
|
|
7814
7898
|
// Strip trailing slash from the resulting origin+path.
|
|
7815
7899
|
return url.toString().replace(/\/$/, '');
|
|
7816
7900
|
}
|
|
7901
|
+
function withoutAuthorization(headers) {
|
|
7902
|
+
if (!headers)
|
|
7903
|
+
return undefined;
|
|
7904
|
+
const clean = {};
|
|
7905
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
7906
|
+
if (key.toLowerCase() === 'authorization')
|
|
7907
|
+
continue;
|
|
7908
|
+
clean[key] = value;
|
|
7909
|
+
}
|
|
7910
|
+
return Object.keys(clean).length > 0 ? clean : undefined;
|
|
7911
|
+
}
|
|
7912
|
+
async function liveAuthHeader(configIsServer, overrides) {
|
|
7913
|
+
if (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders) {
|
|
7914
|
+
return overrides._getAuthHeaders();
|
|
7915
|
+
}
|
|
7916
|
+
return createAuthHeader(configIsServer);
|
|
7917
|
+
}
|
|
7817
7918
|
/**
|
|
7818
7919
|
* Send a player intent to a running live room. Returns `{ ok: true }`.
|
|
7819
7920
|
*
|
|
@@ -7828,7 +7929,7 @@ function realtimeHttpBase(wsApiUrl) {
|
|
|
7828
7929
|
* transport error / timeout.
|
|
7829
7930
|
*/
|
|
7830
7931
|
async function intent(roomPath, intent, opts = {}) {
|
|
7831
|
-
var _a, _b, _c, _d, _e;
|
|
7932
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
7832
7933
|
if (!roomPath || typeof roomPath !== 'string') {
|
|
7833
7934
|
throw new LiveIntentError('A room path is required');
|
|
7834
7935
|
}
|
|
@@ -7849,37 +7950,48 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7849
7950
|
// (e.g. the first join before subscribeView's WS connects) — HTTP also throws
|
|
7850
7951
|
// on a non-2xx, so errors are surfaced there too.
|
|
7851
7952
|
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7953
|
+
const hasAuthOverride = !!((_a = opts._overrides) === null || _a === void 0 ? void 0 : _a._getAuthHeaders);
|
|
7954
|
+
if (!hasAuthOverride) {
|
|
7955
|
+
if (opts.fireAndForget) {
|
|
7956
|
+
if (wsIntent(config.appId, normalizedRoomPath, intent))
|
|
7957
|
+
return { ok: true };
|
|
7958
|
+
}
|
|
7959
|
+
else {
|
|
7960
|
+
const ack = wsIntentReliable(config.appId, normalizedRoomPath, intent);
|
|
7961
|
+
if (ack)
|
|
7962
|
+
return await ack;
|
|
7963
|
+
}
|
|
7860
7964
|
}
|
|
7861
7965
|
const base = realtimeHttpBase(config.wsApiUrl);
|
|
7862
|
-
|
|
7863
|
-
const
|
|
7864
|
-
const
|
|
7966
|
+
const extraHeaders = withoutAuthorization(opts.headers);
|
|
7967
|
+
const overrideHeaders = withoutAuthorization((_b = opts._overrides) === null || _b === void 0 ? void 0 : _b.headers);
|
|
7968
|
+
const buildHeaders = async () => {
|
|
7969
|
+
const authHeader = await liveAuthHeader(config.isServer, opts._overrides);
|
|
7970
|
+
return Object.assign(Object.assign(Object.assign({ 'Content-Type': 'application/json', 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, (overrideHeaders !== null && overrideHeaders !== void 0 ? overrideHeaders : {})), (extraHeaders !== null && extraHeaders !== void 0 ? extraHeaders : {})), (authHeader !== null && authHeader !== void 0 ? authHeader : {}));
|
|
7971
|
+
};
|
|
7865
7972
|
const controller = new AbortController();
|
|
7866
|
-
const timeoutMs = (
|
|
7973
|
+
const timeoutMs = (_c = opts.timeoutMs) !== null && _c !== void 0 ? _c : 60000;
|
|
7867
7974
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7868
7975
|
let res;
|
|
7869
7976
|
try {
|
|
7870
|
-
|
|
7977
|
+
const send = async () => fetch(`${base}/live/intent`, {
|
|
7871
7978
|
method: 'POST',
|
|
7872
|
-
headers,
|
|
7979
|
+
headers: await buildHeaders(),
|
|
7873
7980
|
body: JSON.stringify({ path: normalizedRoomPath, intent }),
|
|
7874
7981
|
signal: controller.signal,
|
|
7875
7982
|
});
|
|
7983
|
+
res = await send();
|
|
7984
|
+
if (res.status === 401 && ((_d = opts._overrides) === null || _d === void 0 ? void 0 : _d._clearAuth)) {
|
|
7985
|
+
await opts._overrides._clearAuth();
|
|
7986
|
+
res = await send();
|
|
7987
|
+
}
|
|
7876
7988
|
}
|
|
7877
7989
|
catch (err) {
|
|
7878
7990
|
clearTimeout(timer);
|
|
7879
7991
|
if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
|
|
7880
7992
|
throw new LiveIntentError(`Live intent to "${roomPath}" timed out after ${timeoutMs}ms`);
|
|
7881
7993
|
}
|
|
7882
|
-
throw new LiveIntentError(`Failed to reach the realtime worker: ${(
|
|
7994
|
+
throw new LiveIntentError(`Failed to reach the realtime worker: ${(_e = err === null || err === void 0 ? void 0 : err.message) !== null && _e !== void 0 ? _e : String(err)}`);
|
|
7883
7995
|
}
|
|
7884
7996
|
clearTimeout(timer);
|
|
7885
7997
|
let body = null;
|
|
@@ -7888,12 +8000,12 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7888
8000
|
try {
|
|
7889
8001
|
body = JSON.parse(text);
|
|
7890
8002
|
}
|
|
7891
|
-
catch (
|
|
8003
|
+
catch (_h) {
|
|
7892
8004
|
body = { raw: text };
|
|
7893
8005
|
}
|
|
7894
8006
|
}
|
|
7895
8007
|
if (!res.ok) {
|
|
7896
|
-
const message = (
|
|
8008
|
+
const message = (_g = (_f = body === null || body === void 0 ? void 0 : body.error) !== null && _f !== void 0 ? _f : body === null || body === void 0 ? void 0 : body.message) !== null && _g !== void 0 ? _g : `Live intent failed with HTTP ${res.status}`;
|
|
7897
8009
|
throw new LiveIntentError(message, res.status, body);
|
|
7898
8010
|
}
|
|
7899
8011
|
return (body !== null && body !== void 0 ? body : { ok: true });
|