@bounded-sh/core 0.0.1 → 0.0.3
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/README.md +2 -2
- package/dist/client/config.d.ts +1 -1
- package/dist/client/live.d.ts +44 -4
- package/dist/client/operations.d.ts +8 -1
- package/dist/client/subscription-v2.d.ts +1 -1
- package/dist/client/subscription.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +126 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +126 -70
- package/dist/index.mjs.map +1 -1
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/core-platform.d.ts +1 -1
- package/dist/utils/rn-session-manager.d.ts +1 -1
- package/dist/utils/web-session-manager.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -75,7 +75,7 @@ function getWebhookKeysUrl() {
|
|
|
75
75
|
/**
|
|
76
76
|
* True when init() configured a Bounded network (the Cloudflare-native stack).
|
|
77
77
|
* Synchronous + non-blocking. Used to route reads/writes that behave differently
|
|
78
|
-
* on Bounded vs the legacy
|
|
78
|
+
* on Bounded vs the legacy backend (e.g. `count`/`aggregate`, which on
|
|
79
79
|
* Bounded must use the deterministic server aggregation, not the legacy AI query).
|
|
80
80
|
*/
|
|
81
81
|
function isBoundedNetwork() {
|
|
@@ -2930,7 +2930,7 @@ class WebSessionManager {
|
|
|
2930
2930
|
// the RIGHT /session/refresh. Email overrides with humanAuthApiUrl; all
|
|
2931
2931
|
// wallet/guest/signature flows default to authApiUrl (the bounded-auth issuer).
|
|
2932
2932
|
const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
|
|
2933
|
-
localStorage.setItem(this.
|
|
2933
|
+
localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
|
|
2934
2934
|
accessToken,
|
|
2935
2935
|
idToken,
|
|
2936
2936
|
refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
|
|
@@ -2941,7 +2941,7 @@ class WebSessionManager {
|
|
|
2941
2941
|
static async getSession() {
|
|
2942
2942
|
if (typeof window === "undefined")
|
|
2943
2943
|
return null;
|
|
2944
|
-
const session = localStorage.getItem(this.
|
|
2944
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
2945
2945
|
if (!session)
|
|
2946
2946
|
return null;
|
|
2947
2947
|
const sessionObj = JSON.parse(session);
|
|
@@ -2965,7 +2965,7 @@ class WebSessionManager {
|
|
|
2965
2965
|
// Persist the ROTATED refresh token — the old one is now
|
|
2966
2966
|
// single-use spent; reusing it would revoke the family.
|
|
2967
2967
|
refreshed.refreshToken);
|
|
2968
|
-
const newSession = localStorage.getItem(this.
|
|
2968
|
+
const newSession = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
2969
2969
|
if (!newSession)
|
|
2970
2970
|
return null;
|
|
2971
2971
|
const newObj = JSON.parse(newSession);
|
|
@@ -2989,14 +2989,14 @@ class WebSessionManager {
|
|
|
2989
2989
|
static clearSession() {
|
|
2990
2990
|
if (typeof window === "undefined")
|
|
2991
2991
|
return;
|
|
2992
|
-
localStorage.removeItem(this.
|
|
2992
|
+
localStorage.removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
2993
2993
|
}
|
|
2994
2994
|
/* ------------------------------------------------------------------ */
|
|
2995
2995
|
/* IS-AUTH */
|
|
2996
2996
|
/* ------------------------------------------------------------------ */
|
|
2997
2997
|
static isAuthenticated() {
|
|
2998
2998
|
// NOTE: this remains synchronous to stay backward compatible
|
|
2999
|
-
return !!localStorage.getItem(this.
|
|
2999
|
+
return !!localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3000
3000
|
}
|
|
3001
3001
|
/* ------------------------------------------------------------------ */
|
|
3002
3002
|
/* TOKEN HELPERS */
|
|
@@ -3004,13 +3004,13 @@ class WebSessionManager {
|
|
|
3004
3004
|
static getIdToken() {
|
|
3005
3005
|
if (typeof window === "undefined")
|
|
3006
3006
|
return null;
|
|
3007
|
-
const session = localStorage.getItem(this.
|
|
3007
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3008
3008
|
return session ? JSON.parse(session).idToken : null;
|
|
3009
3009
|
}
|
|
3010
3010
|
static getRefreshToken() {
|
|
3011
3011
|
if (typeof window === "undefined")
|
|
3012
3012
|
return null;
|
|
3013
|
-
const session = localStorage.getItem(this.
|
|
3013
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3014
3014
|
return session ? JSON.parse(session).refreshToken : null;
|
|
3015
3015
|
}
|
|
3016
3016
|
/** The issuer base that minted this session (for routing silent refresh). */
|
|
@@ -3018,7 +3018,7 @@ class WebSessionManager {
|
|
|
3018
3018
|
var _a;
|
|
3019
3019
|
if (typeof window === "undefined")
|
|
3020
3020
|
return null;
|
|
3021
|
-
const session = localStorage.getItem(this.
|
|
3021
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3022
3022
|
return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
|
|
3023
3023
|
}
|
|
3024
3024
|
/* ------------------------------------------------------------------ */
|
|
@@ -3028,7 +3028,7 @@ class WebSessionManager {
|
|
|
3028
3028
|
var _a;
|
|
3029
3029
|
if (typeof window === "undefined")
|
|
3030
3030
|
return;
|
|
3031
|
-
const session = localStorage.getItem(this.
|
|
3031
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3032
3032
|
if (!session)
|
|
3033
3033
|
return;
|
|
3034
3034
|
const sessionObj = JSON.parse(session);
|
|
@@ -3045,10 +3045,10 @@ class WebSessionManager {
|
|
|
3045
3045
|
sessionObj.refreshToken = refreshToken;
|
|
3046
3046
|
}
|
|
3047
3047
|
(_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
|
|
3048
|
-
localStorage.setItem(this.
|
|
3048
|
+
localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
|
|
3049
3049
|
}
|
|
3050
3050
|
}
|
|
3051
|
-
WebSessionManager.
|
|
3051
|
+
WebSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
|
|
3052
3052
|
|
|
3053
3053
|
let _config = null;
|
|
3054
3054
|
class ReactNativeSessionManager {
|
|
@@ -3141,7 +3141,7 @@ class ReactNativeSessionManager {
|
|
|
3141
3141
|
// Issuer base for silent refresh (email overrides w/ humanAuthApiUrl; wallet/
|
|
3142
3142
|
// guest default to authApiUrl). See WebSessionManager for the rationale.
|
|
3143
3143
|
const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
|
|
3144
|
-
this.getStorage().setItem(this.
|
|
3144
|
+
this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
|
|
3145
3145
|
accessToken,
|
|
3146
3146
|
idToken,
|
|
3147
3147
|
refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
|
|
@@ -3150,7 +3150,7 @@ class ReactNativeSessionManager {
|
|
|
3150
3150
|
/* GET */
|
|
3151
3151
|
/* ------------------------------------------------------------------ */
|
|
3152
3152
|
static async getSession() {
|
|
3153
|
-
const session = this.getStorage().getItem(this.
|
|
3153
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3154
3154
|
if (!session)
|
|
3155
3155
|
return null;
|
|
3156
3156
|
const sessionObj = JSON.parse(session);
|
|
@@ -3171,7 +3171,7 @@ class ReactNativeSessionManager {
|
|
|
3171
3171
|
const refreshed = await refreshSession(refreshToken, sessionObj.issuer);
|
|
3172
3172
|
if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
|
|
3173
3173
|
await this.updateIdTokenAndAccessToken(refreshed.idToken, refreshed.accessToken, refreshed.refreshToken);
|
|
3174
|
-
const newSession = this.getStorage().getItem(this.
|
|
3174
|
+
const newSession = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3175
3175
|
if (!newSession)
|
|
3176
3176
|
return null;
|
|
3177
3177
|
const newObj = JSON.parse(newSession);
|
|
@@ -3193,20 +3193,20 @@ class ReactNativeSessionManager {
|
|
|
3193
3193
|
/* CLEAR */
|
|
3194
3194
|
/* ------------------------------------------------------------------ */
|
|
3195
3195
|
static clearSession() {
|
|
3196
|
-
this.getStorage().removeItem(this.
|
|
3196
|
+
this.getStorage().removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3197
3197
|
}
|
|
3198
3198
|
/* ------------------------------------------------------------------ */
|
|
3199
3199
|
/* IS-AUTH */
|
|
3200
3200
|
/* ------------------------------------------------------------------ */
|
|
3201
3201
|
static isAuthenticated() {
|
|
3202
|
-
return !!this.getStorage().getItem(this.
|
|
3202
|
+
return !!this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3203
3203
|
}
|
|
3204
3204
|
/* ------------------------------------------------------------------ */
|
|
3205
3205
|
/* TOKEN HELPERS */
|
|
3206
3206
|
/* ------------------------------------------------------------------ */
|
|
3207
3207
|
static getIdToken() {
|
|
3208
3208
|
try {
|
|
3209
|
-
const session = this.getStorage().getItem(this.
|
|
3209
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3210
3210
|
return session ? JSON.parse(session).idToken : null;
|
|
3211
3211
|
}
|
|
3212
3212
|
catch (_a) {
|
|
@@ -3217,7 +3217,7 @@ class ReactNativeSessionManager {
|
|
|
3217
3217
|
static getIssuer() {
|
|
3218
3218
|
var _a;
|
|
3219
3219
|
try {
|
|
3220
|
-
const session = this.getStorage().getItem(this.
|
|
3220
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3221
3221
|
return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
|
|
3222
3222
|
}
|
|
3223
3223
|
catch (_b) {
|
|
@@ -3226,7 +3226,7 @@ class ReactNativeSessionManager {
|
|
|
3226
3226
|
}
|
|
3227
3227
|
static getRefreshToken() {
|
|
3228
3228
|
try {
|
|
3229
|
-
const session = this.getStorage().getItem(this.
|
|
3229
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3230
3230
|
return session ? JSON.parse(session).refreshToken : null;
|
|
3231
3231
|
}
|
|
3232
3232
|
catch (_a) {
|
|
@@ -3238,7 +3238,7 @@ class ReactNativeSessionManager {
|
|
|
3238
3238
|
/* ------------------------------------------------------------------ */
|
|
3239
3239
|
static async updateIdTokenAndAccessToken(idToken, accessToken, refreshToken) {
|
|
3240
3240
|
var _a;
|
|
3241
|
-
const session = this.getStorage().getItem(this.
|
|
3241
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3242
3242
|
if (!session)
|
|
3243
3243
|
return;
|
|
3244
3244
|
const sessionObj = JSON.parse(session);
|
|
@@ -3254,10 +3254,10 @@ class ReactNativeSessionManager {
|
|
|
3254
3254
|
sessionObj.refreshToken = refreshToken;
|
|
3255
3255
|
}
|
|
3256
3256
|
(_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
|
|
3257
|
-
this.getStorage().setItem(this.
|
|
3257
|
+
this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
|
|
3258
3258
|
}
|
|
3259
3259
|
}
|
|
3260
|
-
ReactNativeSessionManager.
|
|
3260
|
+
ReactNativeSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
|
|
3261
3261
|
|
|
3262
3262
|
/**
|
|
3263
3263
|
* Return the active CLIENT session manager for the current runtime.
|
|
@@ -3778,9 +3778,9 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
|
|
|
3778
3778
|
/* ENV helpers */
|
|
3779
3779
|
/* ------------------------------------------------------------------ */
|
|
3780
3780
|
// Canonical `BOUNDED_PRIVATE_KEY` (matches the CLI); legacy
|
|
3781
|
-
// `
|
|
3781
|
+
// `BOUNDED_SOLANA_KEYPAIR` still honored. Only consulted when no explicit
|
|
3782
3782
|
// keypair was provided (createWalletClient passes one).
|
|
3783
|
-
const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "
|
|
3783
|
+
const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "BOUNDED_SOLANA_KEYPAIR"];
|
|
3784
3784
|
function loadKeypairFromEnv() {
|
|
3785
3785
|
let secret;
|
|
3786
3786
|
let found;
|
|
@@ -3903,7 +3903,7 @@ var serverSessionManager = /*#__PURE__*/Object.freeze({
|
|
|
3903
3903
|
});
|
|
3904
3904
|
|
|
3905
3905
|
/**
|
|
3906
|
-
* Safe base64 helpers for
|
|
3906
|
+
* Safe base64 helpers for bounded-core.
|
|
3907
3907
|
*
|
|
3908
3908
|
* Uses the global atob/btoa when available (browser, RN with polyfill),
|
|
3909
3909
|
* falls back to the 'buffer' package (Node.js / SSR / RN without polyfill).
|
|
@@ -4083,7 +4083,7 @@ axiosRetry(apiClient, {
|
|
|
4083
4083
|
shouldResetTimeout: true,
|
|
4084
4084
|
onRetry: (retryCount, error, requestConfig) => {
|
|
4085
4085
|
var _a;
|
|
4086
|
-
console.warn(`[
|
|
4086
|
+
console.warn(`[bounded-sdk] retry ${retryCount} for ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url} — ${error.code || error.message}`);
|
|
4087
4087
|
},
|
|
4088
4088
|
});
|
|
4089
4089
|
const refreshInFlight = new Map();
|
|
@@ -4142,8 +4142,8 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
4142
4142
|
: await createAuthHeader(config.isServer);
|
|
4143
4143
|
const headers = Object.assign({ "Content-Type": "application/json", "X-Public-App-Id": config.appId, "X-App-Id": config.appId }, authHeader);
|
|
4144
4144
|
if (typeof window !== "undefined" &&
|
|
4145
|
-
window.
|
|
4146
|
-
const customAppId = window.
|
|
4145
|
+
window.CUSTOM_BOUNDED_APP_ID_HEADER) {
|
|
4146
|
+
const customAppId = window.CUSTOM_BOUNDED_APP_ID_HEADER;
|
|
4147
4147
|
if (customAppId) {
|
|
4148
4148
|
headers["X-App-Id"] = customAppId;
|
|
4149
4149
|
}
|
|
@@ -4402,7 +4402,7 @@ function parseAggregateValue(result) {
|
|
|
4402
4402
|
* Count items in a collection path. Returns a numeric result.
|
|
4403
4403
|
*
|
|
4404
4404
|
* This uses the AI query engine with a count-specific prompt prefix,
|
|
4405
|
-
* so
|
|
4405
|
+
* so Bounded will generate a $count aggregation pipeline and return
|
|
4406
4406
|
* just the count rather than full documents.
|
|
4407
4407
|
*
|
|
4408
4408
|
* IMPORTANT: This only works for collections where the read policy is "true".
|
|
@@ -4909,6 +4909,13 @@ async function runExpressionMany(many) {
|
|
|
4909
4909
|
throw error;
|
|
4910
4910
|
}
|
|
4911
4911
|
}
|
|
4912
|
+
/**
|
|
4913
|
+
* Write a document at `path`. Sugar for a one-element {@link setMany}.
|
|
4914
|
+
*
|
|
4915
|
+
* **Delete:** pass `null` as the document to delete it — `set(path, null)` is
|
|
4916
|
+
* the delete (there is no separate `del`/`remove`). It is routed through the
|
|
4917
|
+
* collection's policy `delete` rule and broadcasts a delete to subscribers.
|
|
4918
|
+
*/
|
|
4912
4919
|
async function set(path, document, options) {
|
|
4913
4920
|
const result = await setMany([{ path, document }], options);
|
|
4914
4921
|
// Clear cache entries that might be affected by this update
|
|
@@ -4927,10 +4934,10 @@ async function setMany(many, options) {
|
|
|
4927
4934
|
let documents = [];
|
|
4928
4935
|
const pathsToInvalidate = new Set();
|
|
4929
4936
|
for (const { path, document: documentWithReservedFields } of many) {
|
|
4930
|
-
// for setting, we should remove any pieces of the document that start with '
|
|
4937
|
+
// for setting, we should remove any pieces of the document that start with '_' as they are internal and shouldn't be updated. The API would block it if it is one of the reserved ones
|
|
4931
4938
|
let document = documentWithReservedFields;
|
|
4932
4939
|
if (documentWithReservedFields) {
|
|
4933
|
-
document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('
|
|
4940
|
+
document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('_')));
|
|
4934
4941
|
}
|
|
4935
4942
|
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
4936
4943
|
pathsToInvalidate.add(normalizedPath);
|
|
@@ -5054,7 +5061,7 @@ async function setMany(many, options) {
|
|
|
5054
5061
|
var _a, _b, _c, _d, _e;
|
|
5055
5062
|
// NOTE (backwards-compat revert): a program-allowlist on server-supplied
|
|
5056
5063
|
// `preInstructions` was tried here for the audit-8 SOL-drain concern, but it
|
|
5057
|
-
// broke legitimate onchain DeFi flows —
|
|
5064
|
+
// broke legitimate onchain DeFi flows — bounded routes Phoenix, Jupiter/swap,
|
|
5058
5065
|
// SPL-Token and ATA-creation instructions through standard-path preInstructions
|
|
5059
5066
|
// (see sol-helper remaining-accounts-extractor). A correct audit-8 fix must be
|
|
5060
5067
|
// DeFi-aware (e.g. block ONLY a raw System Transfer/TransferWithSeed, never a
|
|
@@ -5138,10 +5145,10 @@ async function setMany(many, options) {
|
|
|
5138
5145
|
const transaction = VersionedTransaction.deserialize(txBytes);
|
|
5139
5146
|
// Validate the transaction before signing: ensure only allowed programs
|
|
5140
5147
|
// and no unauthorized System program instructions (e.g., SOL transfers)
|
|
5141
|
-
const
|
|
5148
|
+
const BOUNDED_PROGRAM = 'poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp';
|
|
5142
5149
|
const COMPUTE_BUDGET = 'ComputeBudget111111111111111111111111111111';
|
|
5143
5150
|
const SYSTEM_PROGRAM = '11111111111111111111111111111111';
|
|
5144
|
-
const ALLOWED_PROGRAMS = new Set([
|
|
5151
|
+
const ALLOWED_PROGRAMS = new Set([BOUNDED_PROGRAM, COMPUTE_BUDGET, SYSTEM_PROGRAM]);
|
|
5145
5152
|
// System program instruction discriminators (first 4 bytes, little-endian u32)
|
|
5146
5153
|
const SYSTEM_TRANSFER = 2; // Transfer instruction index
|
|
5147
5154
|
const SYSTEM_TRANSFER_WITH_SEED = 11;
|
|
@@ -5735,8 +5742,8 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5735
5742
|
if (appId && appId !== config.appId) {
|
|
5736
5743
|
wsUrl.searchParams.append('appId', appId);
|
|
5737
5744
|
}
|
|
5738
|
-
else if (typeof window !== 'undefined' && window.
|
|
5739
|
-
wsUrl.searchParams.append('appId', window.
|
|
5745
|
+
else if (typeof window !== 'undefined' && window.CUSTOM_BOUNDED_APP_ID_HEADER) {
|
|
5746
|
+
wsUrl.searchParams.append('appId', window.CUSTOM_BOUNDED_APP_ID_HEADER);
|
|
5740
5747
|
}
|
|
5741
5748
|
else {
|
|
5742
5749
|
wsUrl.searchParams.append('appId', config.appId);
|
|
@@ -6596,7 +6603,7 @@ function serverTimestamp() {
|
|
|
6596
6603
|
// ---------------------------------------------------------------------------
|
|
6597
6604
|
// IDB helpers (lazy-loaded, non-blocking)
|
|
6598
6605
|
// ---------------------------------------------------------------------------
|
|
6599
|
-
const IDB_NAME = '
|
|
6606
|
+
const IDB_NAME = 'bounded-realtime';
|
|
6600
6607
|
const IDB_STORE = 'subscriptions';
|
|
6601
6608
|
const IDB_VERSION = 1;
|
|
6602
6609
|
let idbPromise = null;
|
|
@@ -7034,10 +7041,10 @@ class RealtimeStore {
|
|
|
7034
7041
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
7035
7042
|
const collectionPath = this.getCollectionPath(normalizedPath);
|
|
7036
7043
|
const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), {
|
|
7037
|
-
// System timestamp field name
|
|
7038
|
-
//
|
|
7044
|
+
// System timestamp field name: the Bounded worker stamps the neutral
|
|
7045
|
+
// `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.
|
|
7039
7046
|
// Match it so the optimistic doc lines up with the server's confirmation.
|
|
7040
|
-
[isBoundedNetwork() ? '_updatedAt' : '
|
|
7047
|
+
[isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });
|
|
7041
7048
|
const sub = this.findSubscriptionByPath(collectionPath);
|
|
7042
7049
|
let prevDoc = null;
|
|
7043
7050
|
if (sub) {
|
|
@@ -7490,6 +7497,7 @@ const functions = { invoke };
|
|
|
7490
7497
|
// collection. This file is the consumer surface around that runtime:
|
|
7491
7498
|
//
|
|
7492
7499
|
// bounded.live.intent(roomPath, intent) -> POST {realtime}/live/intent
|
|
7500
|
+
// bounded.live.status(roomPath) -> GET {realtime}/live/status
|
|
7493
7501
|
//
|
|
7494
7502
|
// It mirrors `functions.invoke` exactly: it attaches the caller's session token
|
|
7495
7503
|
// AUTOMATICALLY (the same token the SDK uses for data reads/writes), and throws
|
|
@@ -7502,18 +7510,14 @@ const functions = { invoke };
|
|
|
7502
7510
|
// Addressing: clients address rooms BY PATH (the `path` field in the body). The
|
|
7503
7511
|
// worker derives the roomId from `body.path` and sets X-Room-Id internally;
|
|
7504
7512
|
// clients NEVER set X-Room-Id. The caller's address is taken server-side from
|
|
7505
|
-
// the Authorization header (auth.userAddress) — it is NOT sent in the
|
|
7513
|
+
// the Authorization header (auth.userId/userAddress) — it is NOT sent in the
|
|
7514
|
+
// body.
|
|
7506
7515
|
//
|
|
7507
7516
|
// Subscribing to your view: a per-player view doc lives at
|
|
7508
|
-
// `<roomPath>/view/<
|
|
7509
|
-
// `rooms/$roomId/view/$
|
|
7510
|
-
//
|
|
7511
|
-
//
|
|
7512
|
-
// `subscribeView()` below is thin sugar over that (path construction + address
|
|
7513
|
-
// defaulting); it adds zero new transport.
|
|
7514
|
-
//
|
|
7515
|
-
// Status polling (GET /live/status?path=<sessionCollection>/<roomId>) is out of
|
|
7516
|
-
// scope for this piece — see follow-up.
|
|
7517
|
+
// `<roomPath>/view/<myUserId>` (the policy declares
|
|
7518
|
+
// `rooms/$roomId/view/$userId` ephemeral with `read: $userId == @user.id`).
|
|
7519
|
+
// Wallet-address keyed view paths remain supported through opts.address for
|
|
7520
|
+
// older policies, but new live rooms should key views by @user.id.
|
|
7517
7521
|
// ---------------------------------------------------------------------------
|
|
7518
7522
|
class LiveIntentError extends Error {
|
|
7519
7523
|
constructor(message, statusCode, details) {
|
|
@@ -7594,7 +7598,7 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7594
7598
|
res = await fetch(`${base}/live/intent`, {
|
|
7595
7599
|
method: 'POST',
|
|
7596
7600
|
headers,
|
|
7597
|
-
body: JSON.stringify({ path:
|
|
7601
|
+
body: JSON.stringify({ path: normalizedRoomPath, intent }),
|
|
7598
7602
|
signal: controller.signal,
|
|
7599
7603
|
});
|
|
7600
7604
|
}
|
|
@@ -7622,43 +7626,95 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7622
7626
|
}
|
|
7623
7627
|
return (body !== null && body !== void 0 ? body : { ok: true });
|
|
7624
7628
|
}
|
|
7629
|
+
/**
|
|
7630
|
+
* Fetch the runtime status for a live room.
|
|
7631
|
+
*
|
|
7632
|
+
* const s = await bounded.live.status('rooms/abc');
|
|
7633
|
+
* console.log(s.running, s.stopReason, s.generation, s.etag);
|
|
7634
|
+
*
|
|
7635
|
+
* This is diagnostic/ops surface. It reports whether the room facet exists,
|
|
7636
|
+
* whether it is currently ticking, why it last stopped, which module etag is
|
|
7637
|
+
* loaded, and the current generation used after terminal restarts.
|
|
7638
|
+
*/
|
|
7639
|
+
async function status(roomPath, opts = {}) {
|
|
7640
|
+
var _a, _b, _c, _d, _e;
|
|
7641
|
+
if (!roomPath || typeof roomPath !== 'string') {
|
|
7642
|
+
throw new LiveIntentError('A room path is required');
|
|
7643
|
+
}
|
|
7644
|
+
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
7645
|
+
const config = await getConfig();
|
|
7646
|
+
const base = realtimeHttpBase(config.wsApiUrl);
|
|
7647
|
+
const headers = Object.assign({ 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, ((_a = opts.headers) !== null && _a !== void 0 ? _a : {}));
|
|
7648
|
+
const controller = new AbortController();
|
|
7649
|
+
const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
|
|
7650
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7651
|
+
let res;
|
|
7652
|
+
try {
|
|
7653
|
+
res = await fetch(`${base}/live/status?path=${encodeURIComponent(normalizedRoomPath)}`, {
|
|
7654
|
+
method: 'GET',
|
|
7655
|
+
headers,
|
|
7656
|
+
signal: controller.signal,
|
|
7657
|
+
});
|
|
7658
|
+
}
|
|
7659
|
+
catch (err) {
|
|
7660
|
+
clearTimeout(timer);
|
|
7661
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
|
|
7662
|
+
throw new LiveIntentError(`Live status for "${normalizedRoomPath}" timed out after ${timeoutMs}ms`);
|
|
7663
|
+
}
|
|
7664
|
+
throw new LiveIntentError(`Failed to reach the realtime worker: ${(_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : String(err)}`);
|
|
7665
|
+
}
|
|
7666
|
+
clearTimeout(timer);
|
|
7667
|
+
let body = null;
|
|
7668
|
+
const text = await res.text();
|
|
7669
|
+
if (text) {
|
|
7670
|
+
try {
|
|
7671
|
+
body = JSON.parse(text);
|
|
7672
|
+
}
|
|
7673
|
+
catch (_f) {
|
|
7674
|
+
body = { raw: text };
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
if (!res.ok) {
|
|
7678
|
+
const message = (_e = (_d = body === null || body === void 0 ? void 0 : body.error) !== null && _d !== void 0 ? _d : body === null || body === void 0 ? void 0 : body.message) !== null && _e !== void 0 ? _e : `Live status failed with HTTP ${res.status}`;
|
|
7679
|
+
throw new LiveIntentError(message, res.status, body);
|
|
7680
|
+
}
|
|
7681
|
+
return body;
|
|
7682
|
+
}
|
|
7625
7683
|
/**
|
|
7626
7684
|
* Subscribe to YOUR per-player view of a room. Thin sugar over:
|
|
7627
7685
|
*
|
|
7628
|
-
* subscribe('<roomPath>/view/<
|
|
7686
|
+
* subscribe('<roomPath>/view/<myUserId>', { onData, onError })
|
|
7629
7687
|
*
|
|
7630
|
-
* The
|
|
7631
|
-
*
|
|
7688
|
+
* The view id defaults to the logged-in user's @user.id (from the session token
|
|
7689
|
+
* claims); pass `opts.userId` to override. `opts.address` is kept as a legacy
|
|
7690
|
+
* alias for older wallet-address keyed policies. Returns the unsubscribe
|
|
7632
7691
|
* function (a Promise<() => Promise<void>>, same as `subscribe`).
|
|
7633
7692
|
*
|
|
7634
7693
|
* Note: this is a browser-first helper (the WS subscription manager is
|
|
7635
7694
|
* browser-oriented). Server consumers should use `live.intent`.
|
|
7636
7695
|
*/
|
|
7637
7696
|
async function subscribeView(roomPath, opts) {
|
|
7638
|
-
var _a;
|
|
7697
|
+
var _a, _b, _c;
|
|
7639
7698
|
if (!roomPath || typeof roomPath !== 'string') {
|
|
7640
7699
|
throw new LiveIntentError('A room path is required');
|
|
7641
7700
|
}
|
|
7642
7701
|
if (!opts || typeof opts.onData !== 'function') {
|
|
7643
7702
|
throw new LiveIntentError('subscribeView requires an onData callback');
|
|
7644
7703
|
}
|
|
7645
|
-
let
|
|
7646
|
-
if (!
|
|
7704
|
+
let viewUserId = (_a = opts.userId) !== null && _a !== void 0 ? _a : opts.address;
|
|
7705
|
+
if (!viewUserId) {
|
|
7647
7706
|
const config = await getConfig();
|
|
7648
7707
|
const info = await getUserInfo(config.isServer);
|
|
7649
|
-
// getUserInfo returns the RAW idToken payload
|
|
7650
|
-
//
|
|
7651
|
-
//
|
|
7652
|
-
|
|
7653
|
-
// identity it already authenticated. Resolve it from the actual claim so a
|
|
7654
|
-
// logged-in caller never needs to pass its own address.
|
|
7655
|
-
address = (_a = info === null || info === void 0 ? void 0 : info.address) !== null && _a !== void 0 ? _a : info === null || info === void 0 ? void 0 : info['custom:walletAddress'];
|
|
7708
|
+
// getUserInfo returns the RAW idToken payload. The universal live view key
|
|
7709
|
+
// is @user.id (`custom:userId`); wallet-address keyed views are still
|
|
7710
|
+
// resolved as a compatibility fallback for older policies.
|
|
7711
|
+
viewUserId = (_c = (_b = info === null || info === void 0 ? void 0 : info['custom:userId']) !== null && _b !== void 0 ? _b : info === null || info === void 0 ? void 0 : info['custom:walletAddress']) !== null && _c !== void 0 ? _c : info === null || info === void 0 ? void 0 : info.address;
|
|
7656
7712
|
}
|
|
7657
|
-
if (!
|
|
7658
|
-
throw new LiveIntentError('Could not resolve a player
|
|
7713
|
+
if (!viewUserId || typeof viewUserId !== 'string') {
|
|
7714
|
+
throw new LiveIntentError('Could not resolve a player view id for subscribeView; pass opts.userId or log in first');
|
|
7659
7715
|
}
|
|
7660
7716
|
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
7661
|
-
const viewPath = `${normalizedRoomPath}/view/${
|
|
7717
|
+
const viewPath = `${normalizedRoomPath}/view/${viewUserId}`;
|
|
7662
7718
|
// This is a LIVE/room subscription by construction (the caller hands us the
|
|
7663
7719
|
// room path), so we route its connection to the per-room DO where the live
|
|
7664
7720
|
// view fan-out runs. App code passes NO routing — the room path is the helper's
|
|
@@ -7669,7 +7725,7 @@ async function subscribeView(roomPath, opts) {
|
|
|
7669
7725
|
return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError }, normalizedRoomPath);
|
|
7670
7726
|
}
|
|
7671
7727
|
/** The `bounded.live` namespace surface. */
|
|
7672
|
-
const live = { intent, subscribeView };
|
|
7728
|
+
const live = { intent, status, subscribeView };
|
|
7673
7729
|
|
|
7674
7730
|
// ---------------------------------------------------------------------------
|
|
7675
7731
|
// live-effects.ts -- AUTHOR-facing types + helpers for writing a Bounded live
|
|
@@ -7722,5 +7778,5 @@ function defineLiveModule(mod) {
|
|
|
7722
7778
|
return mod;
|
|
7723
7779
|
}
|
|
7724
7780
|
|
|
7725
|
-
export { EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, RealtimeStore, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveUserIdentityFromIdToken, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getRealtimeStore, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, queryAggregate, reconnectWithNewAuth, refreshSession, resetRealtimeStore, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, withEffects, wsDelete, wsGet, wsGetMany, wsQuery, wsSet };
|
|
7781
|
+
export { EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, RealtimeStore, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveUserIdentityFromIdToken, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getRealtimeStore, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, status as liveStatus, queryAggregate, reconnectWithNewAuth, refreshSession, resetRealtimeStore, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, withEffects, wsDelete, wsGet, wsGetMany, wsQuery, wsSet };
|
|
7726
7782
|
//# sourceMappingURL=index.mjs.map
|