@nanoflags/sdk 0.3.0 → 0.4.0
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 +47 -18
- package/dist/{index-DZj6xbyg.d.cts → index-D5qa8ji-.d.cts} +39 -9
- package/dist/{index-DZj6xbyg.d.ts → index-D5qa8ji-.d.ts} +39 -9
- package/dist/index.cjs +5 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/node.cjs +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/{src-B58PLGGE.cjs → src-CVTvEKbT.js} +109 -59
- package/dist/{src-DUF_NkBY.js → src-IVd-Wps4.cjs} +168 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,11 @@ learn about new revisions, and to send sampled telemetry. An accessor never perf
|
|
|
10
10
|
- Entry points: `@nanoflags/sdk` (Node, browsers, edge runtimes), `@nanoflags/sdk/react`, and
|
|
11
11
|
`@nanoflags/sdk/node` (test-session propagation with `AsyncLocalStorage`).
|
|
12
12
|
- v2 adds [signature verification](#signature-verification) and [test sessions](#test-sessions).
|
|
13
|
-
|
|
13
|
+
- **0.4** verifies signatures **by default** when `baseUrl` is the hosted edge (`https://edge.nanoflags.io`), with
|
|
14
|
+
the production public keys embedded in the SDK, and refuses unsigned bundles there (see
|
|
15
|
+
[Signature verification](#signature-verification)). It also refuses bundles produced for a newer evaluation engine
|
|
16
|
+
(`bundle.engine`, see [Engine version](#engine-version)) and stops reconnecting when the edge reports that the
|
|
17
|
+
environment was deleted. Self-hosted edges (any other `baseUrl`) behave as in 0.3 unless `verify` is set.
|
|
14
18
|
|
|
15
19
|
## Install
|
|
16
20
|
|
|
@@ -107,7 +111,7 @@ createClient({
|
|
|
107
111
|
bootstrap?: Bundle,
|
|
108
112
|
store?: { get(): Promise<string | null>; set(v: string): Promise<void> } | false,
|
|
109
113
|
events?: { enabled?: boolean /* true */, sampleRate?: number /* 0.1 */, flushIntervalMs?: number /* 10000 */, reportIntervalMs?: number /* 60000 */, redact?: (ctx) => object } | false,
|
|
110
|
-
verify?: { publicKeys
|
|
114
|
+
verify?: { publicKeys?: JsonWebKey[] | "fetch", require?: boolean } | false, // default: see Signature verification
|
|
111
115
|
fetch?: typeof fetch,
|
|
112
116
|
})
|
|
113
117
|
```
|
|
@@ -122,7 +126,7 @@ createClient({
|
|
|
122
126
|
| `evaluate(key, fallback, ctx?)` | The engine `Evaluation`: `value`, `reason`, `ruleIndex`, `ruleId`, `inRollout`, `bucket`, `revision`, `trace`. No type check. |
|
|
123
127
|
| `session(token, { service? })` | `Promise<SessionEvaluator>` for a test session. Never rejects. See [Test sessions](#test-sessions). |
|
|
124
128
|
| `useSession(token \| null)` / `sessionHeaders()` | Browser only: join or leave a test session for this page session; headers for downstream calls. |
|
|
125
|
-
| `on("update" \| "error", cb)` | Returns an unsubscribe function. `update` receives `{ revision, previousRevision, checksum, source, transport, latencyMs }` (see [Updates](#updates)). `error` receives a `NanoFlagsError` with `code`: `network`, `unauthorized`, `http`, `invalid_bundle`, `store`, `config`, `signature`, `session`. |
|
|
129
|
+
| `on("update" \| "error", cb)` | Returns an unsubscribe function. `update` receives `{ revision, previousRevision, checksum, source, transport, latencyMs }` (see [Updates](#updates)). `error` receives a `NanoFlagsError` with `code`: `network`, `unauthorized`, `http`, `invalid_bundle`, `store`, `config`, `signature`, `session`, `engine` (bundle for a newer engine, refused), `environment_deleted`. |
|
|
126
130
|
| `refresh()` / `flush()` / `close()` | Revalidate now / send queued telemetry now / stop all background work. |
|
|
127
131
|
|
|
128
132
|
## Fallbacks and staleness
|
|
@@ -149,7 +153,9 @@ A **disabled** flag is not a fallback case: it returns the flag's configured `of
|
|
|
149
153
|
that bundle; the SDK keeps retrying (after a failure: 1 s, 2 s, 4 s … up to the poll interval).
|
|
150
154
|
|
|
151
155
|
The SDK never discards a working bundle because of an error, and never replaces a bundle with an older
|
|
152
|
-
one served as last-known-good. If
|
|
156
|
+
one served as last-known-good. If the environment is deleted, the edge closes the live socket with code `4004`: the client reports
|
|
157
|
+
`error` (`code: "environment_deleted"`), turns `stale`, keeps evaluating the last bundle and stops reconnecting
|
|
158
|
+
(only the regular poll continues, which then reports the key as rejected). If a key is revoked, the client reports `error` (`code: "unauthorized"`)
|
|
153
159
|
and keeps evaluating the last bundle as `stale` — revocation stops updates, it does not erase
|
|
154
160
|
configuration that already reached a process.
|
|
155
161
|
|
|
@@ -194,8 +200,17 @@ carries the signature, key id and environment id). A push that does not verify c
|
|
|
194
200
|
|
|
195
201
|
## Signature verification
|
|
196
202
|
|
|
197
|
-
Published bundles are signed by the NanoFlags publisher with Ed25519.
|
|
198
|
-
|
|
203
|
+
Published bundles are signed by the NanoFlags publisher with Ed25519. The SDK checks the signature **before** a new
|
|
204
|
+
bundle replaces the one in use.
|
|
205
|
+
|
|
206
|
+
**Default (0.4):**
|
|
207
|
+
|
|
208
|
+
| `baseUrl` | `verify` not set | |
|
|
209
|
+
| --- | --- | --- |
|
|
210
|
+
| `https://edge.nanoflags.io` | verified against the embedded production keys (`OFFICIAL_SIGNING_KEYS`), `require: true` | pushed (WebSocket) and fetched bundles must be signed and verify |
|
|
211
|
+
| any other edge | not verified (as in 0.3) | configure `verify` for a self-hosted edge |
|
|
212
|
+
|
|
213
|
+
`verify: false` turns verification off explicitly. To configure it yourself:
|
|
199
214
|
|
|
200
215
|
```ts
|
|
201
216
|
const flags = createClient({
|
|
@@ -207,34 +222,48 @@ const flags = createClient({
|
|
|
207
222
|
});
|
|
208
223
|
```
|
|
209
224
|
|
|
210
|
-
- A response with `X-NanoFlags-Signed: true` is verified against the canonical string
|
|
225
|
+
- A response with `X-NanoFlags-Signed: true` (and every pushed bundle) is verified against the canonical string
|
|
211
226
|
`nanoflags-bundle-v1\n<environment id>\n<revision>\n<checksum>\n<sha256 hex of the bundle JSON text>`
|
|
212
|
-
(the bundle text exactly as the edge sent it), using WebCrypto
|
|
227
|
+
(the bundle text exactly as the edge sent it), using WebCrypto, with the key whose `kid` the edge names
|
|
228
|
+
(`X-NanoFlags-Key-Id`, or `kid` in the push).
|
|
213
229
|
- **Bad signature → the update is rejected.** The previous bundle stays in use, `status` does not
|
|
214
230
|
change, and `error` fires with `code: "signature"`. The SDK retries on its normal schedule.
|
|
215
231
|
- **Unsigned bundles** (the edge sends `X-NanoFlags-Signed: false` while the publisher is catching up,
|
|
216
|
-
typically for under a minute after a publish) are
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
222
|
-
and
|
|
223
|
-
|
|
232
|
+
typically for well under a minute after a publish) are rejected when `require` is true — the default on the hosted
|
|
233
|
+
edge: the bundle in use stays, `error` (`signature`) fires, and the SDK fetches again with a short backoff (1 s,
|
|
234
|
+
2 s, 4 s … up to the poll interval) until the signed revision is there. With `require: false` (the default for
|
|
235
|
+
other edges) they are accepted; then someone who can alter responses can also strip the signature.
|
|
236
|
+
- **Keys and rotation.** Keys are selected by `kid` and cached. With the embedded keys, a bundle signed with a key id
|
|
237
|
+
the SDK does not know (the hosted edge rotated its key after this SDK version was built) makes the SDK load
|
|
238
|
+
`GET /v1/keys` once and verify against those keys as well; an id that is still unknown is rejected, and the reload
|
|
239
|
+
is not repeated within a minute. `publicKeys: "fetch"` loads all keys from `GET /v1/keys` (again when an unknown
|
|
240
|
+
key id appears, at most once a minute) — this detects corruption and tampering in storage and caches, but fetched
|
|
241
|
+
keys are only as trustworthy as the edge that served them. Explicitly pinned `publicKeys` are never extended from
|
|
242
|
+
the network: **pin the keys in your build** if the edge itself is in your threat model, and add the new key before
|
|
243
|
+
a rotation (the edge lists the old key as `status: "retiring"` during the overlap).
|
|
224
244
|
- After the first verified bundle the client remembers the environment id and rejects bundles signed
|
|
225
245
|
for a different environment.
|
|
226
246
|
- A persisted bundle (see below) is stored with its signature and verified again when restored; with
|
|
227
247
|
`require`, an unsigned stored copy is ignored. A `bootstrap` bundle is trusted as given.
|
|
228
248
|
With `publicKeys: "fetch"` a stored copy can only be verified once the keys were loaded, so an
|
|
229
|
-
offline start does not restore it; pinned keys do not have this limitation.
|
|
249
|
+
offline start does not restore it; pinned and embedded keys do not have this limitation.
|
|
230
250
|
- Only public keys are accepted. A JWK with a private part (`d`) is ignored and reported as a `config`
|
|
231
251
|
error. Private signing keys and admin keys never belong in an application.
|
|
232
252
|
- Requires Ed25519 in WebCrypto: Node 20+, current Chrome, Firefox and Safari, Cloudflare Workers,
|
|
233
|
-
Deno, Bun. Where it is missing, signed bundles are rejected (they cannot be checked)
|
|
253
|
+
Deno, Bun. Where it is missing, signed bundles are rejected (they cannot be checked) — on the hosted edge pass
|
|
254
|
+
`verify: false` for such runtimes (Node 18) if you accept that.
|
|
234
255
|
|
|
235
256
|
Not covered: test-session bundles (`GET /v1/session`) are not signed, and verification says nothing
|
|
236
257
|
about *freshness* — it does not stop an attacker from withholding updates.
|
|
237
258
|
|
|
259
|
+
## Engine version
|
|
260
|
+
|
|
261
|
+
Bundles produced by the publisher carry `engine`, the version of the evaluation engine they were produced for
|
|
262
|
+
(`1` today; `SUPPORTED_ENGINE_VERSION` is what this SDK evaluates). A bundle with a **newer** `engine` — a rule format
|
|
263
|
+
this SDK does not understand — is refused wherever it comes from (fetch, push, store, `bootstrap`): the bundle in use
|
|
264
|
+
stays, `status` does not change, and `error` fires with `code: "engine"`. Upgrade the SDK to receive it. Bundles
|
|
265
|
+
without the field (older revisions, the edge's unsigned fallback) are evaluated as engine 1.
|
|
266
|
+
|
|
238
267
|
## Test sessions
|
|
239
268
|
|
|
240
269
|
A test session pins a configuration snapshot and a synthetic context to a token (`nf_ts_…`, created in
|
|
@@ -51,6 +51,11 @@ interface Segment {
|
|
|
51
51
|
}
|
|
52
52
|
interface Bundle {
|
|
53
53
|
schema: 1;
|
|
54
|
+
/**
|
|
55
|
+
* Engine version the bundle was produced for (`ENGINE_VERSION` of the publisher). Absent in bundles from before it
|
|
56
|
+
* existed (treated as 1). SDKs refuse a bundle whose `engine` is newer than the engine they ship.
|
|
57
|
+
*/
|
|
58
|
+
engine?: number;
|
|
54
59
|
project: string;
|
|
55
60
|
environment: string;
|
|
56
61
|
/** Published revision number. 0 means "draft / never published". */
|
|
@@ -98,14 +103,35 @@ interface Store {
|
|
|
98
103
|
interface VerifyOptions {
|
|
99
104
|
/**
|
|
100
105
|
* Public Ed25519 keys as JWK (`{ kty: "OKP", crv: "Ed25519", x, kid? }`), or `"fetch"` to load them
|
|
101
|
-
* once from `GET /v1/keys` on the configured edge
|
|
102
|
-
* `"fetch"`: fetched keys are only as trustworthy as the
|
|
106
|
+
* once from `GET /v1/keys` on the configured edge (again, at most once a minute, when a bundle names an unknown
|
|
107
|
+
* key id). Pinning keys in your build is stronger than `"fetch"`: fetched keys are only as trustworthy as the
|
|
108
|
+
* connection they arrived over. Omitted: the embedded production keys for `https://edge.nanoflags.io`, `"fetch"`
|
|
109
|
+
* for any other edge.
|
|
110
|
+
*/
|
|
111
|
+
publicKeys?: readonly JsonWebKey[] | "fetch";
|
|
112
|
+
/**
|
|
113
|
+
* Also reject bundles that carry no signature (the edge's unsigned fallback, `X-NanoFlags-Signed: false`): the SDK
|
|
114
|
+
* keeps its bundle and fetches again shortly instead. Default true for `https://edge.nanoflags.io`, false otherwise.
|
|
103
115
|
*/
|
|
104
|
-
publicKeys: JsonWebKey[] | "fetch";
|
|
105
|
-
/** Also reject bundles that carry no signature. Default false. */
|
|
106
116
|
require?: boolean;
|
|
107
117
|
}
|
|
108
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The hosted NanoFlags edge and the public halves of its bundle-signing keys (Ed25519, safe to embed).
|
|
120
|
+
*
|
|
121
|
+
* With `baseUrl` equal to `OFFICIAL_EDGE_URL` the SDK verifies every bundle against these keys by default
|
|
122
|
+
* (`verify.require` defaults to true there). A bundle signed with a key id that is not listed here — a rotation that
|
|
123
|
+
* happened after this SDK version was built — makes the SDK load `GET /v1/keys` from the edge once and verify against
|
|
124
|
+
* the keys it lists as well. Rotation procedure: RUNTIME.md, "Signing-key rotation".
|
|
125
|
+
*/
|
|
126
|
+
declare const OFFICIAL_EDGE_URL = "https://edge.nanoflags.io";
|
|
127
|
+
declare const OFFICIAL_SIGNING_KEYS: readonly (JsonWebKey & {
|
|
128
|
+
kid: string;
|
|
129
|
+
})[];
|
|
130
|
+
declare const SDK_VERSION = "0.4.0";
|
|
131
|
+
/** Newest bundle format (`bundle.engine`) this SDK can evaluate. Newer bundles are refused; the previous one stays in use. */
|
|
132
|
+
declare const SUPPORTED_ENGINE_VERSION = 1;
|
|
133
|
+
/** WebSocket close code of the edge for an environment that was deleted: the SDK stops reconnecting. */
|
|
134
|
+
declare const CLOSE_ENVIRONMENT_DELETED = 4004;
|
|
109
135
|
/** Header that carries a test-session token between services. */
|
|
110
136
|
declare const SESSION_HEADER = "x-nanoflags-session";
|
|
111
137
|
type ClientStatus = "not-ready" | "ready" | "stale";
|
|
@@ -173,8 +199,12 @@ interface ClientOptions {
|
|
|
173
199
|
* Verify bundle signatures before a new bundle replaces the current one. A bundle whose signature
|
|
174
200
|
* does not verify is rejected: the previous bundle stays in use and an `error` (`code: "signature"`)
|
|
175
201
|
* is emitted. Without `require`, bundles the edge marks as unsigned are still accepted.
|
|
202
|
+
*
|
|
203
|
+
* Default: with `baseUrl` = `https://edge.nanoflags.io` the SDK verifies against the embedded production keys
|
|
204
|
+
* (`OFFICIAL_SIGNING_KEYS`, plus `GET /v1/keys` once when a bundle names an unknown key id) with `require: true`;
|
|
205
|
+
* with any other `baseUrl` nothing is verified unless configured. `false` turns verification off explicitly.
|
|
176
206
|
*/
|
|
177
|
-
verify?: VerifyOptions;
|
|
207
|
+
verify?: VerifyOptions | false;
|
|
178
208
|
fetch?: typeof fetch;
|
|
179
209
|
}
|
|
180
210
|
/** The flag accessors shared by the client and by session-scoped evaluators. */
|
|
@@ -262,11 +292,11 @@ interface NanoFlagsClient {
|
|
|
262
292
|
readonly version: number;
|
|
263
293
|
}
|
|
264
294
|
declare class NanoFlagsError extends Error {
|
|
265
|
-
readonly code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session";
|
|
295
|
+
readonly code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session" | "engine" | "environment_deleted";
|
|
266
296
|
readonly httpStatus?: number | undefined;
|
|
267
|
-
constructor(message: string, code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session", httpStatus?: number | undefined);
|
|
297
|
+
constructor(message: string, code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session" | "engine" | "environment_deleted", httpStatus?: number | undefined);
|
|
268
298
|
}
|
|
269
299
|
/** A session token is opaque; it only has to be safe to put in a header. */
|
|
270
300
|
declare function isSessionToken(v: unknown): v is string;
|
|
271
301
|
declare function createClient(options: ClientOptions): NanoFlagsClient;
|
|
272
|
-
export {
|
|
302
|
+
export { Store as C, JsonValue as D, Evaluation as E, Reason as O, VerifyOptions as S, Context as T, WebSocketLike as _, FlagEvaluator as a, OFFICIAL_EDGE_URL as b, NanoFlagsError as c, SUPPORTED_ENGINE_VERSION as d, SessionEvaluator as f, WebSocketConstructor as g, UpdateEvent as h, EventsOptions as i, SDK_VERSION as l, Transport as m, ClientOptions as n, LIVE_PROTOCOL as o, SessionOptions as p, ClientStatus as r, NanoFlagsClient as s, CLOSE_ENVIRONMENT_DELETED as t, SESSION_HEADER as u, createClient as v, Bundle as w, OFFICIAL_SIGNING_KEYS as x, isSessionToken as y };
|
|
@@ -51,6 +51,11 @@ interface Segment {
|
|
|
51
51
|
}
|
|
52
52
|
interface Bundle {
|
|
53
53
|
schema: 1;
|
|
54
|
+
/**
|
|
55
|
+
* Engine version the bundle was produced for (`ENGINE_VERSION` of the publisher). Absent in bundles from before it
|
|
56
|
+
* existed (treated as 1). SDKs refuse a bundle whose `engine` is newer than the engine they ship.
|
|
57
|
+
*/
|
|
58
|
+
engine?: number;
|
|
54
59
|
project: string;
|
|
55
60
|
environment: string;
|
|
56
61
|
/** Published revision number. 0 means "draft / never published". */
|
|
@@ -98,14 +103,35 @@ interface Store {
|
|
|
98
103
|
interface VerifyOptions {
|
|
99
104
|
/**
|
|
100
105
|
* Public Ed25519 keys as JWK (`{ kty: "OKP", crv: "Ed25519", x, kid? }`), or `"fetch"` to load them
|
|
101
|
-
* once from `GET /v1/keys` on the configured edge
|
|
102
|
-
* `"fetch"`: fetched keys are only as trustworthy as the
|
|
106
|
+
* once from `GET /v1/keys` on the configured edge (again, at most once a minute, when a bundle names an unknown
|
|
107
|
+
* key id). Pinning keys in your build is stronger than `"fetch"`: fetched keys are only as trustworthy as the
|
|
108
|
+
* connection they arrived over. Omitted: the embedded production keys for `https://edge.nanoflags.io`, `"fetch"`
|
|
109
|
+
* for any other edge.
|
|
110
|
+
*/
|
|
111
|
+
publicKeys?: readonly JsonWebKey[] | "fetch";
|
|
112
|
+
/**
|
|
113
|
+
* Also reject bundles that carry no signature (the edge's unsigned fallback, `X-NanoFlags-Signed: false`): the SDK
|
|
114
|
+
* keeps its bundle and fetches again shortly instead. Default true for `https://edge.nanoflags.io`, false otherwise.
|
|
103
115
|
*/
|
|
104
|
-
publicKeys: JsonWebKey[] | "fetch";
|
|
105
|
-
/** Also reject bundles that carry no signature. Default false. */
|
|
106
116
|
require?: boolean;
|
|
107
117
|
}
|
|
108
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The hosted NanoFlags edge and the public halves of its bundle-signing keys (Ed25519, safe to embed).
|
|
120
|
+
*
|
|
121
|
+
* With `baseUrl` equal to `OFFICIAL_EDGE_URL` the SDK verifies every bundle against these keys by default
|
|
122
|
+
* (`verify.require` defaults to true there). A bundle signed with a key id that is not listed here — a rotation that
|
|
123
|
+
* happened after this SDK version was built — makes the SDK load `GET /v1/keys` from the edge once and verify against
|
|
124
|
+
* the keys it lists as well. Rotation procedure: RUNTIME.md, "Signing-key rotation".
|
|
125
|
+
*/
|
|
126
|
+
declare const OFFICIAL_EDGE_URL = "https://edge.nanoflags.io";
|
|
127
|
+
declare const OFFICIAL_SIGNING_KEYS: readonly (JsonWebKey & {
|
|
128
|
+
kid: string;
|
|
129
|
+
})[];
|
|
130
|
+
declare const SDK_VERSION = "0.4.0";
|
|
131
|
+
/** Newest bundle format (`bundle.engine`) this SDK can evaluate. Newer bundles are refused; the previous one stays in use. */
|
|
132
|
+
declare const SUPPORTED_ENGINE_VERSION = 1;
|
|
133
|
+
/** WebSocket close code of the edge for an environment that was deleted: the SDK stops reconnecting. */
|
|
134
|
+
declare const CLOSE_ENVIRONMENT_DELETED = 4004;
|
|
109
135
|
/** Header that carries a test-session token between services. */
|
|
110
136
|
declare const SESSION_HEADER = "x-nanoflags-session";
|
|
111
137
|
type ClientStatus = "not-ready" | "ready" | "stale";
|
|
@@ -173,8 +199,12 @@ interface ClientOptions {
|
|
|
173
199
|
* Verify bundle signatures before a new bundle replaces the current one. A bundle whose signature
|
|
174
200
|
* does not verify is rejected: the previous bundle stays in use and an `error` (`code: "signature"`)
|
|
175
201
|
* is emitted. Without `require`, bundles the edge marks as unsigned are still accepted.
|
|
202
|
+
*
|
|
203
|
+
* Default: with `baseUrl` = `https://edge.nanoflags.io` the SDK verifies against the embedded production keys
|
|
204
|
+
* (`OFFICIAL_SIGNING_KEYS`, plus `GET /v1/keys` once when a bundle names an unknown key id) with `require: true`;
|
|
205
|
+
* with any other `baseUrl` nothing is verified unless configured. `false` turns verification off explicitly.
|
|
176
206
|
*/
|
|
177
|
-
verify?: VerifyOptions;
|
|
207
|
+
verify?: VerifyOptions | false;
|
|
178
208
|
fetch?: typeof fetch;
|
|
179
209
|
}
|
|
180
210
|
/** The flag accessors shared by the client and by session-scoped evaluators. */
|
|
@@ -262,11 +292,11 @@ interface NanoFlagsClient {
|
|
|
262
292
|
readonly version: number;
|
|
263
293
|
}
|
|
264
294
|
declare class NanoFlagsError extends Error {
|
|
265
|
-
readonly code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session";
|
|
295
|
+
readonly code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session" | "engine" | "environment_deleted";
|
|
266
296
|
readonly httpStatus?: number | undefined;
|
|
267
|
-
constructor(message: string, code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session", httpStatus?: number | undefined);
|
|
297
|
+
constructor(message: string, code: "network" | "unauthorized" | "http" | "invalid_bundle" | "store" | "config" | "signature" | "session" | "engine" | "environment_deleted", httpStatus?: number | undefined);
|
|
268
298
|
}
|
|
269
299
|
/** A session token is opaque; it only has to be safe to put in a header. */
|
|
270
300
|
declare function isSessionToken(v: unknown): v is string;
|
|
271
301
|
declare function createClient(options: ClientOptions): NanoFlagsClient;
|
|
272
|
-
export {
|
|
302
|
+
export { Store as C, JsonValue as D, Evaluation as E, Reason as O, VerifyOptions as S, Context as T, WebSocketLike as _, FlagEvaluator as a, OFFICIAL_EDGE_URL as b, NanoFlagsError as c, SUPPORTED_ENGINE_VERSION as d, SessionEvaluator as f, WebSocketConstructor as g, UpdateEvent as h, EventsOptions as i, SDK_VERSION as l, Transport as m, ClientOptions as n, LIVE_PROTOCOL as o, SessionOptions as p, ClientStatus as r, NanoFlagsClient as s, CLOSE_ENVIRONMENT_DELETED as t, SESSION_HEADER as u, createClient as v, Bundle as w, OFFICIAL_SIGNING_KEYS as x, isSessionToken as y };
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("./src-
|
|
2
|
+
const require_src = require("./src-IVd-Wps4.cjs");
|
|
3
|
+
exports.CLOSE_ENVIRONMENT_DELETED = require_src.CLOSE_ENVIRONMENT_DELETED;
|
|
3
4
|
exports.LIVE_PROTOCOL = require_src.LIVE_PROTOCOL;
|
|
4
5
|
exports.NanoFlagsError = require_src.NanoFlagsError;
|
|
6
|
+
exports.OFFICIAL_EDGE_URL = require_src.OFFICIAL_EDGE_URL;
|
|
7
|
+
exports.OFFICIAL_SIGNING_KEYS = require_src.OFFICIAL_SIGNING_KEYS;
|
|
5
8
|
exports.SDK_VERSION = require_src.SDK_VERSION;
|
|
6
9
|
exports.SESSION_HEADER = require_src.SESSION_HEADER;
|
|
10
|
+
exports.SUPPORTED_ENGINE_VERSION = require_src.SUPPORTED_ENGINE_VERSION;
|
|
7
11
|
exports.createClient = require_src.createClient;
|
|
8
12
|
exports.isSessionToken = require_src.isSessionToken;
|
package/dist/index.d.cts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* the edge pushes the signed bundle itself — no request follows), the SSE stream (`/v1/stream`,
|
|
10
10
|
* a notification followed by a conditional fetch), and polling (`/v1/bundle`).
|
|
11
11
|
*/
|
|
12
|
-
import { C as JsonValue,
|
|
13
|
-
export { type Bundle, ClientOptions, ClientStatus, type Context, type Evaluation, EventsOptions, FlagEvaluator, type JsonValue, LIVE_PROTOCOL, NanoFlagsClient, NanoFlagsError, type Reason, SDK_VERSION, SESSION_HEADER, SessionEvaluator, SessionOptions, type Store, Transport, UpdateEvent, type VerifyOptions, WebSocketConstructor, WebSocketLike, createClient, isSessionToken };
|
|
12
|
+
import { C as Store, D as JsonValue, E as Evaluation, O as Reason, S as VerifyOptions, T as Context, _ as WebSocketLike, a as FlagEvaluator, b as OFFICIAL_EDGE_URL, c as NanoFlagsError, d as SUPPORTED_ENGINE_VERSION, f as SessionEvaluator, g as WebSocketConstructor, h as UpdateEvent, i as EventsOptions, l as SDK_VERSION, m as Transport, n as ClientOptions, o as LIVE_PROTOCOL, p as SessionOptions, r as ClientStatus, s as NanoFlagsClient, t as CLOSE_ENVIRONMENT_DELETED, u as SESSION_HEADER, v as createClient, w as Bundle, x as OFFICIAL_SIGNING_KEYS, y as isSessionToken } from "./index-D5qa8ji-.cjs";
|
|
13
|
+
export { type Bundle, CLOSE_ENVIRONMENT_DELETED, ClientOptions, ClientStatus, type Context, type Evaluation, EventsOptions, FlagEvaluator, type JsonValue, LIVE_PROTOCOL, NanoFlagsClient, NanoFlagsError, OFFICIAL_EDGE_URL, OFFICIAL_SIGNING_KEYS, type Reason, SDK_VERSION, SESSION_HEADER, SUPPORTED_ENGINE_VERSION, SessionEvaluator, SessionOptions, type Store, Transport, UpdateEvent, type VerifyOptions, WebSocketConstructor, WebSocketLike, createClient, isSessionToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* the edge pushes the signed bundle itself — no request follows), the SSE stream (`/v1/stream`,
|
|
10
10
|
* a notification followed by a conditional fetch), and polling (`/v1/bundle`).
|
|
11
11
|
*/
|
|
12
|
-
import { C as JsonValue,
|
|
13
|
-
export { type Bundle, ClientOptions, ClientStatus, type Context, type Evaluation, EventsOptions, FlagEvaluator, type JsonValue, LIVE_PROTOCOL, NanoFlagsClient, NanoFlagsError, type Reason, SDK_VERSION, SESSION_HEADER, SessionEvaluator, SessionOptions, type Store, Transport, UpdateEvent, type VerifyOptions, WebSocketConstructor, WebSocketLike, createClient, isSessionToken };
|
|
12
|
+
import { C as Store, D as JsonValue, E as Evaluation, O as Reason, S as VerifyOptions, T as Context, _ as WebSocketLike, a as FlagEvaluator, b as OFFICIAL_EDGE_URL, c as NanoFlagsError, d as SUPPORTED_ENGINE_VERSION, f as SessionEvaluator, g as WebSocketConstructor, h as UpdateEvent, i as EventsOptions, l as SDK_VERSION, m as Transport, n as ClientOptions, o as LIVE_PROTOCOL, p as SessionOptions, r as ClientStatus, s as NanoFlagsClient, t as CLOSE_ENVIRONMENT_DELETED, u as SESSION_HEADER, v as createClient, w as Bundle, x as OFFICIAL_SIGNING_KEYS, y as isSessionToken } from "./index-D5qa8ji-.js";
|
|
13
|
+
export { type Bundle, CLOSE_ENVIRONMENT_DELETED, ClientOptions, ClientStatus, type Context, type Evaluation, EventsOptions, FlagEvaluator, type JsonValue, LIVE_PROTOCOL, NanoFlagsClient, NanoFlagsError, OFFICIAL_EDGE_URL, OFFICIAL_SIGNING_KEYS, type Reason, SDK_VERSION, SESSION_HEADER, SUPPORTED_ENGINE_VERSION, SessionEvaluator, SessionOptions, type Store, Transport, UpdateEvent, type VerifyOptions, WebSocketConstructor, WebSocketLike, createClient, isSessionToken };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as
|
|
1
|
+
export { t as CLOSE_ENVIRONMENT_DELETED, n as LIVE_PROTOCOL, r as NanoFlagsError, l as OFFICIAL_EDGE_URL, u as OFFICIAL_SIGNING_KEYS, i as SDK_VERSION, a as SESSION_HEADER, o as SUPPORTED_ENGINE_VERSION, s as createClient, c as isSessionToken } from "./src-CVTvEKbT.js";
|
package/dist/node.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("./src-
|
|
2
|
+
const require_src = require("./src-IVd-Wps4.cjs");
|
|
3
3
|
// src/node.ts
|
|
4
4
|
/**
|
|
5
5
|
* @nanoflags/sdk/node — test-session propagation for Node servers.
|
package/dist/node.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as FlagEvaluator, f as SessionEvaluator, p as SessionOptions, s as NanoFlagsClient, u as SESSION_HEADER } from "./index-D5qa8ji-.cjs";
|
|
2
2
|
type SessionClient = Pick<NanoFlagsClient, "session"> & FlagEvaluator & Pick<NanoFlagsClient, "revision">;
|
|
3
3
|
/** Run `fn` with `token` as the current test session. An absent or malformed token runs `fn` outside any session. */
|
|
4
4
|
export declare function withSession<T>(token: string | null | undefined, fn: () => T): T;
|
package/dist/node.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as FlagEvaluator, f as SessionEvaluator, p as SessionOptions, s as NanoFlagsClient, u as SESSION_HEADER } from "./index-D5qa8ji-.js";
|
|
2
2
|
type SessionClient = Pick<NanoFlagsClient, "session"> & FlagEvaluator & Pick<NanoFlagsClient, "revision">;
|
|
3
3
|
/** Run `fn` with `token` as the current test session. An absent or malformed token runs `fn` outside any session. */
|
|
4
4
|
export declare function withSession<T>(token: string | null | undefined, fn: () => T): T;
|
package/dist/node.js
CHANGED
package/dist/react.d.cts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @nanoflags/sdk/react — bindings built on useSyncExternalStore.
|
|
3
3
|
* Components re-render when the bundle or the client status changes; evaluation stays local.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { D as JsonValue, T as Context, r as ClientStatus, s as NanoFlagsClient } from "./index-D5qa8ji-.cjs";
|
|
6
6
|
import { ReactElement, ReactNode } from "react";
|
|
7
7
|
export interface NanoFlagsProviderProps {
|
|
8
8
|
client: NanoFlagsClient;
|
package/dist/react.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @nanoflags/sdk/react — bindings built on useSyncExternalStore.
|
|
3
3
|
* Components re-render when the bundle or the client status changes; evaluation stays local.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { D as JsonValue, T as Context, r as ClientStatus, s as NanoFlagsClient } from "./index-D5qa8ji-.js";
|
|
6
6
|
import { ReactElement, ReactNode } from "react";
|
|
7
7
|
export interface NanoFlagsProviderProps {
|
|
8
8
|
client: NanoFlagsClient;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// ../engine/src/index.ts
|
|
2
1
|
const encoder$1 = new TextEncoder();
|
|
3
2
|
/** MurmurHash3 x86 32-bit over UTF-8 bytes. Used for distribution only — never for security. */
|
|
4
3
|
function murmur3(input, seed = 0) {
|
|
@@ -264,6 +263,27 @@ function evaluate(bundle, flagKey, ctx, fallback) {
|
|
|
264
263
|
function fmt(v) {
|
|
265
264
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
266
265
|
}
|
|
266
|
+
/** True when this engine can evaluate `bundle` (its `engine` is absent or not newer than `ENGINE_VERSION`). */
|
|
267
|
+
function supportsEngine(bundle) {
|
|
268
|
+
const e = bundle.engine;
|
|
269
|
+
return e === void 0 || typeof e === "number" && Number.isInteger(e) && e >= 1 && e <= 1;
|
|
270
|
+
}
|
|
271
|
+
// src/keys.ts
|
|
272
|
+
/**
|
|
273
|
+
* The hosted NanoFlags edge and the public halves of its bundle-signing keys (Ed25519, safe to embed).
|
|
274
|
+
*
|
|
275
|
+
* With `baseUrl` equal to `OFFICIAL_EDGE_URL` the SDK verifies every bundle against these keys by default
|
|
276
|
+
* (`verify.require` defaults to true there). A bundle signed with a key id that is not listed here — a rotation that
|
|
277
|
+
* happened after this SDK version was built — makes the SDK load `GET /v1/keys` from the edge once and verify against
|
|
278
|
+
* the keys it lists as well. Rotation procedure: RUNTIME.md, "Signing-key rotation".
|
|
279
|
+
*/
|
|
280
|
+
const OFFICIAL_EDGE_URL = "https://edge.nanoflags.io";
|
|
281
|
+
const OFFICIAL_SIGNING_KEYS = Object.freeze([Object.freeze({
|
|
282
|
+
kty: "OKP",
|
|
283
|
+
crv: "Ed25519",
|
|
284
|
+
x: "wQu1EsaE-yTjqpUbibGyBrajy3IiRb7LyaiqeYRxG4U",
|
|
285
|
+
kid: "k1"
|
|
286
|
+
})]);
|
|
267
287
|
// src/store.ts
|
|
268
288
|
const DB = "nanoflags";
|
|
269
289
|
const OBJECT_STORE = "bundles";
|
|
@@ -496,7 +516,11 @@ function rawMember(text, member) {
|
|
|
496
516
|
* the edge pushes the signed bundle itself — no request follows), the SSE stream (`/v1/stream`,
|
|
497
517
|
* a notification followed by a conditional fetch), and polling (`/v1/bundle`).
|
|
498
518
|
*/
|
|
499
|
-
const SDK_VERSION = "0.
|
|
519
|
+
const SDK_VERSION = "0.4.0";
|
|
520
|
+
/** Newest bundle format (`bundle.engine`) this SDK can evaluate. Newer bundles are refused; the previous one stays in use. */
|
|
521
|
+
const SUPPORTED_ENGINE_VERSION = 1;
|
|
522
|
+
/** WebSocket close code of the edge for an environment that was deleted: the SDK stops reconnecting. */
|
|
523
|
+
const CLOSE_ENVIRONMENT_DELETED = 4004;
|
|
500
524
|
/** Header that carries a test-session token between services. */
|
|
501
525
|
const SESSION_HEADER = "x-nanoflags-session";
|
|
502
526
|
/** WebSocket subprotocol of `/v1/live`. The key travels as a second protocol entry, never in the URL. */
|
|
@@ -539,6 +563,9 @@ function isBundle(v) {
|
|
|
539
563
|
const b = v;
|
|
540
564
|
return b.schema === 1 && typeof b.revision === "number" && typeof b.flags === "object" && b.flags !== null && !Array.isArray(b.flags) && typeof b.segments === "object" && b.segments !== null;
|
|
541
565
|
}
|
|
566
|
+
function engineError(b) {
|
|
567
|
+
return new NanoFlagsError(`Bundle r${String(b.revision)} needs evaluation engine ${JSON.stringify(b.engine)}; this SDK supports up to 1. Upgrade @nanoflags/sdk. The configuration in use is unchanged.`, "engine");
|
|
568
|
+
}
|
|
542
569
|
function typeOf(v) {
|
|
543
570
|
return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
|
|
544
571
|
}
|
|
@@ -583,15 +610,38 @@ function createClient(options) {
|
|
|
583
610
|
let liveOpen = false;
|
|
584
611
|
let liveAttempt = 0;
|
|
585
612
|
let liveTimer = null;
|
|
613
|
+
/** The edge closed the socket with 4004: the environment was deleted. No more reconnects. */
|
|
614
|
+
let environmentDeleted = false;
|
|
586
615
|
/** Highest revision adopted from a push: a slower HTTP answer must never replace it with something older. */
|
|
587
616
|
let liveRevision = 0;
|
|
588
617
|
let pushChain = Promise.resolve();
|
|
589
618
|
let lastReportAt = 0;
|
|
590
619
|
const usedAttrs = /* @__PURE__ */ new WeakMap();
|
|
591
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Effective verification: `keys` to verify with (`"fetch"` = only `/v1/keys`), `require` = refuse unsigned bundles,
|
|
622
|
+
* `refetch` = an unknown key id loads `/v1/keys` once (official edge with the embedded keys, or `"fetch"`).
|
|
623
|
+
*/
|
|
624
|
+
const official = baseUrl === OFFICIAL_EDGE_URL;
|
|
625
|
+
const verifyOpt = (() => {
|
|
626
|
+
const v = options.verify;
|
|
627
|
+
if (v === false) return null;
|
|
628
|
+
if (v === void 0 || v === null) return official ? {
|
|
629
|
+
keys: OFFICIAL_SIGNING_KEYS,
|
|
630
|
+
require: true,
|
|
631
|
+
refetch: true
|
|
632
|
+
} : null;
|
|
633
|
+
const keys = v.publicKeys === "fetch" ? "fetch" : Array.isArray(v.publicKeys) ? v.publicKeys : official ? OFFICIAL_SIGNING_KEYS : "fetch";
|
|
634
|
+
return {
|
|
635
|
+
keys,
|
|
636
|
+
require: v.require ?? official,
|
|
637
|
+
refetch: keys === "fetch" || official && v.publicKeys === void 0
|
|
638
|
+
};
|
|
639
|
+
})();
|
|
592
640
|
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
593
|
-
let
|
|
594
|
-
let
|
|
641
|
+
let pinnedKeys = null;
|
|
642
|
+
let fetchedKeys = [];
|
|
643
|
+
let keysFetchedAt = 0;
|
|
644
|
+
let keysFetching = null;
|
|
595
645
|
let pinnedEnvironment = null;
|
|
596
646
|
let sig = null;
|
|
597
647
|
let rawText = null;
|
|
@@ -667,6 +717,7 @@ function createClient(options) {
|
|
|
667
717
|
if (!raw || closed) return;
|
|
668
718
|
const p = JSON.parse(raw);
|
|
669
719
|
if (p.v !== 1 || p.id !== id || !isBundle(p.bundle)) return;
|
|
720
|
+
if (!supportsEngine(p.bundle)) return void emitError(engineError(p.bundle));
|
|
670
721
|
if (bundle && bundle.revision >= p.bundle.revision) return;
|
|
671
722
|
let restored = p.bundle;
|
|
672
723
|
let restoredSig = null;
|
|
@@ -752,20 +803,38 @@ function createClient(options) {
|
|
|
752
803
|
clearTimeout(t);
|
|
753
804
|
}
|
|
754
805
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Keys to verify a signature by `kid` with: the pinned / embedded keys, plus those fetched from `/v1/keys`. With
|
|
808
|
+
* `refetch`, a key id none of them has (rotation) loads `/v1/keys` once — again at most once a minute.
|
|
809
|
+
*/
|
|
810
|
+
async function loadKeys(kid) {
|
|
811
|
+
if (!verifyOpt) return [];
|
|
812
|
+
let base = [];
|
|
813
|
+
if (verifyOpt.keys !== "fetch") {
|
|
814
|
+
const p = pinnedKeys ??= importPublicKeys(verifyOpt.keys);
|
|
761
815
|
p.catch(() => {
|
|
762
|
-
if (
|
|
816
|
+
if (pinnedKeys === p) pinnedKeys = null;
|
|
763
817
|
});
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (verifyOpt.
|
|
768
|
-
|
|
818
|
+
base = await p;
|
|
819
|
+
}
|
|
820
|
+
const all = () => [...base, ...fetchedKeys];
|
|
821
|
+
if (!verifyOpt.refetch) return base;
|
|
822
|
+
const known = all();
|
|
823
|
+
if (known.length > 0 && (kid === null || known.some((k) => k.kid === kid))) return known;
|
|
824
|
+
if (keysFetchedAt !== 0 && Date.now() - keysFetchedAt < KEYS_REFETCH_MS) return known;
|
|
825
|
+
keysFetching ??= fetchKeys().then((keys) => {
|
|
826
|
+
fetchedKeys = keys;
|
|
827
|
+
keysFetchedAt = Date.now();
|
|
828
|
+
}).finally(() => {
|
|
829
|
+
keysFetching = null;
|
|
830
|
+
});
|
|
831
|
+
try {
|
|
832
|
+
await keysFetching;
|
|
833
|
+
} catch (e) {
|
|
834
|
+
if (base.length === 0) throw e;
|
|
835
|
+
keysFetchedAt = Date.now();
|
|
836
|
+
}
|
|
837
|
+
return all();
|
|
769
838
|
}
|
|
770
839
|
/** Returns the bundle to adopt, or throws a NanoFlagsError with code "signature". */
|
|
771
840
|
async function checkSignature(res, text, body) {
|
|
@@ -776,7 +845,7 @@ function createClient(options) {
|
|
|
776
845
|
};
|
|
777
846
|
const reject = (why) => new NanoFlagsError(`Bundle r${body.bundle.revision} was rejected: ${why} The configuration in use is unchanged.`, "signature");
|
|
778
847
|
if (res.headers.get("X-NanoFlags-Signed") !== "true") {
|
|
779
|
-
if (verifyOpt.require) throw reject("it is not signed and `verify.require`
|
|
848
|
+
if (verifyOpt.require) throw reject("it is not signed (the edge is still distributing it) and unsigned bundles are refused (`verify.require`); fetching again shortly.");
|
|
780
849
|
return {
|
|
781
850
|
bundle: body.bundle,
|
|
782
851
|
sig: null,
|
|
@@ -860,6 +929,7 @@ function createClient(options) {
|
|
|
860
929
|
const verified = await checkSignature(res, text, body);
|
|
861
930
|
if (closed) return;
|
|
862
931
|
const next = verified.bundle;
|
|
932
|
+
if (!supportsEngine(next)) throw engineError(next);
|
|
863
933
|
failures = 0;
|
|
864
934
|
if (servedStale && bundle && bundle.revision >= next.revision) {
|
|
865
935
|
setStatus("stale");
|
|
@@ -878,6 +948,7 @@ function createClient(options) {
|
|
|
878
948
|
persist();
|
|
879
949
|
} catch (e) {
|
|
880
950
|
if (closed) return;
|
|
951
|
+
if (e instanceof NanoFlagsError && e.code === "engine") return void emitError(e);
|
|
881
952
|
failures++;
|
|
882
953
|
if (e instanceof NanoFlagsError && e.code === "signature") return void emitError(e);
|
|
883
954
|
if (e instanceof NanoFlagsError && e.code === "unauthorized") dropLive();
|
|
@@ -924,7 +995,7 @@ function createClient(options) {
|
|
|
924
995
|
} catch {}
|
|
925
996
|
}
|
|
926
997
|
function startStream() {
|
|
927
|
-
if (closed || options.streaming === false || !doFetch || streamActive) return;
|
|
998
|
+
if (closed || options.streaming === false || !doFetch || streamActive || environmentDeleted) return;
|
|
928
999
|
streamActive = true;
|
|
929
1000
|
const ES = typeof EventSource === "function" ? EventSource : void 0;
|
|
930
1001
|
if (ES && isBrowserKey) {
|
|
@@ -1023,6 +1094,7 @@ function createClient(options) {
|
|
|
1023
1094
|
parsed = null;
|
|
1024
1095
|
}
|
|
1025
1096
|
if (!isBundle(parsed) || parsed.revision !== revision) return void emitError(new NanoFlagsError("The edge pushed an invalid bundle; it was ignored.", "invalid_bundle"));
|
|
1097
|
+
if (!supportsEngine(parsed)) return void emitError(engineError(parsed));
|
|
1026
1098
|
if (bundle && revision < bundle.revision) return;
|
|
1027
1099
|
failures = 0;
|
|
1028
1100
|
if (bundle && revision === bundle.revision) {
|
|
@@ -1051,7 +1123,7 @@ function createClient(options) {
|
|
|
1051
1123
|
} catch {}
|
|
1052
1124
|
}
|
|
1053
1125
|
function connectLive() {
|
|
1054
|
-
if (closed || !liveWanted || socket) return;
|
|
1126
|
+
if (closed || !liveWanted || socket || environmentDeleted) return;
|
|
1055
1127
|
let ws;
|
|
1056
1128
|
try {
|
|
1057
1129
|
ws = new WS(liveUrl, [LIVE_PROTOCOL, `key.${sdkKey}`]);
|
|
@@ -1121,11 +1193,21 @@ function createClient(options) {
|
|
|
1121
1193
|
if (typeof m !== "object" || m === null || Array.isArray(m)) return;
|
|
1122
1194
|
pushChain = pushChain.then(() => applyPush(m)).catch(() => void 0);
|
|
1123
1195
|
};
|
|
1124
|
-
ws.onclose =
|
|
1196
|
+
ws.onclose = (event) => {
|
|
1197
|
+
if (event?.code === 4004) environmentDeleted = true;
|
|
1198
|
+
end();
|
|
1199
|
+
};
|
|
1125
1200
|
ws.onerror = end;
|
|
1126
1201
|
}
|
|
1127
1202
|
function liveEnded(opened, openMs, rotating) {
|
|
1128
1203
|
if (closed) return;
|
|
1204
|
+
if (environmentDeleted) {
|
|
1205
|
+
stopStream();
|
|
1206
|
+
if (bundle) setStatus("stale");
|
|
1207
|
+
emitError(new NanoFlagsError("The environment of this SDK key was deleted. The last configuration stays in use; it will not be updated.", "environment_deleted"));
|
|
1208
|
+
schedulePoll();
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1129
1211
|
if (rotating) return connectLive();
|
|
1130
1212
|
liveAttempt = opened && openMs > 3e4 ? 1 : liveAttempt + 1;
|
|
1131
1213
|
if (!opened) startStream();
|
|
@@ -1439,7 +1521,10 @@ function createClient(options) {
|
|
|
1439
1521
|
}
|
|
1440
1522
|
};
|
|
1441
1523
|
if (options.bootstrap !== void 0) {
|
|
1442
|
-
if (isBundle(options.bootstrap)
|
|
1524
|
+
if (isBundle(options.bootstrap) && !supportsEngine(options.bootstrap)) {
|
|
1525
|
+
const err = engineError(options.bootstrap);
|
|
1526
|
+
queueMicrotask(() => emitError(err));
|
|
1527
|
+
} else if (isBundle(options.bootstrap)) adopt(options.bootstrap, {
|
|
1443
1528
|
etag: null,
|
|
1444
1529
|
checksum: null,
|
|
1445
1530
|
status: "ready",
|
|
@@ -1447,7 +1532,7 @@ function createClient(options) {
|
|
|
1447
1532
|
});
|
|
1448
1533
|
else queueMicrotask(() => emitError(new NanoFlagsError("The bootstrap bundle is invalid and was ignored.", "invalid_bundle")));
|
|
1449
1534
|
}
|
|
1450
|
-
if (verifyOpt && Array.isArray(verifyOpt.
|
|
1535
|
+
if (verifyOpt && Array.isArray(verifyOpt.keys) && verifyOpt.keys.some((k) => !isPublicEd25519Jwk(k))) queueMicrotask(() => emitError(new NanoFlagsError("`verify.publicKeys` must contain public Ed25519 JWKs only. Other entries (including private keys) were ignored.", "config")));
|
|
1451
1536
|
if (typeof sdkKey !== "string" || sdkKey === "" || baseUrl === "") {
|
|
1452
1537
|
queueMicrotask(() => emitError(new NanoFlagsError("`sdkKey` and `baseUrl` are required.", "config")));
|
|
1453
1538
|
resolveReady();
|
|
@@ -1468,39 +1553,4 @@ function createClient(options) {
|
|
|
1468
1553
|
if (doc && typeof addEventListener === "function") addEventListener("online", onOnline);
|
|
1469
1554
|
return client;
|
|
1470
1555
|
}
|
|
1471
|
-
|
|
1472
|
-
enumerable: true,
|
|
1473
|
-
get: function() {
|
|
1474
|
-
return LIVE_PROTOCOL;
|
|
1475
|
-
}
|
|
1476
|
-
});
|
|
1477
|
-
Object.defineProperty(exports, "NanoFlagsError", {
|
|
1478
|
-
enumerable: true,
|
|
1479
|
-
get: function() {
|
|
1480
|
-
return NanoFlagsError;
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
Object.defineProperty(exports, "SDK_VERSION", {
|
|
1484
|
-
enumerable: true,
|
|
1485
|
-
get: function() {
|
|
1486
|
-
return SDK_VERSION;
|
|
1487
|
-
}
|
|
1488
|
-
});
|
|
1489
|
-
Object.defineProperty(exports, "SESSION_HEADER", {
|
|
1490
|
-
enumerable: true,
|
|
1491
|
-
get: function() {
|
|
1492
|
-
return SESSION_HEADER;
|
|
1493
|
-
}
|
|
1494
|
-
});
|
|
1495
|
-
Object.defineProperty(exports, "createClient", {
|
|
1496
|
-
enumerable: true,
|
|
1497
|
-
get: function() {
|
|
1498
|
-
return createClient;
|
|
1499
|
-
}
|
|
1500
|
-
});
|
|
1501
|
-
Object.defineProperty(exports, "isSessionToken", {
|
|
1502
|
-
enumerable: true,
|
|
1503
|
-
get: function() {
|
|
1504
|
-
return isSessionToken;
|
|
1505
|
-
}
|
|
1506
|
-
});
|
|
1556
|
+
export { SESSION_HEADER as a, isSessionToken as c, SDK_VERSION as i, OFFICIAL_EDGE_URL as l, LIVE_PROTOCOL as n, SUPPORTED_ENGINE_VERSION as o, NanoFlagsError as r, createClient as s, CLOSE_ENVIRONMENT_DELETED as t, OFFICIAL_SIGNING_KEYS as u };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// ../engine/src/index.ts
|
|
2
1
|
const encoder$1 = new TextEncoder();
|
|
3
2
|
/** MurmurHash3 x86 32-bit over UTF-8 bytes. Used for distribution only — never for security. */
|
|
4
3
|
function murmur3(input, seed = 0) {
|
|
@@ -264,6 +263,27 @@ function evaluate(bundle, flagKey, ctx, fallback) {
|
|
|
264
263
|
function fmt(v) {
|
|
265
264
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
266
265
|
}
|
|
266
|
+
/** True when this engine can evaluate `bundle` (its `engine` is absent or not newer than `ENGINE_VERSION`). */
|
|
267
|
+
function supportsEngine(bundle) {
|
|
268
|
+
const e = bundle.engine;
|
|
269
|
+
return e === void 0 || typeof e === "number" && Number.isInteger(e) && e >= 1 && e <= 1;
|
|
270
|
+
}
|
|
271
|
+
// src/keys.ts
|
|
272
|
+
/**
|
|
273
|
+
* The hosted NanoFlags edge and the public halves of its bundle-signing keys (Ed25519, safe to embed).
|
|
274
|
+
*
|
|
275
|
+
* With `baseUrl` equal to `OFFICIAL_EDGE_URL` the SDK verifies every bundle against these keys by default
|
|
276
|
+
* (`verify.require` defaults to true there). A bundle signed with a key id that is not listed here — a rotation that
|
|
277
|
+
* happened after this SDK version was built — makes the SDK load `GET /v1/keys` from the edge once and verify against
|
|
278
|
+
* the keys it lists as well. Rotation procedure: RUNTIME.md, "Signing-key rotation".
|
|
279
|
+
*/
|
|
280
|
+
const OFFICIAL_EDGE_URL = "https://edge.nanoflags.io";
|
|
281
|
+
const OFFICIAL_SIGNING_KEYS = Object.freeze([Object.freeze({
|
|
282
|
+
kty: "OKP",
|
|
283
|
+
crv: "Ed25519",
|
|
284
|
+
x: "wQu1EsaE-yTjqpUbibGyBrajy3IiRb7LyaiqeYRxG4U",
|
|
285
|
+
kid: "k1"
|
|
286
|
+
})]);
|
|
267
287
|
// src/store.ts
|
|
268
288
|
const DB = "nanoflags";
|
|
269
289
|
const OBJECT_STORE = "bundles";
|
|
@@ -496,7 +516,11 @@ function rawMember(text, member) {
|
|
|
496
516
|
* the edge pushes the signed bundle itself — no request follows), the SSE stream (`/v1/stream`,
|
|
497
517
|
* a notification followed by a conditional fetch), and polling (`/v1/bundle`).
|
|
498
518
|
*/
|
|
499
|
-
const SDK_VERSION = "0.
|
|
519
|
+
const SDK_VERSION = "0.4.0";
|
|
520
|
+
/** Newest bundle format (`bundle.engine`) this SDK can evaluate. Newer bundles are refused; the previous one stays in use. */
|
|
521
|
+
const SUPPORTED_ENGINE_VERSION = 1;
|
|
522
|
+
/** WebSocket close code of the edge for an environment that was deleted: the SDK stops reconnecting. */
|
|
523
|
+
const CLOSE_ENVIRONMENT_DELETED = 4004;
|
|
500
524
|
/** Header that carries a test-session token between services. */
|
|
501
525
|
const SESSION_HEADER = "x-nanoflags-session";
|
|
502
526
|
/** WebSocket subprotocol of `/v1/live`. The key travels as a second protocol entry, never in the URL. */
|
|
@@ -539,6 +563,9 @@ function isBundle(v) {
|
|
|
539
563
|
const b = v;
|
|
540
564
|
return b.schema === 1 && typeof b.revision === "number" && typeof b.flags === "object" && b.flags !== null && !Array.isArray(b.flags) && typeof b.segments === "object" && b.segments !== null;
|
|
541
565
|
}
|
|
566
|
+
function engineError(b) {
|
|
567
|
+
return new NanoFlagsError(`Bundle r${String(b.revision)} needs evaluation engine ${JSON.stringify(b.engine)}; this SDK supports up to 1. Upgrade @nanoflags/sdk. The configuration in use is unchanged.`, "engine");
|
|
568
|
+
}
|
|
542
569
|
function typeOf(v) {
|
|
543
570
|
return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
|
|
544
571
|
}
|
|
@@ -583,15 +610,38 @@ function createClient(options) {
|
|
|
583
610
|
let liveOpen = false;
|
|
584
611
|
let liveAttempt = 0;
|
|
585
612
|
let liveTimer = null;
|
|
613
|
+
/** The edge closed the socket with 4004: the environment was deleted. No more reconnects. */
|
|
614
|
+
let environmentDeleted = false;
|
|
586
615
|
/** Highest revision adopted from a push: a slower HTTP answer must never replace it with something older. */
|
|
587
616
|
let liveRevision = 0;
|
|
588
617
|
let pushChain = Promise.resolve();
|
|
589
618
|
let lastReportAt = 0;
|
|
590
619
|
const usedAttrs = /* @__PURE__ */ new WeakMap();
|
|
591
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Effective verification: `keys` to verify with (`"fetch"` = only `/v1/keys`), `require` = refuse unsigned bundles,
|
|
622
|
+
* `refetch` = an unknown key id loads `/v1/keys` once (official edge with the embedded keys, or `"fetch"`).
|
|
623
|
+
*/
|
|
624
|
+
const official = baseUrl === OFFICIAL_EDGE_URL;
|
|
625
|
+
const verifyOpt = (() => {
|
|
626
|
+
const v = options.verify;
|
|
627
|
+
if (v === false) return null;
|
|
628
|
+
if (v === void 0 || v === null) return official ? {
|
|
629
|
+
keys: OFFICIAL_SIGNING_KEYS,
|
|
630
|
+
require: true,
|
|
631
|
+
refetch: true
|
|
632
|
+
} : null;
|
|
633
|
+
const keys = v.publicKeys === "fetch" ? "fetch" : Array.isArray(v.publicKeys) ? v.publicKeys : official ? OFFICIAL_SIGNING_KEYS : "fetch";
|
|
634
|
+
return {
|
|
635
|
+
keys,
|
|
636
|
+
require: v.require ?? official,
|
|
637
|
+
refetch: keys === "fetch" || official && v.publicKeys === void 0
|
|
638
|
+
};
|
|
639
|
+
})();
|
|
592
640
|
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
593
|
-
let
|
|
594
|
-
let
|
|
641
|
+
let pinnedKeys = null;
|
|
642
|
+
let fetchedKeys = [];
|
|
643
|
+
let keysFetchedAt = 0;
|
|
644
|
+
let keysFetching = null;
|
|
595
645
|
let pinnedEnvironment = null;
|
|
596
646
|
let sig = null;
|
|
597
647
|
let rawText = null;
|
|
@@ -667,6 +717,7 @@ function createClient(options) {
|
|
|
667
717
|
if (!raw || closed) return;
|
|
668
718
|
const p = JSON.parse(raw);
|
|
669
719
|
if (p.v !== 1 || p.id !== id || !isBundle(p.bundle)) return;
|
|
720
|
+
if (!supportsEngine(p.bundle)) return void emitError(engineError(p.bundle));
|
|
670
721
|
if (bundle && bundle.revision >= p.bundle.revision) return;
|
|
671
722
|
let restored = p.bundle;
|
|
672
723
|
let restoredSig = null;
|
|
@@ -752,20 +803,38 @@ function createClient(options) {
|
|
|
752
803
|
clearTimeout(t);
|
|
753
804
|
}
|
|
754
805
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Keys to verify a signature by `kid` with: the pinned / embedded keys, plus those fetched from `/v1/keys`. With
|
|
808
|
+
* `refetch`, a key id none of them has (rotation) loads `/v1/keys` once — again at most once a minute.
|
|
809
|
+
*/
|
|
810
|
+
async function loadKeys(kid) {
|
|
811
|
+
if (!verifyOpt) return [];
|
|
812
|
+
let base = [];
|
|
813
|
+
if (verifyOpt.keys !== "fetch") {
|
|
814
|
+
const p = pinnedKeys ??= importPublicKeys(verifyOpt.keys);
|
|
761
815
|
p.catch(() => {
|
|
762
|
-
if (
|
|
816
|
+
if (pinnedKeys === p) pinnedKeys = null;
|
|
763
817
|
});
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (verifyOpt.
|
|
768
|
-
|
|
818
|
+
base = await p;
|
|
819
|
+
}
|
|
820
|
+
const all = () => [...base, ...fetchedKeys];
|
|
821
|
+
if (!verifyOpt.refetch) return base;
|
|
822
|
+
const known = all();
|
|
823
|
+
if (known.length > 0 && (kid === null || known.some((k) => k.kid === kid))) return known;
|
|
824
|
+
if (keysFetchedAt !== 0 && Date.now() - keysFetchedAt < KEYS_REFETCH_MS) return known;
|
|
825
|
+
keysFetching ??= fetchKeys().then((keys) => {
|
|
826
|
+
fetchedKeys = keys;
|
|
827
|
+
keysFetchedAt = Date.now();
|
|
828
|
+
}).finally(() => {
|
|
829
|
+
keysFetching = null;
|
|
830
|
+
});
|
|
831
|
+
try {
|
|
832
|
+
await keysFetching;
|
|
833
|
+
} catch (e) {
|
|
834
|
+
if (base.length === 0) throw e;
|
|
835
|
+
keysFetchedAt = Date.now();
|
|
836
|
+
}
|
|
837
|
+
return all();
|
|
769
838
|
}
|
|
770
839
|
/** Returns the bundle to adopt, or throws a NanoFlagsError with code "signature". */
|
|
771
840
|
async function checkSignature(res, text, body) {
|
|
@@ -776,7 +845,7 @@ function createClient(options) {
|
|
|
776
845
|
};
|
|
777
846
|
const reject = (why) => new NanoFlagsError(`Bundle r${body.bundle.revision} was rejected: ${why} The configuration in use is unchanged.`, "signature");
|
|
778
847
|
if (res.headers.get("X-NanoFlags-Signed") !== "true") {
|
|
779
|
-
if (verifyOpt.require) throw reject("it is not signed and `verify.require`
|
|
848
|
+
if (verifyOpt.require) throw reject("it is not signed (the edge is still distributing it) and unsigned bundles are refused (`verify.require`); fetching again shortly.");
|
|
780
849
|
return {
|
|
781
850
|
bundle: body.bundle,
|
|
782
851
|
sig: null,
|
|
@@ -860,6 +929,7 @@ function createClient(options) {
|
|
|
860
929
|
const verified = await checkSignature(res, text, body);
|
|
861
930
|
if (closed) return;
|
|
862
931
|
const next = verified.bundle;
|
|
932
|
+
if (!supportsEngine(next)) throw engineError(next);
|
|
863
933
|
failures = 0;
|
|
864
934
|
if (servedStale && bundle && bundle.revision >= next.revision) {
|
|
865
935
|
setStatus("stale");
|
|
@@ -878,6 +948,7 @@ function createClient(options) {
|
|
|
878
948
|
persist();
|
|
879
949
|
} catch (e) {
|
|
880
950
|
if (closed) return;
|
|
951
|
+
if (e instanceof NanoFlagsError && e.code === "engine") return void emitError(e);
|
|
881
952
|
failures++;
|
|
882
953
|
if (e instanceof NanoFlagsError && e.code === "signature") return void emitError(e);
|
|
883
954
|
if (e instanceof NanoFlagsError && e.code === "unauthorized") dropLive();
|
|
@@ -924,7 +995,7 @@ function createClient(options) {
|
|
|
924
995
|
} catch {}
|
|
925
996
|
}
|
|
926
997
|
function startStream() {
|
|
927
|
-
if (closed || options.streaming === false || !doFetch || streamActive) return;
|
|
998
|
+
if (closed || options.streaming === false || !doFetch || streamActive || environmentDeleted) return;
|
|
928
999
|
streamActive = true;
|
|
929
1000
|
const ES = typeof EventSource === "function" ? EventSource : void 0;
|
|
930
1001
|
if (ES && isBrowserKey) {
|
|
@@ -1023,6 +1094,7 @@ function createClient(options) {
|
|
|
1023
1094
|
parsed = null;
|
|
1024
1095
|
}
|
|
1025
1096
|
if (!isBundle(parsed) || parsed.revision !== revision) return void emitError(new NanoFlagsError("The edge pushed an invalid bundle; it was ignored.", "invalid_bundle"));
|
|
1097
|
+
if (!supportsEngine(parsed)) return void emitError(engineError(parsed));
|
|
1026
1098
|
if (bundle && revision < bundle.revision) return;
|
|
1027
1099
|
failures = 0;
|
|
1028
1100
|
if (bundle && revision === bundle.revision) {
|
|
@@ -1051,7 +1123,7 @@ function createClient(options) {
|
|
|
1051
1123
|
} catch {}
|
|
1052
1124
|
}
|
|
1053
1125
|
function connectLive() {
|
|
1054
|
-
if (closed || !liveWanted || socket) return;
|
|
1126
|
+
if (closed || !liveWanted || socket || environmentDeleted) return;
|
|
1055
1127
|
let ws;
|
|
1056
1128
|
try {
|
|
1057
1129
|
ws = new WS(liveUrl, [LIVE_PROTOCOL, `key.${sdkKey}`]);
|
|
@@ -1121,11 +1193,21 @@ function createClient(options) {
|
|
|
1121
1193
|
if (typeof m !== "object" || m === null || Array.isArray(m)) return;
|
|
1122
1194
|
pushChain = pushChain.then(() => applyPush(m)).catch(() => void 0);
|
|
1123
1195
|
};
|
|
1124
|
-
ws.onclose =
|
|
1196
|
+
ws.onclose = (event) => {
|
|
1197
|
+
if (event?.code === 4004) environmentDeleted = true;
|
|
1198
|
+
end();
|
|
1199
|
+
};
|
|
1125
1200
|
ws.onerror = end;
|
|
1126
1201
|
}
|
|
1127
1202
|
function liveEnded(opened, openMs, rotating) {
|
|
1128
1203
|
if (closed) return;
|
|
1204
|
+
if (environmentDeleted) {
|
|
1205
|
+
stopStream();
|
|
1206
|
+
if (bundle) setStatus("stale");
|
|
1207
|
+
emitError(new NanoFlagsError("The environment of this SDK key was deleted. The last configuration stays in use; it will not be updated.", "environment_deleted"));
|
|
1208
|
+
schedulePoll();
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1129
1211
|
if (rotating) return connectLive();
|
|
1130
1212
|
liveAttempt = opened && openMs > 3e4 ? 1 : liveAttempt + 1;
|
|
1131
1213
|
if (!opened) startStream();
|
|
@@ -1439,7 +1521,10 @@ function createClient(options) {
|
|
|
1439
1521
|
}
|
|
1440
1522
|
};
|
|
1441
1523
|
if (options.bootstrap !== void 0) {
|
|
1442
|
-
if (isBundle(options.bootstrap)
|
|
1524
|
+
if (isBundle(options.bootstrap) && !supportsEngine(options.bootstrap)) {
|
|
1525
|
+
const err = engineError(options.bootstrap);
|
|
1526
|
+
queueMicrotask(() => emitError(err));
|
|
1527
|
+
} else if (isBundle(options.bootstrap)) adopt(options.bootstrap, {
|
|
1443
1528
|
etag: null,
|
|
1444
1529
|
checksum: null,
|
|
1445
1530
|
status: "ready",
|
|
@@ -1447,7 +1532,7 @@ function createClient(options) {
|
|
|
1447
1532
|
});
|
|
1448
1533
|
else queueMicrotask(() => emitError(new NanoFlagsError("The bootstrap bundle is invalid and was ignored.", "invalid_bundle")));
|
|
1449
1534
|
}
|
|
1450
|
-
if (verifyOpt && Array.isArray(verifyOpt.
|
|
1535
|
+
if (verifyOpt && Array.isArray(verifyOpt.keys) && verifyOpt.keys.some((k) => !isPublicEd25519Jwk(k))) queueMicrotask(() => emitError(new NanoFlagsError("`verify.publicKeys` must contain public Ed25519 JWKs only. Other entries (including private keys) were ignored.", "config")));
|
|
1451
1536
|
if (typeof sdkKey !== "string" || sdkKey === "" || baseUrl === "") {
|
|
1452
1537
|
queueMicrotask(() => emitError(new NanoFlagsError("`sdkKey` and `baseUrl` are required.", "config")));
|
|
1453
1538
|
resolveReady();
|
|
@@ -1468,4 +1553,63 @@ function createClient(options) {
|
|
|
1468
1553
|
if (doc && typeof addEventListener === "function") addEventListener("online", onOnline);
|
|
1469
1554
|
return client;
|
|
1470
1555
|
}
|
|
1471
|
-
|
|
1556
|
+
Object.defineProperty(exports, "CLOSE_ENVIRONMENT_DELETED", {
|
|
1557
|
+
enumerable: true,
|
|
1558
|
+
get: function() {
|
|
1559
|
+
return CLOSE_ENVIRONMENT_DELETED;
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
Object.defineProperty(exports, "LIVE_PROTOCOL", {
|
|
1563
|
+
enumerable: true,
|
|
1564
|
+
get: function() {
|
|
1565
|
+
return LIVE_PROTOCOL;
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
Object.defineProperty(exports, "NanoFlagsError", {
|
|
1569
|
+
enumerable: true,
|
|
1570
|
+
get: function() {
|
|
1571
|
+
return NanoFlagsError;
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
Object.defineProperty(exports, "OFFICIAL_EDGE_URL", {
|
|
1575
|
+
enumerable: true,
|
|
1576
|
+
get: function() {
|
|
1577
|
+
return OFFICIAL_EDGE_URL;
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
Object.defineProperty(exports, "OFFICIAL_SIGNING_KEYS", {
|
|
1581
|
+
enumerable: true,
|
|
1582
|
+
get: function() {
|
|
1583
|
+
return OFFICIAL_SIGNING_KEYS;
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
Object.defineProperty(exports, "SDK_VERSION", {
|
|
1587
|
+
enumerable: true,
|
|
1588
|
+
get: function() {
|
|
1589
|
+
return SDK_VERSION;
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
Object.defineProperty(exports, "SESSION_HEADER", {
|
|
1593
|
+
enumerable: true,
|
|
1594
|
+
get: function() {
|
|
1595
|
+
return SESSION_HEADER;
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
Object.defineProperty(exports, "SUPPORTED_ENGINE_VERSION", {
|
|
1599
|
+
enumerable: true,
|
|
1600
|
+
get: function() {
|
|
1601
|
+
return SUPPORTED_ENGINE_VERSION;
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
Object.defineProperty(exports, "createClient", {
|
|
1605
|
+
enumerable: true,
|
|
1606
|
+
get: function() {
|
|
1607
|
+
return createClient;
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
Object.defineProperty(exports, "isSessionToken", {
|
|
1611
|
+
enumerable: true,
|
|
1612
|
+
get: function() {
|
|
1613
|
+
return isSessionToken;
|
|
1614
|
+
}
|
|
1615
|
+
});
|
package/package.json
CHANGED