@bounded-sh/core 0.0.36 → 0.0.38
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/decline.d.ts +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +176 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3838,6 +3838,126 @@ async function updateIdTokenAndAccessToken(idToken, accessToken, isServer = fals
|
|
|
3838
3838
|
await getActiveSessionManager().updateIdTokenAndAccessToken(idToken, accessToken, refreshToken);
|
|
3839
3839
|
}
|
|
3840
3840
|
|
|
3841
|
+
// ---------------------------------------------------------------------------
|
|
3842
|
+
// decline.ts -- the structured "declined write" surface.
|
|
3843
|
+
//
|
|
3844
|
+
// When a data-plane write violates a proven policy rule or invariant, the
|
|
3845
|
+
// realtime backend rejects it: HTTP 403 { verdict:'declined', reason:'rule' }
|
|
3846
|
+
// or 409 { verdict:'declined', reason:'invariant', invariant } on the request
|
|
3847
|
+
// path, or a coded error frame over the room socket. This module turns those
|
|
3848
|
+
// rejections into a first-class, share-worthy decline:
|
|
3849
|
+
//
|
|
3850
|
+
// - `BoundedDeclineError` -- the thrown error. Keeps every legacy field apps
|
|
3851
|
+
// already catch (code/status/statusCode/requestId/...), and adds `.decline`
|
|
3852
|
+
// (the structured object below) plus a stable `.isBoundedDecline` flag.
|
|
3853
|
+
// - `deriveDecline(body, status)` -- normalizes a server rejection body into a
|
|
3854
|
+
// `BoundedDecline`, trusting a backend-sent `decline` object when present
|
|
3855
|
+
// and synthesizing a minimal one from legacy fields otherwise. Returns
|
|
3856
|
+
// `null` when the rejection is not a policy/invariant decline.
|
|
3857
|
+
// - `dispatchDeclineEvent(decline, appId?)` -- in a browser, fires the
|
|
3858
|
+
// `bounded:decline` CustomEvent the hosted widget listens to. Fire-and-
|
|
3859
|
+
// forget: guarded for non-browser/SSR, never throws, never delays the throw.
|
|
3860
|
+
// ---------------------------------------------------------------------------
|
|
3861
|
+
const RULE_DEFAULT_MESSAGE = 'This write is not allowed by policy.';
|
|
3862
|
+
const INVARIANT_DEFAULT_MESSAGE = 'This write would violate a proven invariant.';
|
|
3863
|
+
/** The window event the hosted widget listens to for rendering a decline card. */
|
|
3864
|
+
const DECLINE_EVENT_NAME = 'bounded:decline';
|
|
3865
|
+
/**
|
|
3866
|
+
* Normalize a server rejection body into a structured `BoundedDecline`, or
|
|
3867
|
+
* `null` when the rejection is not a policy/invariant decline.
|
|
3868
|
+
*
|
|
3869
|
+
* Prefers a backend-sent `decline` object verbatim; otherwise synthesizes a
|
|
3870
|
+
* minimal `{ verdict, reason, invariant?, message }` from the legacy envelope
|
|
3871
|
+
* (`verdict` / `reason` / `code` / `invariant`). Only strong signals count —
|
|
3872
|
+
* a bare 403/409 with no policy marker is left alone (returns `null`).
|
|
3873
|
+
*/
|
|
3874
|
+
function deriveDecline(body, status) {
|
|
3875
|
+
if (!body || typeof body !== 'object')
|
|
3876
|
+
return null;
|
|
3877
|
+
// Trust a backend-sent structured decline verbatim.
|
|
3878
|
+
const provided = body.decline;
|
|
3879
|
+
if (provided && typeof provided === 'object' && provided.verdict === 'declined') {
|
|
3880
|
+
return provided;
|
|
3881
|
+
}
|
|
3882
|
+
const rawReason = body.reason;
|
|
3883
|
+
const code = body.code;
|
|
3884
|
+
const hasInvariant = body.invariant != null;
|
|
3885
|
+
let reason = rawReason === 'rule' || rawReason === 'invariant' ? rawReason : undefined;
|
|
3886
|
+
if (!reason) {
|
|
3887
|
+
if (code === 'invariant_violation' || hasInvariant)
|
|
3888
|
+
reason = 'invariant';
|
|
3889
|
+
else if (code === 'policy_denied')
|
|
3890
|
+
reason = 'rule';
|
|
3891
|
+
}
|
|
3892
|
+
const isDecline = body.verdict === 'declined' || reason === 'rule' || reason === 'invariant';
|
|
3893
|
+
if (!isDecline)
|
|
3894
|
+
return null;
|
|
3895
|
+
const resolvedReason = reason !== null && reason !== void 0 ? reason : (status === 409 ? 'invariant' : 'rule');
|
|
3896
|
+
const rawMessage = body.message;
|
|
3897
|
+
const message = typeof rawMessage === 'string' && rawMessage.length > 0
|
|
3898
|
+
? rawMessage
|
|
3899
|
+
: resolvedReason === 'invariant'
|
|
3900
|
+
? INVARIANT_DEFAULT_MESSAGE
|
|
3901
|
+
: RULE_DEFAULT_MESSAGE;
|
|
3902
|
+
const decline = { verdict: 'declined', reason: resolvedReason, message };
|
|
3903
|
+
if (hasInvariant)
|
|
3904
|
+
decline.invariant = body.invariant;
|
|
3905
|
+
if (typeof body.collection === 'string')
|
|
3906
|
+
decline.collection = body.collection;
|
|
3907
|
+
if (typeof body.op === 'string')
|
|
3908
|
+
decline.op = body.op;
|
|
3909
|
+
if (body.boundary && typeof body.boundary === 'object')
|
|
3910
|
+
decline.boundary = body.boundary;
|
|
3911
|
+
if (typeof body.provenAtDeploy === 'boolean')
|
|
3912
|
+
decline.provenAtDeploy = body.provenAtDeploy;
|
|
3913
|
+
return decline;
|
|
3914
|
+
}
|
|
3915
|
+
/**
|
|
3916
|
+
* In a browser, dispatch a `bounded:decline` CustomEvent for the surfaced
|
|
3917
|
+
* decline. Fire-and-forget: guarded for non-browser/SSR (no `window`/
|
|
3918
|
+
* `CustomEvent`), never throws, and never delays the rejection it accompanies.
|
|
3919
|
+
*/
|
|
3920
|
+
function dispatchDeclineEvent(decline, appId) {
|
|
3921
|
+
try {
|
|
3922
|
+
if (typeof window === 'undefined')
|
|
3923
|
+
return;
|
|
3924
|
+
const Ctor = globalThis.CustomEvent;
|
|
3925
|
+
if (typeof Ctor !== 'function')
|
|
3926
|
+
return;
|
|
3927
|
+
const detail = Object.assign({}, decline);
|
|
3928
|
+
if (appId)
|
|
3929
|
+
detail.appId = appId;
|
|
3930
|
+
detail.at = Date.now();
|
|
3931
|
+
window.dispatchEvent(new Ctor(DECLINE_EVENT_NAME, { detail }));
|
|
3932
|
+
}
|
|
3933
|
+
catch (_a) {
|
|
3934
|
+
// Fire-and-forget: a widget/telemetry hiccup must never mask the decline.
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* A rejected write that violated a proven policy rule or invariant. Thrown by
|
|
3939
|
+
* the data-plane write paths (set/setMany/update/delete) in place of a bare
|
|
3940
|
+
* Error, and attached (as `.decline`) to live-intent rejections.
|
|
3941
|
+
*
|
|
3942
|
+
* Back-compatible: the full server envelope (code/status/statusCode/requestId/
|
|
3943
|
+
* ...) is copied onto the instance, so existing `catch` handlers keep working;
|
|
3944
|
+
* new code reads `.decline`. Detect it with `err.name === 'BoundedDeclineError'`
|
|
3945
|
+
* or the bundle-copy-safe `err.isBoundedDecline === true`.
|
|
3946
|
+
*/
|
|
3947
|
+
class BoundedDeclineError extends Error {
|
|
3948
|
+
constructor(decline, envelope) {
|
|
3949
|
+
super(`Declined: ${decline.message}`);
|
|
3950
|
+
// Copy the legacy envelope FIRST, then re-assert our own fields so a
|
|
3951
|
+
// stray envelope key can never clobber name/message/decline/flag.
|
|
3952
|
+
if (envelope && typeof envelope === 'object')
|
|
3953
|
+
Object.assign(this, envelope);
|
|
3954
|
+
this.name = 'BoundedDeclineError';
|
|
3955
|
+
this.message = `Declined: ${decline.message}`;
|
|
3956
|
+
this.decline = decline;
|
|
3957
|
+
this.isBoundedDecline = true;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3841
3961
|
const apiClient = axios.create();
|
|
3842
3962
|
axiosRetry(apiClient, {
|
|
3843
3963
|
retries: 2,
|
|
@@ -3893,7 +4013,7 @@ async function refreshAuthSessionOnce(appId, isServer) {
|
|
|
3893
4013
|
return refreshPromise;
|
|
3894
4014
|
}
|
|
3895
4015
|
async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
3896
|
-
var _a, _b, _c, _d, _e, _f
|
|
4016
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3897
4017
|
const config = await getConfig();
|
|
3898
4018
|
async function executeRequest() {
|
|
3899
4019
|
var _a;
|
|
@@ -3944,7 +4064,7 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
3944
4064
|
}
|
|
3945
4065
|
return await executeRequest();
|
|
3946
4066
|
}
|
|
3947
|
-
catch (
|
|
4067
|
+
catch (_g) {
|
|
3948
4068
|
throw error;
|
|
3949
4069
|
}
|
|
3950
4070
|
}
|
|
@@ -3955,17 +4075,34 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
3955
4075
|
const responseData = typeof error.response.data === 'object'
|
|
3956
4076
|
? error.response.data
|
|
3957
4077
|
: { message: String(error.response.data) };
|
|
3958
|
-
const
|
|
4078
|
+
const httpStatus = (_d = error.response) === null || _d === void 0 ? void 0 : _d.status;
|
|
4079
|
+
// A policy/invariant denial (403 rule / 409 invariant) is surfaced as
|
|
4080
|
+
// a first-class BoundedDeclineError carrying `.decline`; every other
|
|
4081
|
+
// failure stays a bare enriched Error (unchanged behavior).
|
|
4082
|
+
const decline = deriveDecline(responseData, httpStatus);
|
|
4083
|
+
const apiError = decline
|
|
4084
|
+
? new BoundedDeclineError(decline)
|
|
4085
|
+
: new Error(responseData.message || error.message || 'Request failed');
|
|
3959
4086
|
Object.assign(apiError, responseData);
|
|
3960
|
-
apiError.statusCode =
|
|
4087
|
+
apiError.statusCode = httpStatus;
|
|
3961
4088
|
// Surface the structured error envelope { code, status, requestId } so
|
|
3962
4089
|
// callers can branch on the stable category (e.g.
|
|
3963
4090
|
// `if (e.code === 'invariant_violation')`) without parsing the message.
|
|
3964
4091
|
// `code` is a stable category ('policy_denied' | 'invariant_violation');
|
|
3965
4092
|
// `status` is the HTTP status; `requestId` correlates with the decision log.
|
|
3966
4093
|
apiError.code = (_e = responseData.code) !== null && _e !== void 0 ? _e : apiError.code;
|
|
3967
|
-
apiError.status =
|
|
3968
|
-
apiError.requestId = (
|
|
4094
|
+
apiError.status = httpStatus !== null && httpStatus !== void 0 ? httpStatus : responseData.status;
|
|
4095
|
+
apiError.requestId = (_f = responseData.requestId) !== null && _f !== void 0 ? _f : apiError.requestId;
|
|
4096
|
+
if (decline) {
|
|
4097
|
+
// Re-assert decline-owned fields after the envelope copy, then
|
|
4098
|
+
// notify the browser widget. Fire-and-forget: never delays or
|
|
4099
|
+
// masks the throw.
|
|
4100
|
+
apiError.decline = decline;
|
|
4101
|
+
apiError.name = 'BoundedDeclineError';
|
|
4102
|
+
apiError.isBoundedDecline = true;
|
|
4103
|
+
apiError.message = `Declined: ${decline.message}`;
|
|
4104
|
+
dispatchDeclineEvent(decline, config.appId);
|
|
4105
|
+
}
|
|
3969
4106
|
throw apiError;
|
|
3970
4107
|
}
|
|
3971
4108
|
throw error;
|
|
@@ -6042,6 +6179,24 @@ function handleServerMessage(connection, message) {
|
|
|
6042
6179
|
err.requestId = message.requestId;
|
|
6043
6180
|
if (message.subscriptionId)
|
|
6044
6181
|
err.subscriptionId = message.subscriptionId;
|
|
6182
|
+
// A policy/invariant denial over the socket carries the same
|
|
6183
|
+
// structured decline as the HTTP path (live-intent acks, WS
|
|
6184
|
+
// writes). Mirror `.decline` and notify the browser widget.
|
|
6185
|
+
const declineBody = message.decline
|
|
6186
|
+
? { decline: message.decline }
|
|
6187
|
+
: {
|
|
6188
|
+
code: message.code,
|
|
6189
|
+
message: message.message,
|
|
6190
|
+
reason: message.reason,
|
|
6191
|
+
invariant: message.invariant,
|
|
6192
|
+
verdict: message.verdict,
|
|
6193
|
+
};
|
|
6194
|
+
const decline = deriveDecline(declineBody);
|
|
6195
|
+
if (decline) {
|
|
6196
|
+
err.decline = decline;
|
|
6197
|
+
err.isBoundedDecline = true;
|
|
6198
|
+
dispatchDeclineEvent(decline, connection.appId);
|
|
6199
|
+
}
|
|
6045
6200
|
return err;
|
|
6046
6201
|
};
|
|
6047
6202
|
const wsError = buildWsError();
|
|
@@ -7109,7 +7264,17 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
7109
7264
|
}
|
|
7110
7265
|
if (!res.ok) {
|
|
7111
7266
|
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}`;
|
|
7112
|
-
|
|
7267
|
+
const liveError = new LiveIntentError(message, res.status, body);
|
|
7268
|
+
// A policy/invariant denial of a live intent carries the same structured
|
|
7269
|
+
// decline as the data-write path. Mirror `.decline` onto the LiveIntentError
|
|
7270
|
+
// (its name/contract is unchanged) and notify the browser widget.
|
|
7271
|
+
const decline = deriveDecline(body, res.status);
|
|
7272
|
+
if (decline) {
|
|
7273
|
+
liveError.decline = decline;
|
|
7274
|
+
liveError.isBoundedDecline = true;
|
|
7275
|
+
dispatchDeclineEvent(decline, config.appId);
|
|
7276
|
+
}
|
|
7277
|
+
throw liveError;
|
|
7113
7278
|
}
|
|
7114
7279
|
return (body !== null && body !== void 0 ? body : { ok: true });
|
|
7115
7280
|
}
|
|
@@ -7263,5 +7428,5 @@ function defineLiveModule(mod) {
|
|
|
7263
7428
|
return mod;
|
|
7264
7429
|
}
|
|
7265
7430
|
|
|
7266
|
-
export { EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveUserIdentityFromIdToken, docId, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, status as liveStatus, now, queryAggregate, reconnectWithNewAuth, refreshSession, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, toMillis, toSeconds, withEffects };
|
|
7431
|
+
export { BoundedDeclineError, DECLINE_EVENT_NAME, EFFECT_INTENT_ADDRESS, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, ReactNativeSessionManager, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithSignature, defineLiveModule, deriveDecline, deriveUserIdentityFromIdToken, dispatchDeclineEvent, docId, functions, genAuthNonce, genSolanaMessage, get, getActiveSessionManager, getCachedData, getConfig, getFiles, getIdToken, getMany, getWebhookKeysUrl, hasActiveConnection, increment, init, invoke as invokeFunction, isEffectResult, live, intent as liveIntent, status as liveStatus, now, queryAggregate, reconnectWithNewAuth, refreshSession, revokeSession, runExpression, runExpressionMany, runQuery, runQueryMany, search, serverTimestamp, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe, subscribeView as subscribeLiveView, toMillis, toSeconds, withEffects };
|
|
7267
7432
|
//# sourceMappingURL=index.mjs.map
|