@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.js
CHANGED
|
@@ -95,7 +95,7 @@ function getWebhookKeysUrl() {
|
|
|
95
95
|
/**
|
|
96
96
|
* True when init() configured a Bounded network (the Cloudflare-native stack).
|
|
97
97
|
* Synchronous + non-blocking. Used to route reads/writes that behave differently
|
|
98
|
-
* on Bounded vs the legacy
|
|
98
|
+
* on Bounded vs the legacy backend (e.g. `count`/`aggregate`, which on
|
|
99
99
|
* Bounded must use the deterministic server aggregation, not the legacy AI query).
|
|
100
100
|
*/
|
|
101
101
|
function isBoundedNetwork() {
|
|
@@ -2950,7 +2950,7 @@ class WebSessionManager {
|
|
|
2950
2950
|
// the RIGHT /session/refresh. Email overrides with humanAuthApiUrl; all
|
|
2951
2951
|
// wallet/guest/signature flows default to authApiUrl (the bounded-auth issuer).
|
|
2952
2952
|
const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
|
|
2953
|
-
localStorage.setItem(this.
|
|
2953
|
+
localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
|
|
2954
2954
|
accessToken,
|
|
2955
2955
|
idToken,
|
|
2956
2956
|
refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
|
|
@@ -2961,7 +2961,7 @@ class WebSessionManager {
|
|
|
2961
2961
|
static async getSession() {
|
|
2962
2962
|
if (typeof window === "undefined")
|
|
2963
2963
|
return null;
|
|
2964
|
-
const session = localStorage.getItem(this.
|
|
2964
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
2965
2965
|
if (!session)
|
|
2966
2966
|
return null;
|
|
2967
2967
|
const sessionObj = JSON.parse(session);
|
|
@@ -2985,7 +2985,7 @@ class WebSessionManager {
|
|
|
2985
2985
|
// Persist the ROTATED refresh token — the old one is now
|
|
2986
2986
|
// single-use spent; reusing it would revoke the family.
|
|
2987
2987
|
refreshed.refreshToken);
|
|
2988
|
-
const newSession = localStorage.getItem(this.
|
|
2988
|
+
const newSession = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
2989
2989
|
if (!newSession)
|
|
2990
2990
|
return null;
|
|
2991
2991
|
const newObj = JSON.parse(newSession);
|
|
@@ -3009,14 +3009,14 @@ class WebSessionManager {
|
|
|
3009
3009
|
static clearSession() {
|
|
3010
3010
|
if (typeof window === "undefined")
|
|
3011
3011
|
return;
|
|
3012
|
-
localStorage.removeItem(this.
|
|
3012
|
+
localStorage.removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3013
3013
|
}
|
|
3014
3014
|
/* ------------------------------------------------------------------ */
|
|
3015
3015
|
/* IS-AUTH */
|
|
3016
3016
|
/* ------------------------------------------------------------------ */
|
|
3017
3017
|
static isAuthenticated() {
|
|
3018
3018
|
// NOTE: this remains synchronous to stay backward compatible
|
|
3019
|
-
return !!localStorage.getItem(this.
|
|
3019
|
+
return !!localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3020
3020
|
}
|
|
3021
3021
|
/* ------------------------------------------------------------------ */
|
|
3022
3022
|
/* TOKEN HELPERS */
|
|
@@ -3024,13 +3024,13 @@ class WebSessionManager {
|
|
|
3024
3024
|
static getIdToken() {
|
|
3025
3025
|
if (typeof window === "undefined")
|
|
3026
3026
|
return null;
|
|
3027
|
-
const session = localStorage.getItem(this.
|
|
3027
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3028
3028
|
return session ? JSON.parse(session).idToken : null;
|
|
3029
3029
|
}
|
|
3030
3030
|
static getRefreshToken() {
|
|
3031
3031
|
if (typeof window === "undefined")
|
|
3032
3032
|
return null;
|
|
3033
|
-
const session = localStorage.getItem(this.
|
|
3033
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3034
3034
|
return session ? JSON.parse(session).refreshToken : null;
|
|
3035
3035
|
}
|
|
3036
3036
|
/** The issuer base that minted this session (for routing silent refresh). */
|
|
@@ -3038,7 +3038,7 @@ class WebSessionManager {
|
|
|
3038
3038
|
var _a;
|
|
3039
3039
|
if (typeof window === "undefined")
|
|
3040
3040
|
return null;
|
|
3041
|
-
const session = localStorage.getItem(this.
|
|
3041
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3042
3042
|
return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
|
|
3043
3043
|
}
|
|
3044
3044
|
/* ------------------------------------------------------------------ */
|
|
@@ -3048,7 +3048,7 @@ class WebSessionManager {
|
|
|
3048
3048
|
var _a;
|
|
3049
3049
|
if (typeof window === "undefined")
|
|
3050
3050
|
return;
|
|
3051
|
-
const session = localStorage.getItem(this.
|
|
3051
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3052
3052
|
if (!session)
|
|
3053
3053
|
return;
|
|
3054
3054
|
const sessionObj = JSON.parse(session);
|
|
@@ -3065,10 +3065,10 @@ class WebSessionManager {
|
|
|
3065
3065
|
sessionObj.refreshToken = refreshToken;
|
|
3066
3066
|
}
|
|
3067
3067
|
(_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
|
|
3068
|
-
localStorage.setItem(this.
|
|
3068
|
+
localStorage.setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
|
|
3069
3069
|
}
|
|
3070
3070
|
}
|
|
3071
|
-
WebSessionManager.
|
|
3071
|
+
WebSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
|
|
3072
3072
|
|
|
3073
3073
|
let _config = null;
|
|
3074
3074
|
class ReactNativeSessionManager {
|
|
@@ -3161,7 +3161,7 @@ class ReactNativeSessionManager {
|
|
|
3161
3161
|
// Issuer base for silent refresh (email overrides w/ humanAuthApiUrl; wallet/
|
|
3162
3162
|
// guest default to authApiUrl). See WebSessionManager for the rationale.
|
|
3163
3163
|
const sessionIssuer = (issuer || config.authApiUrl || "").replace(/\/$/, "");
|
|
3164
|
-
this.getStorage().setItem(this.
|
|
3164
|
+
this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(Object.assign({ address,
|
|
3165
3165
|
accessToken,
|
|
3166
3166
|
idToken,
|
|
3167
3167
|
refreshToken, appId: currentAppId }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
|
|
@@ -3170,7 +3170,7 @@ class ReactNativeSessionManager {
|
|
|
3170
3170
|
/* GET */
|
|
3171
3171
|
/* ------------------------------------------------------------------ */
|
|
3172
3172
|
static async getSession() {
|
|
3173
|
-
const session = this.getStorage().getItem(this.
|
|
3173
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3174
3174
|
if (!session)
|
|
3175
3175
|
return null;
|
|
3176
3176
|
const sessionObj = JSON.parse(session);
|
|
@@ -3191,7 +3191,7 @@ class ReactNativeSessionManager {
|
|
|
3191
3191
|
const refreshed = await refreshSession(refreshToken, sessionObj.issuer);
|
|
3192
3192
|
if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
|
|
3193
3193
|
await this.updateIdTokenAndAccessToken(refreshed.idToken, refreshed.accessToken, refreshed.refreshToken);
|
|
3194
|
-
const newSession = this.getStorage().getItem(this.
|
|
3194
|
+
const newSession = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3195
3195
|
if (!newSession)
|
|
3196
3196
|
return null;
|
|
3197
3197
|
const newObj = JSON.parse(newSession);
|
|
@@ -3213,20 +3213,20 @@ class ReactNativeSessionManager {
|
|
|
3213
3213
|
/* CLEAR */
|
|
3214
3214
|
/* ------------------------------------------------------------------ */
|
|
3215
3215
|
static clearSession() {
|
|
3216
|
-
this.getStorage().removeItem(this.
|
|
3216
|
+
this.getStorage().removeItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3217
3217
|
}
|
|
3218
3218
|
/* ------------------------------------------------------------------ */
|
|
3219
3219
|
/* IS-AUTH */
|
|
3220
3220
|
/* ------------------------------------------------------------------ */
|
|
3221
3221
|
static isAuthenticated() {
|
|
3222
|
-
return !!this.getStorage().getItem(this.
|
|
3222
|
+
return !!this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3223
3223
|
}
|
|
3224
3224
|
/* ------------------------------------------------------------------ */
|
|
3225
3225
|
/* TOKEN HELPERS */
|
|
3226
3226
|
/* ------------------------------------------------------------------ */
|
|
3227
3227
|
static getIdToken() {
|
|
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).idToken : null;
|
|
3231
3231
|
}
|
|
3232
3232
|
catch (_a) {
|
|
@@ -3237,7 +3237,7 @@ class ReactNativeSessionManager {
|
|
|
3237
3237
|
static getIssuer() {
|
|
3238
3238
|
var _a;
|
|
3239
3239
|
try {
|
|
3240
|
-
const session = this.getStorage().getItem(this.
|
|
3240
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3241
3241
|
return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
|
|
3242
3242
|
}
|
|
3243
3243
|
catch (_b) {
|
|
@@ -3246,7 +3246,7 @@ class ReactNativeSessionManager {
|
|
|
3246
3246
|
}
|
|
3247
3247
|
static getRefreshToken() {
|
|
3248
3248
|
try {
|
|
3249
|
-
const session = this.getStorage().getItem(this.
|
|
3249
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3250
3250
|
return session ? JSON.parse(session).refreshToken : null;
|
|
3251
3251
|
}
|
|
3252
3252
|
catch (_a) {
|
|
@@ -3258,7 +3258,7 @@ class ReactNativeSessionManager {
|
|
|
3258
3258
|
/* ------------------------------------------------------------------ */
|
|
3259
3259
|
static async updateIdTokenAndAccessToken(idToken, accessToken, refreshToken) {
|
|
3260
3260
|
var _a;
|
|
3261
|
-
const session = this.getStorage().getItem(this.
|
|
3261
|
+
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3262
3262
|
if (!session)
|
|
3263
3263
|
return;
|
|
3264
3264
|
const sessionObj = JSON.parse(session);
|
|
@@ -3274,10 +3274,10 @@ class ReactNativeSessionManager {
|
|
|
3274
3274
|
sessionObj.refreshToken = refreshToken;
|
|
3275
3275
|
}
|
|
3276
3276
|
(_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
|
|
3277
|
-
this.getStorage().setItem(this.
|
|
3277
|
+
this.getStorage().setItem(this.BOUNDED_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
|
|
3278
3278
|
}
|
|
3279
3279
|
}
|
|
3280
|
-
ReactNativeSessionManager.
|
|
3280
|
+
ReactNativeSessionManager.BOUNDED_SESSION_STORAGE_KEY = "bounded_session_storage";
|
|
3281
3281
|
|
|
3282
3282
|
/**
|
|
3283
3283
|
* Return the active CLIENT session manager for the current runtime.
|
|
@@ -3798,9 +3798,9 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
|
|
|
3798
3798
|
/* ENV helpers */
|
|
3799
3799
|
/* ------------------------------------------------------------------ */
|
|
3800
3800
|
// Canonical `BOUNDED_PRIVATE_KEY` (matches the CLI); legacy
|
|
3801
|
-
// `
|
|
3801
|
+
// `BOUNDED_SOLANA_KEYPAIR` still honored. Only consulted when no explicit
|
|
3802
3802
|
// keypair was provided (createWalletClient passes one).
|
|
3803
|
-
const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "
|
|
3803
|
+
const ENV_KEYPAIRS = ["BOUNDED_PRIVATE_KEY", "BOUNDED_SOLANA_KEYPAIR"];
|
|
3804
3804
|
function loadKeypairFromEnv() {
|
|
3805
3805
|
let secret;
|
|
3806
3806
|
let found;
|
|
@@ -3923,7 +3923,7 @@ var serverSessionManager = /*#__PURE__*/Object.freeze({
|
|
|
3923
3923
|
});
|
|
3924
3924
|
|
|
3925
3925
|
/**
|
|
3926
|
-
* Safe base64 helpers for
|
|
3926
|
+
* Safe base64 helpers for bounded-core.
|
|
3927
3927
|
*
|
|
3928
3928
|
* Uses the global atob/btoa when available (browser, RN with polyfill),
|
|
3929
3929
|
* falls back to the 'buffer' package (Node.js / SSR / RN without polyfill).
|
|
@@ -4103,7 +4103,7 @@ axiosRetry(apiClient, {
|
|
|
4103
4103
|
shouldResetTimeout: true,
|
|
4104
4104
|
onRetry: (retryCount, error, requestConfig) => {
|
|
4105
4105
|
var _a;
|
|
4106
|
-
console.warn(`[
|
|
4106
|
+
console.warn(`[bounded-sdk] retry ${retryCount} for ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url} — ${error.code || error.message}`);
|
|
4107
4107
|
},
|
|
4108
4108
|
});
|
|
4109
4109
|
const refreshInFlight = new Map();
|
|
@@ -4162,8 +4162,8 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
4162
4162
|
: await createAuthHeader(config.isServer);
|
|
4163
4163
|
const headers = Object.assign({ "Content-Type": "application/json", "X-Public-App-Id": config.appId, "X-App-Id": config.appId }, authHeader);
|
|
4164
4164
|
if (typeof window !== "undefined" &&
|
|
4165
|
-
window.
|
|
4166
|
-
const customAppId = window.
|
|
4165
|
+
window.CUSTOM_BOUNDED_APP_ID_HEADER) {
|
|
4166
|
+
const customAppId = window.CUSTOM_BOUNDED_APP_ID_HEADER;
|
|
4167
4167
|
if (customAppId) {
|
|
4168
4168
|
headers["X-App-Id"] = customAppId;
|
|
4169
4169
|
}
|
|
@@ -4422,7 +4422,7 @@ function parseAggregateValue(result) {
|
|
|
4422
4422
|
* Count items in a collection path. Returns a numeric result.
|
|
4423
4423
|
*
|
|
4424
4424
|
* This uses the AI query engine with a count-specific prompt prefix,
|
|
4425
|
-
* so
|
|
4425
|
+
* so Bounded will generate a $count aggregation pipeline and return
|
|
4426
4426
|
* just the count rather than full documents.
|
|
4427
4427
|
*
|
|
4428
4428
|
* IMPORTANT: This only works for collections where the read policy is "true".
|
|
@@ -4929,6 +4929,13 @@ async function runExpressionMany(many) {
|
|
|
4929
4929
|
throw error;
|
|
4930
4930
|
}
|
|
4931
4931
|
}
|
|
4932
|
+
/**
|
|
4933
|
+
* Write a document at `path`. Sugar for a one-element {@link setMany}.
|
|
4934
|
+
*
|
|
4935
|
+
* **Delete:** pass `null` as the document to delete it — `set(path, null)` is
|
|
4936
|
+
* the delete (there is no separate `del`/`remove`). It is routed through the
|
|
4937
|
+
* collection's policy `delete` rule and broadcasts a delete to subscribers.
|
|
4938
|
+
*/
|
|
4932
4939
|
async function set(path, document, options) {
|
|
4933
4940
|
const result = await setMany([{ path, document }], options);
|
|
4934
4941
|
// Clear cache entries that might be affected by this update
|
|
@@ -4947,10 +4954,10 @@ async function setMany(many, options) {
|
|
|
4947
4954
|
let documents = [];
|
|
4948
4955
|
const pathsToInvalidate = new Set();
|
|
4949
4956
|
for (const { path, document: documentWithReservedFields } of many) {
|
|
4950
|
-
// for setting, we should remove any pieces of the document that start with '
|
|
4957
|
+
// 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
|
|
4951
4958
|
let document = documentWithReservedFields;
|
|
4952
4959
|
if (documentWithReservedFields) {
|
|
4953
|
-
document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('
|
|
4960
|
+
document = Object.fromEntries(Object.entries(documentWithReservedFields).filter(([key]) => !key.startsWith('_')));
|
|
4954
4961
|
}
|
|
4955
4962
|
const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
|
|
4956
4963
|
pathsToInvalidate.add(normalizedPath);
|
|
@@ -5074,7 +5081,7 @@ async function setMany(many, options) {
|
|
|
5074
5081
|
var _a, _b, _c, _d, _e;
|
|
5075
5082
|
// NOTE (backwards-compat revert): a program-allowlist on server-supplied
|
|
5076
5083
|
// `preInstructions` was tried here for the audit-8 SOL-drain concern, but it
|
|
5077
|
-
// broke legitimate onchain DeFi flows —
|
|
5084
|
+
// broke legitimate onchain DeFi flows — bounded routes Phoenix, Jupiter/swap,
|
|
5078
5085
|
// SPL-Token and ATA-creation instructions through standard-path preInstructions
|
|
5079
5086
|
// (see sol-helper remaining-accounts-extractor). A correct audit-8 fix must be
|
|
5080
5087
|
// DeFi-aware (e.g. block ONLY a raw System Transfer/TransferWithSeed, never a
|
|
@@ -5158,10 +5165,10 @@ async function setMany(many, options) {
|
|
|
5158
5165
|
const transaction = web3_js.VersionedTransaction.deserialize(txBytes);
|
|
5159
5166
|
// Validate the transaction before signing: ensure only allowed programs
|
|
5160
5167
|
// and no unauthorized System program instructions (e.g., SOL transfers)
|
|
5161
|
-
const
|
|
5168
|
+
const BOUNDED_PROGRAM = 'poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp';
|
|
5162
5169
|
const COMPUTE_BUDGET = 'ComputeBudget111111111111111111111111111111';
|
|
5163
5170
|
const SYSTEM_PROGRAM = '11111111111111111111111111111111';
|
|
5164
|
-
const ALLOWED_PROGRAMS = new Set([
|
|
5171
|
+
const ALLOWED_PROGRAMS = new Set([BOUNDED_PROGRAM, COMPUTE_BUDGET, SYSTEM_PROGRAM]);
|
|
5165
5172
|
// System program instruction discriminators (first 4 bytes, little-endian u32)
|
|
5166
5173
|
const SYSTEM_TRANSFER = 2; // Transfer instruction index
|
|
5167
5174
|
const SYSTEM_TRANSFER_WITH_SEED = 11;
|
|
@@ -5755,8 +5762,8 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5755
5762
|
if (appId && appId !== config.appId) {
|
|
5756
5763
|
wsUrl.searchParams.append('appId', appId);
|
|
5757
5764
|
}
|
|
5758
|
-
else if (typeof window !== 'undefined' && window.
|
|
5759
|
-
wsUrl.searchParams.append('appId', window.
|
|
5765
|
+
else if (typeof window !== 'undefined' && window.CUSTOM_BOUNDED_APP_ID_HEADER) {
|
|
5766
|
+
wsUrl.searchParams.append('appId', window.CUSTOM_BOUNDED_APP_ID_HEADER);
|
|
5760
5767
|
}
|
|
5761
5768
|
else {
|
|
5762
5769
|
wsUrl.searchParams.append('appId', config.appId);
|
|
@@ -6616,7 +6623,7 @@ function serverTimestamp() {
|
|
|
6616
6623
|
// ---------------------------------------------------------------------------
|
|
6617
6624
|
// IDB helpers (lazy-loaded, non-blocking)
|
|
6618
6625
|
// ---------------------------------------------------------------------------
|
|
6619
|
-
const IDB_NAME = '
|
|
6626
|
+
const IDB_NAME = 'bounded-realtime';
|
|
6620
6627
|
const IDB_STORE = 'subscriptions';
|
|
6621
6628
|
const IDB_VERSION = 1;
|
|
6622
6629
|
let idbPromise = null;
|
|
@@ -7054,10 +7061,10 @@ class RealtimeStore {
|
|
|
7054
7061
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
7055
7062
|
const collectionPath = this.getCollectionPath(normalizedPath);
|
|
7056
7063
|
const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), {
|
|
7057
|
-
// System timestamp field name
|
|
7058
|
-
//
|
|
7064
|
+
// System timestamp field name: the Bounded worker stamps the neutral
|
|
7065
|
+
// `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.
|
|
7059
7066
|
// Match it so the optimistic doc lines up with the server's confirmation.
|
|
7060
|
-
[isBoundedNetwork() ? '_updatedAt' : '
|
|
7067
|
+
[isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });
|
|
7061
7068
|
const sub = this.findSubscriptionByPath(collectionPath);
|
|
7062
7069
|
let prevDoc = null;
|
|
7063
7070
|
if (sub) {
|
|
@@ -7510,6 +7517,7 @@ const functions = { invoke };
|
|
|
7510
7517
|
// collection. This file is the consumer surface around that runtime:
|
|
7511
7518
|
//
|
|
7512
7519
|
// bounded.live.intent(roomPath, intent) -> POST {realtime}/live/intent
|
|
7520
|
+
// bounded.live.status(roomPath) -> GET {realtime}/live/status
|
|
7513
7521
|
//
|
|
7514
7522
|
// It mirrors `functions.invoke` exactly: it attaches the caller's session token
|
|
7515
7523
|
// AUTOMATICALLY (the same token the SDK uses for data reads/writes), and throws
|
|
@@ -7522,18 +7530,14 @@ const functions = { invoke };
|
|
|
7522
7530
|
// Addressing: clients address rooms BY PATH (the `path` field in the body). The
|
|
7523
7531
|
// worker derives the roomId from `body.path` and sets X-Room-Id internally;
|
|
7524
7532
|
// clients NEVER set X-Room-Id. The caller's address is taken server-side from
|
|
7525
|
-
// the Authorization header (auth.userAddress) — it is NOT sent in the
|
|
7533
|
+
// the Authorization header (auth.userId/userAddress) — it is NOT sent in the
|
|
7534
|
+
// body.
|
|
7526
7535
|
//
|
|
7527
7536
|
// Subscribing to your view: a per-player view doc lives at
|
|
7528
|
-
// `<roomPath>/view/<
|
|
7529
|
-
// `rooms/$roomId/view/$
|
|
7530
|
-
//
|
|
7531
|
-
//
|
|
7532
|
-
// `subscribeView()` below is thin sugar over that (path construction + address
|
|
7533
|
-
// defaulting); it adds zero new transport.
|
|
7534
|
-
//
|
|
7535
|
-
// Status polling (GET /live/status?path=<sessionCollection>/<roomId>) is out of
|
|
7536
|
-
// scope for this piece — see follow-up.
|
|
7537
|
+
// `<roomPath>/view/<myUserId>` (the policy declares
|
|
7538
|
+
// `rooms/$roomId/view/$userId` ephemeral with `read: $userId == @user.id`).
|
|
7539
|
+
// Wallet-address keyed view paths remain supported through opts.address for
|
|
7540
|
+
// older policies, but new live rooms should key views by @user.id.
|
|
7537
7541
|
// ---------------------------------------------------------------------------
|
|
7538
7542
|
class LiveIntentError extends Error {
|
|
7539
7543
|
constructor(message, statusCode, details) {
|
|
@@ -7614,7 +7618,7 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7614
7618
|
res = await fetch(`${base}/live/intent`, {
|
|
7615
7619
|
method: 'POST',
|
|
7616
7620
|
headers,
|
|
7617
|
-
body: JSON.stringify({ path:
|
|
7621
|
+
body: JSON.stringify({ path: normalizedRoomPath, intent }),
|
|
7618
7622
|
signal: controller.signal,
|
|
7619
7623
|
});
|
|
7620
7624
|
}
|
|
@@ -7642,43 +7646,95 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7642
7646
|
}
|
|
7643
7647
|
return (body !== null && body !== void 0 ? body : { ok: true });
|
|
7644
7648
|
}
|
|
7649
|
+
/**
|
|
7650
|
+
* Fetch the runtime status for a live room.
|
|
7651
|
+
*
|
|
7652
|
+
* const s = await bounded.live.status('rooms/abc');
|
|
7653
|
+
* console.log(s.running, s.stopReason, s.generation, s.etag);
|
|
7654
|
+
*
|
|
7655
|
+
* This is diagnostic/ops surface. It reports whether the room facet exists,
|
|
7656
|
+
* whether it is currently ticking, why it last stopped, which module etag is
|
|
7657
|
+
* loaded, and the current generation used after terminal restarts.
|
|
7658
|
+
*/
|
|
7659
|
+
async function status(roomPath, opts = {}) {
|
|
7660
|
+
var _a, _b, _c, _d, _e;
|
|
7661
|
+
if (!roomPath || typeof roomPath !== 'string') {
|
|
7662
|
+
throw new LiveIntentError('A room path is required');
|
|
7663
|
+
}
|
|
7664
|
+
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
7665
|
+
const config = await getConfig();
|
|
7666
|
+
const base = realtimeHttpBase(config.wsApiUrl);
|
|
7667
|
+
const headers = Object.assign({ 'X-App-Id': config.appId, 'X-Public-App-Id': config.appId }, ((_a = opts.headers) !== null && _a !== void 0 ? _a : {}));
|
|
7668
|
+
const controller = new AbortController();
|
|
7669
|
+
const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
|
|
7670
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7671
|
+
let res;
|
|
7672
|
+
try {
|
|
7673
|
+
res = await fetch(`${base}/live/status?path=${encodeURIComponent(normalizedRoomPath)}`, {
|
|
7674
|
+
method: 'GET',
|
|
7675
|
+
headers,
|
|
7676
|
+
signal: controller.signal,
|
|
7677
|
+
});
|
|
7678
|
+
}
|
|
7679
|
+
catch (err) {
|
|
7680
|
+
clearTimeout(timer);
|
|
7681
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
|
|
7682
|
+
throw new LiveIntentError(`Live status for "${normalizedRoomPath}" timed out after ${timeoutMs}ms`);
|
|
7683
|
+
}
|
|
7684
|
+
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)}`);
|
|
7685
|
+
}
|
|
7686
|
+
clearTimeout(timer);
|
|
7687
|
+
let body = null;
|
|
7688
|
+
const text = await res.text();
|
|
7689
|
+
if (text) {
|
|
7690
|
+
try {
|
|
7691
|
+
body = JSON.parse(text);
|
|
7692
|
+
}
|
|
7693
|
+
catch (_f) {
|
|
7694
|
+
body = { raw: text };
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
if (!res.ok) {
|
|
7698
|
+
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}`;
|
|
7699
|
+
throw new LiveIntentError(message, res.status, body);
|
|
7700
|
+
}
|
|
7701
|
+
return body;
|
|
7702
|
+
}
|
|
7645
7703
|
/**
|
|
7646
7704
|
* Subscribe to YOUR per-player view of a room. Thin sugar over:
|
|
7647
7705
|
*
|
|
7648
|
-
* subscribe('<roomPath>/view/<
|
|
7706
|
+
* subscribe('<roomPath>/view/<myUserId>', { onData, onError })
|
|
7649
7707
|
*
|
|
7650
|
-
* The
|
|
7651
|
-
*
|
|
7708
|
+
* The view id defaults to the logged-in user's @user.id (from the session token
|
|
7709
|
+
* claims); pass `opts.userId` to override. `opts.address` is kept as a legacy
|
|
7710
|
+
* alias for older wallet-address keyed policies. Returns the unsubscribe
|
|
7652
7711
|
* function (a Promise<() => Promise<void>>, same as `subscribe`).
|
|
7653
7712
|
*
|
|
7654
7713
|
* Note: this is a browser-first helper (the WS subscription manager is
|
|
7655
7714
|
* browser-oriented). Server consumers should use `live.intent`.
|
|
7656
7715
|
*/
|
|
7657
7716
|
async function subscribeView(roomPath, opts) {
|
|
7658
|
-
var _a;
|
|
7717
|
+
var _a, _b, _c;
|
|
7659
7718
|
if (!roomPath || typeof roomPath !== 'string') {
|
|
7660
7719
|
throw new LiveIntentError('A room path is required');
|
|
7661
7720
|
}
|
|
7662
7721
|
if (!opts || typeof opts.onData !== 'function') {
|
|
7663
7722
|
throw new LiveIntentError('subscribeView requires an onData callback');
|
|
7664
7723
|
}
|
|
7665
|
-
let
|
|
7666
|
-
if (!
|
|
7724
|
+
let viewUserId = (_a = opts.userId) !== null && _a !== void 0 ? _a : opts.address;
|
|
7725
|
+
if (!viewUserId) {
|
|
7667
7726
|
const config = await getConfig();
|
|
7668
7727
|
const info = await getUserInfo(config.isServer);
|
|
7669
|
-
// getUserInfo returns the RAW idToken payload
|
|
7670
|
-
//
|
|
7671
|
-
//
|
|
7672
|
-
|
|
7673
|
-
// identity it already authenticated. Resolve it from the actual claim so a
|
|
7674
|
-
// logged-in caller never needs to pass its own address.
|
|
7675
|
-
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'];
|
|
7728
|
+
// getUserInfo returns the RAW idToken payload. The universal live view key
|
|
7729
|
+
// is @user.id (`custom:userId`); wallet-address keyed views are still
|
|
7730
|
+
// resolved as a compatibility fallback for older policies.
|
|
7731
|
+
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;
|
|
7676
7732
|
}
|
|
7677
|
-
if (!
|
|
7678
|
-
throw new LiveIntentError('Could not resolve a player
|
|
7733
|
+
if (!viewUserId || typeof viewUserId !== 'string') {
|
|
7734
|
+
throw new LiveIntentError('Could not resolve a player view id for subscribeView; pass opts.userId or log in first');
|
|
7679
7735
|
}
|
|
7680
7736
|
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
7681
|
-
const viewPath = `${normalizedRoomPath}/view/${
|
|
7737
|
+
const viewPath = `${normalizedRoomPath}/view/${viewUserId}`;
|
|
7682
7738
|
// This is a LIVE/room subscription by construction (the caller hands us the
|
|
7683
7739
|
// room path), so we route its connection to the per-room DO where the live
|
|
7684
7740
|
// view fan-out runs. App code passes NO routing — the room path is the helper's
|
|
@@ -7689,7 +7745,7 @@ async function subscribeView(roomPath, opts) {
|
|
|
7689
7745
|
return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError }, normalizedRoomPath);
|
|
7690
7746
|
}
|
|
7691
7747
|
/** The `bounded.live` namespace surface. */
|
|
7692
|
-
const live = { intent, subscribeView };
|
|
7748
|
+
const live = { intent, status, subscribeView };
|
|
7693
7749
|
|
|
7694
7750
|
// ---------------------------------------------------------------------------
|
|
7695
7751
|
// live-effects.ts -- AUTHOR-facing types + helpers for writing a Bounded live
|
|
@@ -7778,6 +7834,7 @@ exports.invokeFunction = invoke;
|
|
|
7778
7834
|
exports.isEffectResult = isEffectResult;
|
|
7779
7835
|
exports.live = live;
|
|
7780
7836
|
exports.liveIntent = intent;
|
|
7837
|
+
exports.liveStatus = status;
|
|
7781
7838
|
exports.queryAggregate = queryAggregate;
|
|
7782
7839
|
exports.reconnectWithNewAuth = reconnectWithNewAuth;
|
|
7783
7840
|
exports.refreshSession = refreshSession;
|