@bounded-sh/core 0.0.7 → 0.0.9
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/config.d.ts +10 -7
- package/dist/client/operations.d.ts +1 -1
- package/dist/client/realtime-store.d.ts +1 -0
- package/dist/index.js +212 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +212 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -20,10 +20,13 @@ let clientConfig = {
|
|
|
20
20
|
humanAuthApiUrl: 'https://auth.bounded.sh',
|
|
21
21
|
functionsUrl: 'https://functions.bounded.sh',
|
|
22
22
|
appId: '',
|
|
23
|
-
// 'email' = Bounded
|
|
24
|
-
// for
|
|
25
|
-
//
|
|
26
|
-
//
|
|
23
|
+
// 'email' = Bounded Auth human login (inline email OTP) — the out-of-box default
|
|
24
|
+
// for normal apps. Hosted OAuth/social uses loginWithRedirect/loginWithPopup.
|
|
25
|
+
// Text OTP is off by default and uses hosted/headless text helpers only when
|
|
26
|
+
// Bounded explicitly enables it for the issuer. For
|
|
27
|
+
// crypto/onchain wallet login use authMethod:'phantom' (Solana / Phantom), or
|
|
28
|
+
// signInAnonymously() for zero-friction 'guest' accounts. ('wallet' is an
|
|
29
|
+
// unimplemented stub; don't use.)
|
|
27
30
|
authMethod: 'email',
|
|
28
31
|
chain: '',
|
|
29
32
|
rpcUrl: '',
|
|
@@ -4223,7 +4226,7 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
4223
4226
|
}
|
|
4224
4227
|
}
|
|
4225
4228
|
|
|
4226
|
-
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
4229
|
+
var __rest$1 = (undefined && undefined.__rest) || function (s, e) {
|
|
4227
4230
|
var t = {};
|
|
4228
4231
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4229
4232
|
t[p] = s[p];
|
|
@@ -4698,7 +4701,7 @@ async function search(path, query, opts = {}) {
|
|
|
4698
4701
|
normalizedPath = normalizedPath.slice(0, -1);
|
|
4699
4702
|
}
|
|
4700
4703
|
if (!normalizedPath || normalizedPath.length === 0) {
|
|
4701
|
-
|
|
4704
|
+
throw new Error("Invalid path provided.");
|
|
4702
4705
|
}
|
|
4703
4706
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
4704
4707
|
throw new Error("search query must be a non-empty string");
|
|
@@ -4725,7 +4728,7 @@ async function get(path, opts = {}) {
|
|
|
4725
4728
|
normalizedPath = normalizedPath.slice(0, -1);
|
|
4726
4729
|
}
|
|
4727
4730
|
if (!normalizedPath || normalizedPath.length === 0) {
|
|
4728
|
-
|
|
4731
|
+
throw new Error("Invalid path provided.");
|
|
4729
4732
|
}
|
|
4730
4733
|
// Create cache key combining path, prompt, filter, sort, includeSubPaths,
|
|
4731
4734
|
// shape, limit, cursor — and (H1) the caller's appId + principal fingerprint,
|
|
@@ -4853,6 +4856,23 @@ function cleanupExpiredCache() {
|
|
|
4853
4856
|
});
|
|
4854
4857
|
lastCacheCleanup = now;
|
|
4855
4858
|
}
|
|
4859
|
+
function classifyGetManyBatchError(error) {
|
|
4860
|
+
var _a, _b, _c;
|
|
4861
|
+
const err = error;
|
|
4862
|
+
const status = (_b = (_a = err === null || err === void 0 ? void 0 : err.status) !== null && _a !== void 0 ? _a : err === null || err === void 0 ? void 0 : err.statusCode) !== null && _b !== void 0 ? _b : (_c = err === null || err === void 0 ? void 0 : err.response) === null || _c === void 0 ? void 0 : _c.status;
|
|
4863
|
+
const message = error instanceof Error
|
|
4864
|
+
? error.message
|
|
4865
|
+
: typeof (err === null || err === void 0 ? void 0 : err.message) === 'string'
|
|
4866
|
+
? err.message
|
|
4867
|
+
: 'Unknown error';
|
|
4868
|
+
if (status === 401 || status === 403) {
|
|
4869
|
+
return { code: 'UNAUTHORIZED', message };
|
|
4870
|
+
}
|
|
4871
|
+
if (status === 400) {
|
|
4872
|
+
return { code: 'INVALID_PATH', message };
|
|
4873
|
+
}
|
|
4874
|
+
return { code: 'REQUEST_FAILED', message };
|
|
4875
|
+
}
|
|
4856
4876
|
async function getMany(paths, opts = {}) {
|
|
4857
4877
|
var _a, _b, _c, _d, _e;
|
|
4858
4878
|
if (paths.length === 0) {
|
|
@@ -4897,6 +4917,11 @@ async function getMany(paths, opts = {}) {
|
|
|
4897
4917
|
if (uncachedPaths.length > 0) {
|
|
4898
4918
|
try {
|
|
4899
4919
|
const response = await makeApiRequest('POST', 'items/batch', { paths: uncachedPaths }, opts._overrides);
|
|
4920
|
+
if (response.status === 404 && response.data == null) {
|
|
4921
|
+
const endpointError = new Error('Batch read endpoint returned 404');
|
|
4922
|
+
endpointError.status = 404;
|
|
4923
|
+
throw endpointError;
|
|
4924
|
+
}
|
|
4900
4925
|
// makeApiRequest returns `{ data: <httpBody> }`, and the worker's items/batch
|
|
4901
4926
|
// httpBody is `{ data: { results: [...] }, status }` — so the results are
|
|
4902
4927
|
// double-nested at response.data.data.results. (Reading response.data.results
|
|
@@ -4939,11 +4964,12 @@ async function getMany(paths, opts = {}) {
|
|
|
4939
4964
|
}
|
|
4940
4965
|
}
|
|
4941
4966
|
catch (error) {
|
|
4967
|
+
const batchError = classifyGetManyBatchError(error);
|
|
4942
4968
|
for (const originalIndex of uncachedIndices) {
|
|
4943
4969
|
results[originalIndex] = {
|
|
4944
4970
|
path: normalizedPaths[originalIndex],
|
|
4945
4971
|
data: null,
|
|
4946
|
-
error:
|
|
4972
|
+
error: batchError,
|
|
4947
4973
|
};
|
|
4948
4974
|
}
|
|
4949
4975
|
}
|
|
@@ -5105,26 +5131,23 @@ async function setMany(many, options) {
|
|
|
5105
5131
|
return Object.assign(Object.assign({}, documents.map(d => d.document)), { transactionId: transactionResult.signature, signedTransaction: transactionResult.signedTransaction });
|
|
5106
5132
|
}
|
|
5107
5133
|
// Handle Solana on-chain transaction flow
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
}
|
|
5119
|
-
lastTxSignature = transactionResult.transactionSignature;
|
|
5120
|
-
signedTransaction = transactionResult.signedTransaction;
|
|
5134
|
+
if (!Array.isArray(transactions) || transactions.length !== 1) {
|
|
5135
|
+
throw new Error(`Expected exactly one on-chain transaction, received ${Array.isArray(transactions) ? transactions.length : 0}`);
|
|
5136
|
+
}
|
|
5137
|
+
const curTx = transactions[0];
|
|
5138
|
+
let transactionResult;
|
|
5139
|
+
if (curTx.serializedTransaction) {
|
|
5140
|
+
transactionResult = await handlePreBuiltTransaction(curTx, authProvider, options);
|
|
5141
|
+
}
|
|
5142
|
+
else {
|
|
5143
|
+
transactionResult = await handleSolanaTransaction(curTx, authProvider, options);
|
|
5121
5144
|
}
|
|
5122
5145
|
// Sync items after all transactions are confirmed
|
|
5123
5146
|
// Wait for 1.5 seconds to ensure all transactions are confirmed
|
|
5124
5147
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
5125
5148
|
await syncItems(many.map(m => m.path), options);
|
|
5126
5149
|
// TODO: Should we wait here or do the optimistic subscription updates like below?
|
|
5127
|
-
return Object.assign(Object.assign({}, documents.map(d => d.document)), { transactionId:
|
|
5150
|
+
return Object.assign(Object.assign({}, documents.map(d => d.document)), { transactionId: transactionResult.transactionSignature, signedTransaction: transactionResult.signedTransaction });
|
|
5128
5151
|
}
|
|
5129
5152
|
else if (setResponse.status === 200) {
|
|
5130
5153
|
// This means that the document was set successfully.
|
|
@@ -5137,7 +5160,7 @@ async function setMany(many, options) {
|
|
|
5137
5160
|
else if (setResponse.data &&
|
|
5138
5161
|
typeof setResponse.data === 'object' &&
|
|
5139
5162
|
setResponse.data.success === true) {
|
|
5140
|
-
const _k = setResponse.data, { success: _success } = _k, rest = __rest(_k, ["success"]);
|
|
5163
|
+
const _k = setResponse.data, { success: _success } = _k, rest = __rest$1(_k, ["success"]);
|
|
5141
5164
|
return Object.assign(Object.assign(Object.assign({}, documents.map(d => d.document)), rest), { transactionId: null });
|
|
5142
5165
|
}
|
|
5143
5166
|
else {
|
|
@@ -5373,7 +5396,7 @@ async function getFiles(path, options) {
|
|
|
5373
5396
|
try {
|
|
5374
5397
|
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
5375
5398
|
if (!normalizedPath || normalizedPath.length === 0) {
|
|
5376
|
-
|
|
5399
|
+
throw new Error("Invalid path provided.");
|
|
5377
5400
|
}
|
|
5378
5401
|
const apiPath = `storage?path=${normalizedPath}`;
|
|
5379
5402
|
const response = await makeApiRequest('GET', apiPath, null, options === null || options === void 0 ? void 0 : options._overrides);
|
|
@@ -5793,6 +5816,12 @@ function roomKeyFromRoutePath(routePath) {
|
|
|
5793
5816
|
return null;
|
|
5794
5817
|
return `${segs[0]}/${segs[1]}`;
|
|
5795
5818
|
}
|
|
5819
|
+
function replaySubscriptions(connection) {
|
|
5820
|
+
for (const sub of connection.subscriptions.values()) {
|
|
5821
|
+
sub.lastData = undefined;
|
|
5822
|
+
sendSubscribe(connection, sub);
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5796
5825
|
async function getOrCreateConnection(appId, isServer, routePath, authTokenProvider, principalKey) {
|
|
5797
5826
|
attachBrowserReconnectHooksOnce();
|
|
5798
5827
|
// A per-room subscription gets its OWN connection routed to the room DO; all
|
|
@@ -5818,10 +5847,12 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5818
5847
|
pendingRequests: new Map(),
|
|
5819
5848
|
isConnecting: false,
|
|
5820
5849
|
isConnected: false,
|
|
5850
|
+
isAuthenticating: false,
|
|
5821
5851
|
appId,
|
|
5822
5852
|
key: connKey,
|
|
5823
5853
|
routePath: roomKey ? routePath : undefined,
|
|
5824
5854
|
authTokenProvider,
|
|
5855
|
+
pendingAuthToken: null,
|
|
5825
5856
|
tokenRefreshTimer: null,
|
|
5826
5857
|
consecutiveAuthFailures: 0,
|
|
5827
5858
|
};
|
|
@@ -5846,16 +5877,17 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5846
5877
|
// Per-room connection: carry the room path so the worker routes this WS
|
|
5847
5878
|
// to the room DO (appId#room#roomId) where the live view fan-out lives.
|
|
5848
5879
|
if (connection.routePath) {
|
|
5849
|
-
wsUrl.searchParams.append('
|
|
5880
|
+
wsUrl.searchParams.append('routePath', connection.routePath);
|
|
5850
5881
|
}
|
|
5851
|
-
//
|
|
5852
|
-
// token from the wallet's own session (self-refreshing); all others
|
|
5853
|
-
// the ambient env/web session.
|
|
5882
|
+
// Resolve auth token if available. A wallet-scoped connection resolves
|
|
5883
|
+
// its token from the wallet's own session (self-refreshing); all others
|
|
5884
|
+
// use the ambient env/web session. The token is sent as the first WS
|
|
5885
|
+
// frame after open, never as a URL query parameter.
|
|
5854
5886
|
const authToken = connection.authTokenProvider
|
|
5855
5887
|
? await connection.authTokenProvider().catch(() => null)
|
|
5856
5888
|
: await getFreshAuthToken(isServer);
|
|
5889
|
+
connection.pendingAuthToken = authToken || null;
|
|
5857
5890
|
if (authToken) {
|
|
5858
|
-
wsUrl.searchParams.append('authorization', authToken);
|
|
5859
5891
|
// Successful token acquisition — reset failure counter
|
|
5860
5892
|
connection.consecutiveAuthFailures = 0;
|
|
5861
5893
|
}
|
|
@@ -5881,6 +5913,7 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5881
5913
|
connection.ws = ws;
|
|
5882
5914
|
// Handle connection open
|
|
5883
5915
|
ws.addEventListener('open', () => {
|
|
5916
|
+
var _a, _b;
|
|
5884
5917
|
connection.isConnecting = false;
|
|
5885
5918
|
connection.isConnected = true;
|
|
5886
5919
|
// NOTE: Do NOT reset consecutiveAuthFailures here. It is reset when a
|
|
@@ -5895,10 +5928,26 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5895
5928
|
// urlProvider skips straight to unauthenticated connection.
|
|
5896
5929
|
// Schedule periodic token freshness checks
|
|
5897
5930
|
scheduleTokenRefresh(connection, isServer);
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5931
|
+
if (connection.pendingAuthToken) {
|
|
5932
|
+
connection.isAuthenticating = true;
|
|
5933
|
+
try {
|
|
5934
|
+
(_a = connection.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 'auth', token: connection.pendingAuthToken }));
|
|
5935
|
+
}
|
|
5936
|
+
catch (error) {
|
|
5937
|
+
connection.isAuthenticating = false;
|
|
5938
|
+
connection.isConnected = false;
|
|
5939
|
+
console.error('[WS v2] Error sending auth message:', error);
|
|
5940
|
+
try {
|
|
5941
|
+
(_b = connection.ws) === null || _b === void 0 ? void 0 : _b.close(1008, 'Authentication send failed');
|
|
5942
|
+
}
|
|
5943
|
+
catch (_c) {
|
|
5944
|
+
// Already closed.
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
else {
|
|
5949
|
+
connection.isAuthenticating = false;
|
|
5950
|
+
replaySubscriptions(connection);
|
|
5902
5951
|
}
|
|
5903
5952
|
});
|
|
5904
5953
|
// Handle incoming messages
|
|
@@ -5922,6 +5971,7 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5922
5971
|
// Handle close
|
|
5923
5972
|
ws.addEventListener('close', () => {
|
|
5924
5973
|
connection.isConnected = false;
|
|
5974
|
+
connection.isAuthenticating = false;
|
|
5925
5975
|
if (connection.tokenRefreshTimer) {
|
|
5926
5976
|
clearInterval(connection.tokenRefreshTimer);
|
|
5927
5977
|
connection.tokenRefreshTimer = null;
|
|
@@ -5938,6 +5988,11 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5938
5988
|
function handleServerMessage(connection, message) {
|
|
5939
5989
|
var _a, _b;
|
|
5940
5990
|
switch (message.type) {
|
|
5991
|
+
case 'authenticated': {
|
|
5992
|
+
connection.isAuthenticating = false;
|
|
5993
|
+
replaySubscriptions(connection);
|
|
5994
|
+
break;
|
|
5995
|
+
}
|
|
5941
5996
|
case 'subscribed': {
|
|
5942
5997
|
const subscription = connection.subscriptions.get(message.subscriptionId);
|
|
5943
5998
|
if (subscription) {
|
|
@@ -6238,7 +6293,7 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
|
|
|
6238
6293
|
};
|
|
6239
6294
|
connection.subscriptions.set(subscriptionId, subscription);
|
|
6240
6295
|
// Send subscribe message if connected
|
|
6241
|
-
if (connection.isConnected) {
|
|
6296
|
+
if (connection.isConnected && !connection.isAuthenticating) {
|
|
6242
6297
|
// Create a promise to wait for subscription confirmation
|
|
6243
6298
|
const subscriptionPromise = new Promise((resolve, reject) => {
|
|
6244
6299
|
connection.pendingSubscriptions.set(subscriptionId, { resolve, reject });
|
|
@@ -6276,7 +6331,7 @@ async function removeCallbackFromSubscription(connection, subscriptionId, callba
|
|
|
6276
6331
|
}
|
|
6277
6332
|
// No more callbacks, unsubscribe from server
|
|
6278
6333
|
connection.subscriptions.delete(subscriptionId);
|
|
6279
|
-
if (connection.isConnected) {
|
|
6334
|
+
if (connection.isConnected && !connection.isAuthenticating) {
|
|
6280
6335
|
// Create a promise to wait for unsubscription confirmation
|
|
6281
6336
|
const unsubscribePromise = new Promise((resolve, reject) => {
|
|
6282
6337
|
connection.pendingUnsubscriptions.set(subscriptionId, { resolve, reject });
|
|
@@ -6452,12 +6507,59 @@ function generateRequestId() {
|
|
|
6452
6507
|
*/
|
|
6453
6508
|
function hasActiveConnection() {
|
|
6454
6509
|
for (const connection of connections.values()) {
|
|
6455
|
-
if (connection.ws && connection.isConnected) {
|
|
6510
|
+
if (connection.ws && connection.isConnected && !connection.isAuthenticating) {
|
|
6456
6511
|
return true;
|
|
6457
6512
|
}
|
|
6458
6513
|
}
|
|
6459
6514
|
return false;
|
|
6460
6515
|
}
|
|
6516
|
+
async function waitForConnectionAuthenticated(connection) {
|
|
6517
|
+
if (!connection.isAuthenticating || !connection.ws)
|
|
6518
|
+
return;
|
|
6519
|
+
const ws = connection.ws;
|
|
6520
|
+
await new Promise((resolve, reject) => {
|
|
6521
|
+
let timeout;
|
|
6522
|
+
let cleanup = () => { };
|
|
6523
|
+
const onMessage = (event) => {
|
|
6524
|
+
try {
|
|
6525
|
+
const message = JSON.parse(event.data);
|
|
6526
|
+
if ((message === null || message === void 0 ? void 0 : message.type) === 'authenticated') {
|
|
6527
|
+
cleanup();
|
|
6528
|
+
resolve();
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
catch (_a) {
|
|
6532
|
+
// Other frames are handled by the main listener.
|
|
6533
|
+
}
|
|
6534
|
+
};
|
|
6535
|
+
const onClose = () => {
|
|
6536
|
+
cleanup();
|
|
6537
|
+
reject(new Error('WebSocket disconnected during authentication'));
|
|
6538
|
+
};
|
|
6539
|
+
const onError = () => {
|
|
6540
|
+
cleanup();
|
|
6541
|
+
reject(new Error('WebSocket authentication failed'));
|
|
6542
|
+
};
|
|
6543
|
+
cleanup = () => {
|
|
6544
|
+
clearTimeout(timeout);
|
|
6545
|
+
ws.removeEventListener('message', onMessage);
|
|
6546
|
+
ws.removeEventListener('close', onClose);
|
|
6547
|
+
ws.removeEventListener('error', onError);
|
|
6548
|
+
};
|
|
6549
|
+
timeout = setTimeout(() => {
|
|
6550
|
+
cleanup();
|
|
6551
|
+
reject(new Error('WebSocket authentication timeout'));
|
|
6552
|
+
}, 10000);
|
|
6553
|
+
if (!connection.isAuthenticating) {
|
|
6554
|
+
cleanup();
|
|
6555
|
+
resolve();
|
|
6556
|
+
return;
|
|
6557
|
+
}
|
|
6558
|
+
ws.addEventListener('message', onMessage);
|
|
6559
|
+
ws.addEventListener('close', onClose);
|
|
6560
|
+
ws.addEventListener('error', onError);
|
|
6561
|
+
});
|
|
6562
|
+
}
|
|
6461
6563
|
async function sendRequest(msgBuilder) {
|
|
6462
6564
|
const config = await getConfig();
|
|
6463
6565
|
const appId = config.appId;
|
|
@@ -6483,6 +6585,7 @@ async function sendRequest(msgBuilder) {
|
|
|
6483
6585
|
if (!connection.ws || !connection.isConnected) {
|
|
6484
6586
|
throw new Error('WebSocket connection not available');
|
|
6485
6587
|
}
|
|
6588
|
+
await waitForConnectionAuthenticated(connection);
|
|
6486
6589
|
const requestId = generateRequestId();
|
|
6487
6590
|
const message = msgBuilder(requestId);
|
|
6488
6591
|
return new Promise((resolve, reject) => {
|
|
@@ -6519,7 +6622,7 @@ function wsIntent(appId, roomRoutePath, intent) {
|
|
|
6519
6622
|
const roomKey = roomKeyFromRoutePath(roomRoutePath);
|
|
6520
6623
|
const connKey = roomKey ? `${appId}#room#${roomKey}` : appId;
|
|
6521
6624
|
const connection = connections.get(connKey);
|
|
6522
|
-
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN) {
|
|
6625
|
+
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
|
|
6523
6626
|
return false;
|
|
6524
6627
|
}
|
|
6525
6628
|
try {
|
|
@@ -6542,7 +6645,7 @@ function wsIntentReliable(appId, roomRoutePath, intent) {
|
|
|
6542
6645
|
const roomKey = roomKeyFromRoutePath(roomRoutePath);
|
|
6543
6646
|
const connKey = roomKey ? `${appId}#room#${roomKey}` : appId;
|
|
6544
6647
|
const connection = connections.get(connKey);
|
|
6545
|
-
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN) {
|
|
6648
|
+
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
|
|
6546
6649
|
return undefined;
|
|
6547
6650
|
}
|
|
6548
6651
|
const requestId = generateRequestId();
|
|
@@ -6869,6 +6972,7 @@ class RealtimeStore {
|
|
|
6869
6972
|
this.idbDirtyKeys = new Set();
|
|
6870
6973
|
this.closed = false;
|
|
6871
6974
|
this.authToken = null;
|
|
6975
|
+
this.authenticating = false;
|
|
6872
6976
|
this.isServer = false;
|
|
6873
6977
|
this.tokenRefreshTimer = null;
|
|
6874
6978
|
// -----------------------------------------------------------------------
|
|
@@ -6916,7 +7020,7 @@ class RealtimeStore {
|
|
|
6916
7020
|
this.initPromise = this.init();
|
|
6917
7021
|
await this.initPromise;
|
|
6918
7022
|
}
|
|
6919
|
-
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN)
|
|
7023
|
+
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)
|
|
6920
7024
|
return;
|
|
6921
7025
|
if (this.connectPromise)
|
|
6922
7026
|
return this.connectPromise;
|
|
@@ -6931,21 +7035,52 @@ class RealtimeStore {
|
|
|
6931
7035
|
}
|
|
6932
7036
|
const params = new URLSearchParams();
|
|
6933
7037
|
params.set('apiKey', this.appId);
|
|
6934
|
-
if (this.authToken)
|
|
6935
|
-
params.set('authorization', this.authToken);
|
|
6936
|
-
// Note: token in URL is required until DO server supports subprotocol auth.
|
|
6937
|
-
// WSS encrypts the full URL including query params on the wire.
|
|
6938
7038
|
const url = `${this.wsUrl}?${params.toString()}`;
|
|
6939
7039
|
const ws = new WebSocket(url);
|
|
6940
7040
|
this.ws = ws;
|
|
6941
|
-
|
|
7041
|
+
let authTimer = null;
|
|
7042
|
+
const finishConnected = () => {
|
|
7043
|
+
if (authTimer) {
|
|
7044
|
+
clearTimeout(authTimer);
|
|
7045
|
+
authTimer = null;
|
|
7046
|
+
}
|
|
7047
|
+
this.authenticating = false;
|
|
6942
7048
|
ws.removeEventListener('error', onError);
|
|
6943
7049
|
this.reconnectDelay = 1000;
|
|
6944
7050
|
this.connectPromise = null;
|
|
6945
7051
|
this.resubscribeAll();
|
|
6946
7052
|
resolve();
|
|
6947
7053
|
};
|
|
7054
|
+
const onOpen = () => {
|
|
7055
|
+
if (!this.authToken) {
|
|
7056
|
+
finishConnected();
|
|
7057
|
+
return;
|
|
7058
|
+
}
|
|
7059
|
+
this.authenticating = true;
|
|
7060
|
+
authTimer = setTimeout(() => {
|
|
7061
|
+
this.authenticating = false;
|
|
7062
|
+
this.connectPromise = null;
|
|
7063
|
+
try {
|
|
7064
|
+
ws.close(1008, 'Authentication timeout');
|
|
7065
|
+
}
|
|
7066
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
7067
|
+
reject(new Error('WebSocket authentication timeout'));
|
|
7068
|
+
}, 10000);
|
|
7069
|
+
try {
|
|
7070
|
+
ws.send(JSON.stringify({ type: 'auth', token: this.authToken }));
|
|
7071
|
+
}
|
|
7072
|
+
catch (e) {
|
|
7073
|
+
if (authTimer)
|
|
7074
|
+
clearTimeout(authTimer);
|
|
7075
|
+
this.authenticating = false;
|
|
7076
|
+
this.connectPromise = null;
|
|
7077
|
+
reject(e);
|
|
7078
|
+
}
|
|
7079
|
+
};
|
|
6948
7080
|
const onError = (e) => {
|
|
7081
|
+
if (authTimer)
|
|
7082
|
+
clearTimeout(authTimer);
|
|
7083
|
+
this.authenticating = false;
|
|
6949
7084
|
ws.removeEventListener('open', onOpen);
|
|
6950
7085
|
this.connectPromise = null;
|
|
6951
7086
|
reject(new Error('WebSocket connection failed'));
|
|
@@ -6953,9 +7088,22 @@ class RealtimeStore {
|
|
|
6953
7088
|
ws.addEventListener('open', onOpen, { once: true });
|
|
6954
7089
|
ws.addEventListener('error', onError, { once: true });
|
|
6955
7090
|
ws.addEventListener('message', (event) => {
|
|
7091
|
+
if (this.authenticating) {
|
|
7092
|
+
try {
|
|
7093
|
+
const msg = JSON.parse(typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data));
|
|
7094
|
+
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'authenticated') {
|
|
7095
|
+
finishConnected();
|
|
7096
|
+
return;
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
catch ( /* fall through to normal handling */_a) { /* fall through to normal handling */ }
|
|
7100
|
+
}
|
|
6956
7101
|
this.handleMessage(event.data);
|
|
6957
7102
|
});
|
|
6958
7103
|
ws.addEventListener('close', () => {
|
|
7104
|
+
if (authTimer)
|
|
7105
|
+
clearTimeout(authTimer);
|
|
7106
|
+
this.authenticating = false;
|
|
6959
7107
|
this.ws = null;
|
|
6960
7108
|
this.connectPromise = null;
|
|
6961
7109
|
this.rejectAllPending('WebSocket closed');
|
|
@@ -7008,6 +7156,8 @@ class RealtimeStore {
|
|
|
7008
7156
|
break;
|
|
7009
7157
|
case 'pong':
|
|
7010
7158
|
break;
|
|
7159
|
+
case 'authenticated':
|
|
7160
|
+
break;
|
|
7011
7161
|
// v1 compat: handle legacy message types during transition
|
|
7012
7162
|
case 'subscribed':
|
|
7013
7163
|
this.handleSnapshot(Object.assign(Object.assign({}, msg), { type: 'snapshot', docs: msg.data }));
|
|
@@ -7609,6 +7759,17 @@ function resetRealtimeStore() {
|
|
|
7609
7759
|
// rule, and dispatches to the function — returning its JSON, or throwing on
|
|
7610
7760
|
// 403 / error.
|
|
7611
7761
|
// ---------------------------------------------------------------------------
|
|
7762
|
+
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
7763
|
+
var t = {};
|
|
7764
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
7765
|
+
t[p] = s[p];
|
|
7766
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7767
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7768
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
7769
|
+
t[p[i]] = s[p[i]];
|
|
7770
|
+
}
|
|
7771
|
+
return t;
|
|
7772
|
+
};
|
|
7612
7773
|
/** Prod functions dispatcher; overridable via init({ functionsUrl }) or network preset. */
|
|
7613
7774
|
const DEFAULT_FUNCTIONS_URL = 'https://functions.bounded.sh';
|
|
7614
7775
|
class FunctionInvokeError extends Error {
|
|
@@ -7619,6 +7780,12 @@ class FunctionInvokeError extends Error {
|
|
|
7619
7780
|
this.name = 'FunctionInvokeError';
|
|
7620
7781
|
}
|
|
7621
7782
|
}
|
|
7783
|
+
function stripAuthHeaders(headers) {
|
|
7784
|
+
if (!headers)
|
|
7785
|
+
return undefined;
|
|
7786
|
+
const { Authorization, authorization } = headers, rest = __rest(headers, ["Authorization", "authorization"]);
|
|
7787
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
7788
|
+
}
|
|
7622
7789
|
/**
|
|
7623
7790
|
* Invoke a deployed Bounded Function by name. Returns the function's JSON.
|
|
7624
7791
|
*
|
|
@@ -7641,7 +7808,7 @@ async function invoke(name, args = {}, opts = {}) {
|
|
|
7641
7808
|
const authHeader = ((_a = opts._overrides) === null || _a === void 0 ? void 0 : _a._getAuthHeaders)
|
|
7642
7809
|
? await opts._overrides._getAuthHeaders()
|
|
7643
7810
|
: await createAuthHeader(config.isServer);
|
|
7644
|
-
const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json', 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, (
|
|
7811
|
+
const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json', 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, ((_b = stripAuthHeaders(opts.headers)) !== null && _b !== void 0 ? _b : {})), (authHeader !== null && authHeader !== void 0 ? authHeader : {}));
|
|
7645
7812
|
const controller = new AbortController();
|
|
7646
7813
|
const timeoutMs = (_c = opts.timeoutMs) !== null && _c !== void 0 ? _c : 60000;
|
|
7647
7814
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|