@bounded-sh/core 0.0.100 → 0.0.101
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/app-token.d.ts +77 -0
- package/dist/client/create-client.d.ts +69 -17
- package/dist/client/subscription-v2.d.ts +23 -0
- package/dist/{index-B8PpRj6x.js → index-BEGeDFQI.js} +739 -106
- package/dist/index-BEGeDFQI.js.map +1 -0
- package/dist/{index-DW5yMQwV.mjs → index-CMT0saxy.mjs} +736 -107
- package/dist/index-CMT0saxy.mjs.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{realtime-store-CeDT7vJF.mjs → realtime-store-DqAAqfo9.mjs} +2 -2
- package/dist/{realtime-store-CeDT7vJF.mjs.map → realtime-store-DqAAqfo9.mjs.map} +1 -1
- package/dist/{realtime-store-D7mJ7rTj.js → realtime-store-DrtEj-bE.js} +2 -2
- package/dist/{realtime-store-D7mJ7rTj.js.map → realtime-store-DrtEj-bE.js.map} +1 -1
- package/dist/{solana-message-w2FxOkFA.js → solana-message-CJTnYiJu.js} +2 -2
- package/dist/{solana-message-w2FxOkFA.js.map → solana-message-CJTnYiJu.js.map} +1 -1
- package/dist/{solana-message-BGkYFU3C.mjs → solana-message-C_717vAx.mjs} +2 -2
- package/dist/{solana-message-BGkYFU3C.mjs.map → solana-message-C_717vAx.mjs.map} +1 -1
- package/dist/{solana-operations-DJQDYdrk.js → solana-operations-CU0YbiMu.js} +3 -3
- package/dist/{solana-operations-DJQDYdrk.js.map → solana-operations-CU0YbiMu.js.map} +1 -1
- package/dist/{solana-operations-CHgW7Pue.mjs → solana-operations-DGNA2nGZ.mjs} +4 -4
- package/dist/{solana-operations-CHgW7Pue.mjs.map → solana-operations-DGNA2nGZ.mjs.map} +1 -1
- package/dist/{solana-v1-operations-BPn-poee.mjs → solana-v1-operations-Bpd4TASG.mjs} +3 -3
- package/dist/{solana-v1-operations-BPn-poee.mjs.map → solana-v1-operations-Bpd4TASG.mjs.map} +1 -1
- package/dist/{solana-v1-operations-BF2rjG23.js → solana-v1-operations-yQII8r1s.js} +3 -3
- package/dist/{solana-v1-operations-BF2rjG23.js.map → solana-v1-operations-yQII8r1s.js.map} +1 -1
- package/package.json +2 -2
- package/dist/index-B8PpRj6x.js.map +0 -1
- package/dist/index-DW5yMQwV.mjs.map +0 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** A minted child-app credential and the moment it stops being usable. */
|
|
2
|
+
export interface AppToken {
|
|
3
|
+
/** The bare id token to send as `Authorization: Bearer <token>`. */
|
|
4
|
+
token: string;
|
|
5
|
+
/** Epoch milliseconds at which the token expires. */
|
|
6
|
+
expiresAtMs: number;
|
|
7
|
+
/** The child app the token is pinned to. */
|
|
8
|
+
appId: string;
|
|
9
|
+
}
|
|
10
|
+
/** Why an exchange stopped trying. Recorded, never retried past this point. */
|
|
11
|
+
export interface AppTokenGiveUp {
|
|
12
|
+
appId: string;
|
|
13
|
+
/** HTTP status of the last attempt, or null for a transport failure. */
|
|
14
|
+
status: number | null;
|
|
15
|
+
/** Machine-readable reason: the issuer's `error` field where it sent one. */
|
|
16
|
+
reason: string;
|
|
17
|
+
attempts: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ExchangeAppTokenOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Origin of the Bounded Auth issuer that serves `/app-token/exchange` - the
|
|
22
|
+
* resolved `humanAuthApiUrl` for your network. A path suffix is tolerated.
|
|
23
|
+
*/
|
|
24
|
+
authApiUrl: string;
|
|
25
|
+
/** The child app to mint a token for. */
|
|
26
|
+
appId: string;
|
|
27
|
+
/** The signed-in PARENT app's id token (e.g. the venue session's). */
|
|
28
|
+
parentToken: string;
|
|
29
|
+
/** HTTP attempts, including the first. Default 3, clamped to 1..5. */
|
|
30
|
+
maxAttempts?: number;
|
|
31
|
+
/** Called once when an exchange stops trying. Defaults to a console warning. */
|
|
32
|
+
onGiveUp?: (info: AppTokenGiveUp) => void;
|
|
33
|
+
/** Test seam; defaults to the global fetch. */
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
}
|
|
36
|
+
/** Thrown when an exchange stops trying. `giveUp` carries the recorded reason. */
|
|
37
|
+
export declare class AppTokenExchangeError extends Error {
|
|
38
|
+
readonly giveUp: AppTokenGiveUp;
|
|
39
|
+
constructor(giveUp: AppTokenGiveUp);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a child-app id token for the human the parent token belongs to, from cache
|
|
43
|
+
* when one is still comfortably fresh and otherwise by exchanging once.
|
|
44
|
+
*
|
|
45
|
+
* Concurrent callers for the same (parent credential, appId) share one exchange.
|
|
46
|
+
* On give-up this rejects with {@link AppTokenExchangeError} and caches nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function exchangeAppToken(options: ExchangeAppTokenOptions): Promise<AppToken>;
|
|
49
|
+
/**
|
|
50
|
+
* Drop cached child-app tokens. Call this on sign-out; an identity change needs
|
|
51
|
+
* no call, because the cache key is the exact parent credential.
|
|
52
|
+
*
|
|
53
|
+
* With no argument every entry goes. `appId` narrows to one child app, and
|
|
54
|
+
* `parentToken` narrows to one session's entries.
|
|
55
|
+
*/
|
|
56
|
+
export declare function clearAppTokenCache(scope?: {
|
|
57
|
+
appId?: string;
|
|
58
|
+
parentToken?: string;
|
|
59
|
+
}): void;
|
|
60
|
+
export interface AppTokenAuthHeadersOptions extends Omit<ExchangeAppTokenOptions, 'parentToken'> {
|
|
61
|
+
/**
|
|
62
|
+
* The CURRENT parent-app credential, read on every call so a refreshed session
|
|
63
|
+
* is picked up and a signed-out one degrades the instance to anonymous.
|
|
64
|
+
* Return the bare id token, or null/undefined when signed out.
|
|
65
|
+
*/
|
|
66
|
+
parentToken: () => string | null | undefined | Promise<string | null | undefined>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the `getAuthHeaders` a scoped `createClient({ appId, getAuthHeaders })`
|
|
70
|
+
* instance authenticates with, backed by {@link exchangeAppToken}.
|
|
71
|
+
*
|
|
72
|
+
* Signed out (`parentToken()` yields nothing) returns NO headers, which leaves
|
|
73
|
+
* the scoped instance anonymous - the public read lane - instead of failing. A
|
|
74
|
+
* give-up on the exchange itself rejects, so a broken exchange surfaces as an
|
|
75
|
+
* error rather than as silently unauthenticated reads.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createAppTokenAuthHeaders(options: AppTokenAuthHeadersOptions): () => Promise<Record<string, string>>;
|
|
@@ -14,24 +14,58 @@
|
|
|
14
14
|
* per-subscription override channel, plus the websocket layer already keying
|
|
15
15
|
* connections by appId.
|
|
16
16
|
*
|
|
17
|
-
* ##
|
|
17
|
+
* ## Anonymous by default, authenticated with `getAuthHeaders`
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
* refreshes the global session, and connects to its app's
|
|
21
|
-
* anonymously. It is therefore only useful against collections
|
|
22
|
-
* exposes with a public read rule (`read: true`) - private/per-user
|
|
23
|
-
* be denied by the target app's policy, exactly as an anonymous caller
|
|
19
|
+
* With no `getAuthHeaders`, a scoped instance is UNAUTHENTICATED. It sends no
|
|
20
|
+
* auth, never reads or refreshes the global session, and connects to its app's
|
|
21
|
+
* realtime endpoint anonymously. It is therefore only useful against collections
|
|
22
|
+
* a Bounded app exposes with a public read rule (`read: true`) - private/per-user
|
|
23
|
+
* reads will be denied by the target app's policy, exactly as an anonymous caller
|
|
24
|
+
* would be.
|
|
25
|
+
*
|
|
26
|
+
* Supply `getAuthHeaders` and the instance authenticates AS THAT CREDENTIAL, on
|
|
27
|
+
* both transports, with nothing shared with any other principal:
|
|
28
|
+
* - HTTP sends exactly the headers the provider returns, alongside this
|
|
29
|
+
* instance's `X-App-Id`. The provider is the SOLE auth source: the ambient
|
|
30
|
+
* global session is neither read nor refreshed nor cleared, and the read
|
|
31
|
+
* cache is keyed by the provider's own opaque credential.
|
|
32
|
+
* - The websocket gets its OWN connection, keyed by that credential
|
|
33
|
+
* (`principalKey`/`principalToken`), so it never shares a socket, an auth
|
|
34
|
+
* frame or cached data with the global client or with an anonymous scoped
|
|
35
|
+
* instance for the same appId.
|
|
36
|
+
* - No per-instance session is ever persisted. The global session store (which
|
|
37
|
+
* deletes a stored session whose appId differs from the global config's) is
|
|
38
|
+
* only reachable through the global session manager, and a per-instance
|
|
39
|
+
* provider never calls into it.
|
|
40
|
+
* - A provider that returns no credential leaves the instance ANONYMOUS for
|
|
41
|
+
* that call rather than failing it, so a signed-out page degrades to the
|
|
42
|
+
* public read lane. An already-authenticated socket is never silently
|
|
43
|
+
* downgraded: it keeps failing closed on a credential that stops resolving.
|
|
24
44
|
*
|
|
25
45
|
* Writes and any auth-requiring surface are NOT supported on a scoped instance
|
|
26
|
-
* and throw a clear error
|
|
46
|
+
* and throw a clear error, with or without a credential.
|
|
27
47
|
*/
|
|
28
48
|
import { type GetOptions, type SearchOptions, type CountOptions, type AggregateOptions, type QueryAggregateOptions, type RunQueryOptions, type AggregateOperation, type AggregateResult, type AggregateRow, type AggregateSpec, type GetManyResult, type RequestOverrides } from './operations';
|
|
29
49
|
import { type ClientConfig } from './config';
|
|
30
50
|
import type { SubscriptionOptions } from '../types';
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the auth headers a scoped instance authenticates with. Called per
|
|
53
|
+
* request and per subscribe, so a rotated credential is picked up without
|
|
54
|
+
* recreating the client.
|
|
55
|
+
*
|
|
56
|
+
* Return `{ Authorization: 'Bearer <idToken>' }` for the app this instance is
|
|
57
|
+
* bound to - typically from `createAppTokenAuthHeaders`, which exchanges the
|
|
58
|
+
* signed-in parent app's session for a child-app token. Return `{}` (or null)
|
|
59
|
+
* when there is no credential: the instance stays anonymous for that call.
|
|
60
|
+
*/
|
|
61
|
+
export type ScopedAuthHeadersProvider = () => Record<string, string> | null | undefined | Promise<Record<string, string> | null | undefined>;
|
|
31
62
|
/** Config accepted by {@link createClient}. Only `appId` is required; `network`
|
|
32
|
-
* (or explicit endpoint fields) selects the target Bounded backend.
|
|
63
|
+
* (or explicit endpoint fields) selects the target Bounded backend. Supply
|
|
64
|
+
* `getAuthHeaders` to make the instance authenticated instead of anonymous. */
|
|
33
65
|
export type CreateClientConfig = Partial<ClientConfig> & {
|
|
34
66
|
appId: string;
|
|
67
|
+
/** Per-instance credential source. See {@link ScopedAuthHeadersProvider}. */
|
|
68
|
+
getAuthHeaders?: ScopedAuthHeadersProvider;
|
|
35
69
|
};
|
|
36
70
|
/**
|
|
37
71
|
* A read-only client scoped to a single Bounded app, isolated from the global
|
|
@@ -40,6 +74,8 @@ export type CreateClientConfig = Partial<ClientConfig> & {
|
|
|
40
74
|
export interface BoundedClient {
|
|
41
75
|
/** The app id this instance is bound to. */
|
|
42
76
|
readonly appId: string;
|
|
77
|
+
/** True when this instance was given a `getAuthHeaders` credential source. */
|
|
78
|
+
readonly authenticated: boolean;
|
|
43
79
|
/** A snapshot of this instance's fully-resolved config (endpoints + appId). */
|
|
44
80
|
getConfig(): ClientConfig;
|
|
45
81
|
/** Read a document or collection. See the global `get`. */
|
|
@@ -68,36 +104,52 @@ export interface BoundedClient {
|
|
|
68
104
|
}[], opts?: RunQueryOptions): Promise<any[]>;
|
|
69
105
|
/**
|
|
70
106
|
* Subscribe to realtime updates for a path on THIS app's realtime endpoint.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
107
|
+
* Authenticated when the instance has a credential (its own socket, keyed by
|
|
108
|
+
* that credential), anonymous otherwise - in which case the collection must be
|
|
109
|
+
* publicly readable. Returns an unsubscribe function. Subscriptions on this
|
|
110
|
+
* instance are fully independent of the global client and of any other scoped
|
|
111
|
+
* instance.
|
|
74
112
|
*/
|
|
75
113
|
subscribe(path: string, options: SubscriptionOptions | ((data: any) => void)): Promise<() => Promise<void>>;
|
|
76
|
-
/** Not supported on a
|
|
114
|
+
/** Not supported on a scoped instance - always throws {@link ScopedClientReadOnlyError}. */
|
|
77
115
|
set(path: string, document: any): Promise<never>;
|
|
78
|
-
/** Not supported on a
|
|
116
|
+
/** Not supported on a scoped instance - always throws {@link ScopedClientReadOnlyError}. */
|
|
79
117
|
setMany(many: {
|
|
80
118
|
path: string;
|
|
81
119
|
document: any;
|
|
82
120
|
}[]): Promise<never>;
|
|
83
121
|
}
|
|
84
|
-
/** Error thrown when an unsupported (write
|
|
85
|
-
*
|
|
122
|
+
/** Error thrown when an unsupported (write) call is made on a scoped client.
|
|
123
|
+
* Reads may be authenticated per instance; writes still are not supported. */
|
|
86
124
|
export declare class ScopedClientReadOnlyError extends Error {
|
|
87
125
|
constructor(operation: string);
|
|
88
126
|
}
|
|
89
127
|
/**
|
|
90
|
-
* Create a read-only client scoped to a single Bounded app
|
|
128
|
+
* Create a read-only client scoped to a single Bounded app, anonymous by default
|
|
129
|
+
* and authenticated when given `getAuthHeaders`.
|
|
91
130
|
*
|
|
92
131
|
* @example
|
|
93
132
|
* ```ts
|
|
94
133
|
* // Global client stays the venue's own authenticated app:
|
|
95
134
|
* await init({ appId: VENUE_APP, network: 'bounded-production', authMethod: 'email' });
|
|
96
135
|
*
|
|
97
|
-
* // A second,
|
|
136
|
+
* // A second, anonymous client for the steward runtime app's public collections:
|
|
98
137
|
* const steward = createClient({ appId: STEWARD_APP, network: 'bounded-production' });
|
|
99
138
|
* const state = await steward.get('runtime/current');
|
|
100
139
|
* const stop = await steward.subscribe('runtime/current', (d) => render(d));
|
|
140
|
+
*
|
|
141
|
+
* // The same second app, read AS THE SIGNED-IN HUMAN: the venue session is
|
|
142
|
+
* // exchanged for a token pinned to the child app (see createAppTokenAuthHeaders).
|
|
143
|
+
* const mine = createClient({
|
|
144
|
+
* appId: ROOT_APP,
|
|
145
|
+
* network: 'bounded-production',
|
|
146
|
+
* getAuthHeaders: createAppTokenAuthHeaders({
|
|
147
|
+
* authApiUrl: HUMAN_AUTH_URL,
|
|
148
|
+
* appId: ROOT_APP,
|
|
149
|
+
* parentToken: () => getIdToken(),
|
|
150
|
+
* }),
|
|
151
|
+
* });
|
|
152
|
+
* const stopChat = await mine.subscribe(`conversations/${id}/m`, (d) => render(d));
|
|
101
153
|
* ```
|
|
102
154
|
*/
|
|
103
155
|
export declare function createClient(config: CreateClientConfig): BoundedClient;
|
|
@@ -5,6 +5,29 @@
|
|
|
5
5
|
* It maintains the same external API as v1 but uses the new v2 protocol internally.
|
|
6
6
|
*/
|
|
7
7
|
import { SubscriptionOptions } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Live subscriptions the SDK puts on ONE socket before opening another.
|
|
10
|
+
*
|
|
11
|
+
* The realtime worker refuses the 51st distinct subscribe on a connection with
|
|
12
|
+
* `{ code: 'subscription_limit', status: 429 }` - see
|
|
13
|
+
* `packages/cdk/cloudflare/realtime-worker/src/subscriptions.ts`
|
|
14
|
+
* (`attachment.subscriptions.length >= 50`). The cap is structural there: every
|
|
15
|
+
* subscription spec has to fit the 16KB Durable Object hibernation attachment,
|
|
16
|
+
* and each write fans out across the whole set of one socket.
|
|
17
|
+
*
|
|
18
|
+
* 45 leaves deliberate headroom under that 50: two concurrent subscribeV2 calls
|
|
19
|
+
* can both see room and overshoot by a frame or two, and another tab may already
|
|
20
|
+
* hold subscriptions on the same DO. A refusal that happens anyway is treated as
|
|
21
|
+
* backpressure, not a failure (see rehomeRefusedSubscription).
|
|
22
|
+
*/
|
|
23
|
+
export declare const SHARD_CAP = 45;
|
|
24
|
+
/**
|
|
25
|
+
* Sockets one routing target (app, room, or wallet identity) may hold. Past this
|
|
26
|
+
* a subscribe fails loudly with `subscription_limit` instead of opening
|
|
27
|
+
* connections without end - 8 * 45 = 360 concurrent live subscriptions per
|
|
28
|
+
* target, far beyond any legitimate UI.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MAX_SHARDS_PER_KEY = 8;
|
|
8
31
|
/**
|
|
9
32
|
* Subscribe to data at a path using WebSocket v2.
|
|
10
33
|
* Returns an unsubscribe function.
|