@fireweaveai/web-sdk-cjs 0.0.0-reserve.0 → 0.0.0-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -51
- package/package.json +4 -66
- package/LICENSE +0 -21
- package/dist/application/client.d.ts +0 -104
- package/dist/application/client.js +0 -147
- package/dist/application/mode.d.ts +0 -99
- package/dist/application/mode.js +0 -101
- package/dist/application/ports.d.ts +0 -101
- package/dist/application/ports.js +0 -2
- package/dist/application/runtime.d.ts +0 -70
- package/dist/application/runtime.js +0 -348
- package/dist/build/node/define.d.ts +0 -14
- package/dist/build/node/define.js +0 -38
- package/dist/build/node/shared.d.ts +0 -19
- package/dist/build/node/shared.js +0 -39
- package/dist/build/node/vite.d.ts +0 -53
- package/dist/build/node/vite.js +0 -106
- package/dist/build/src/start/build-info.d.ts +0 -2
- package/dist/build/src/start/build-info.js +0 -9
- package/dist/build/src/start/names.d.ts +0 -38
- package/dist/build/src/start/names.js +0 -41
- package/dist/build/src/start/policy.d.ts +0 -70
- package/dist/build/src/start/policy.js +0 -128
- package/dist/domain/context.d.ts +0 -51
- package/dist/domain/context.js +0 -72
- package/dist/domain/errors.d.ts +0 -36
- package/dist/domain/errors.js +0 -97
- package/dist/domain/target.d.ts +0 -2
- package/dist/domain/target.js +0 -2
- package/dist/domain/types.d.ts +0 -89
- package/dist/domain/types.js +0 -10
- package/dist/domain/validation.d.ts +0 -128
- package/dist/domain/validation.js +0 -358
- package/dist/index.d.ts +0 -50
- package/dist/index.js +0 -66
- package/dist/infrastructure/adapters/inmemory.d.ts +0 -50
- package/dist/infrastructure/adapters/inmemory.js +0 -94
- package/dist/infrastructure/adapters/local.d.ts +0 -79
- package/dist/infrastructure/adapters/local.js +0 -98
- package/dist/infrastructure/adapters/remote.d.ts +0 -44
- package/dist/infrastructure/adapters/remote.js +0 -329
- package/dist/infrastructure/hosts.d.ts +0 -10
- package/dist/infrastructure/hosts.js +0 -84
- package/dist/start/build-info.d.ts +0 -2
- package/dist/start/build-info.js +0 -9
- package/dist/start/control-points.d.ts +0 -24
- package/dist/start/control-points.js +0 -61
- package/dist/start/fw.d.ts +0 -42
- package/dist/start/fw.js +0 -137
- package/dist/start/identity.d.ts +0 -19
- package/dist/start/identity.js +0 -93
- package/dist/start/index.d.ts +0 -34
- package/dist/start/index.js +0 -38
- package/dist/start/names.d.ts +0 -38
- package/dist/start/names.js +0 -41
- package/dist/start/policy.d.ts +0 -70
- package/dist/start/policy.js +0 -128
- package/dist/start/state.d.ts +0 -103
- package/dist/start/state.js +0 -375
package/dist/start/policy.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sourced = sourced;
|
|
4
|
-
exports.firstOf = firstOf;
|
|
5
|
-
exports.resolvePolicy = resolvePolicy;
|
|
6
|
-
/**
|
|
7
|
-
* The web start-profile policy: one pure resolver shared by the browser
|
|
8
|
-
* start() and the Node build helpers (node/vite.ts, node/define.ts), so a
|
|
9
|
-
* build and the page it produces can never disagree about mode, key or URL.
|
|
10
|
-
*
|
|
11
|
-
* Pure on purpose: no DOM, no env, no core import. Callers hand in values
|
|
12
|
-
* with their source names; the result is a config, or a failure carrying a
|
|
13
|
-
* fixed reason code and a message that names sources and variables, never a
|
|
14
|
-
* value.
|
|
15
|
-
*/
|
|
16
|
-
const names_js_1 = require("./names.js");
|
|
17
|
-
/** A trimmed, non-empty string with its source, or undefined. Non-strings count as unset. */
|
|
18
|
-
function sourced(value, source) {
|
|
19
|
-
if (typeof value !== 'string')
|
|
20
|
-
return undefined;
|
|
21
|
-
const trimmed = value.trim();
|
|
22
|
-
return trimmed === '' ? undefined : { value: trimmed, source };
|
|
23
|
-
}
|
|
24
|
-
/** The first candidate that is set. */
|
|
25
|
-
function firstOf(...candidates) {
|
|
26
|
-
return candidates.find((c) => c !== undefined);
|
|
27
|
-
}
|
|
28
|
-
const fail = (reason, variable, message) => ({
|
|
29
|
-
ok: false,
|
|
30
|
-
reason,
|
|
31
|
-
variable,
|
|
32
|
-
message: `[fireweave] ${message}`,
|
|
33
|
-
});
|
|
34
|
-
/** Key family check. Messages name the source, never the value. */
|
|
35
|
-
function checkKey(key) {
|
|
36
|
-
const v = key.value;
|
|
37
|
-
if (v.startsWith(names_js_1.BROWSER_KEY_PREFIX))
|
|
38
|
-
return undefined;
|
|
39
|
-
if (v.startsWith('project-api-key_')) {
|
|
40
|
-
return fail('server-key', key.source, `The key from ${key.source} is a server key (project-api-key_…), which must never ship to a browser. Use a browser key (fw_public_…) from Project settings, API keys. If a bundle built with this key was deployed, revoke the key.`);
|
|
41
|
-
}
|
|
42
|
-
if (/^ph[a-z]_/.test(v)) {
|
|
43
|
-
return fail('wrong-key-family', key.source, `The key from ${key.source} is an analytics vendor key, not a FireWeave browser key (fw_public_…).`);
|
|
44
|
-
}
|
|
45
|
-
if (v.startsWith('fw_org_') || v.startsWith('cli_at_')) {
|
|
46
|
-
return fail('wrong-key-family', key.source, `The key from ${key.source} is an organisation or CLI token, not a FireWeave browser key (fw_public_…).`);
|
|
47
|
-
}
|
|
48
|
-
return fail('wrong-key-family', key.source, `The key from ${key.source} is not a FireWeave browser key (fw_public_…).`);
|
|
49
|
-
}
|
|
50
|
-
const isLoopback = (hostname) => hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
|
|
51
|
-
function resolveUrl(url, channel) {
|
|
52
|
-
if (url === undefined)
|
|
53
|
-
return { url: names_js_1.CHANNEL_URLS[channel], urlSource: `SDK channel (${channel})` };
|
|
54
|
-
const value = url.value.replace(/\/+$/, '');
|
|
55
|
-
if (value.startsWith('//')) {
|
|
56
|
-
return fail('insecure-url', url.source, `The endpoint from ${url.source} is protocol-relative. Use an https URL or a same-origin path such as '/fw'.`);
|
|
57
|
-
}
|
|
58
|
-
// A same-origin proxy path: the browser resolves it against location.origin.
|
|
59
|
-
if (value.startsWith('/'))
|
|
60
|
-
return { url: value, urlSource: url.source };
|
|
61
|
-
let parsed;
|
|
62
|
-
try {
|
|
63
|
-
parsed = new URL(value);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return fail('insecure-url', url.source, `The endpoint from ${url.source} is not a valid URL.`);
|
|
67
|
-
}
|
|
68
|
-
const httpsOk = parsed.protocol === 'https:';
|
|
69
|
-
const loopbackHttp = parsed.protocol === 'http:' && isLoopback(parsed.hostname);
|
|
70
|
-
if (!httpsOk && !loopbackHttp) {
|
|
71
|
-
return fail('insecure-url', url.source, `The endpoint from ${url.source} must use https (http is allowed only for localhost).`);
|
|
72
|
-
}
|
|
73
|
-
return { url: value, urlSource: url.source, allowedHosts: [parsed.hostname.replace(/^\[|\]$/g, ''), ...names_js_1.LOOPBACK_HOSTS] };
|
|
74
|
-
}
|
|
75
|
-
function remote(input, key, modeSource, warnings) {
|
|
76
|
-
const keyProblem = checkKey(key);
|
|
77
|
-
if (keyProblem !== undefined)
|
|
78
|
-
return keyProblem;
|
|
79
|
-
const url = resolveUrl(input.url, input.channel);
|
|
80
|
-
if ('ok' in url)
|
|
81
|
-
return url;
|
|
82
|
-
return { ok: true, config: { mode: 'remote', modeSource, ...url, key: key.value, keySource: key.source }, warnings };
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Resolve mode, key and endpoint.
|
|
86
|
-
*
|
|
87
|
-
* An explicit mode wins. Otherwise a key means remote; no key and a
|
|
88
|
-
* development environment name means local; anything else fails closed, so a
|
|
89
|
-
* missing key never turns into silent local evaluation for real visitors.
|
|
90
|
-
*/
|
|
91
|
-
function resolvePolicy(input) {
|
|
92
|
-
const warnings = [];
|
|
93
|
-
const { mode, key, environment } = input;
|
|
94
|
-
if (mode !== undefined && mode !== 'remote' && mode !== 'local') {
|
|
95
|
-
return fail('invalid-mode', 'mode', `start({ mode }) must be 'remote' or 'local'.`);
|
|
96
|
-
}
|
|
97
|
-
if (mode === 'local') {
|
|
98
|
-
if (key !== undefined) {
|
|
99
|
-
warnings.push(`[fireweave] mode 'local' ignores the key from ${key.source}; nothing is sent to fw-server.`);
|
|
100
|
-
}
|
|
101
|
-
return { ok: true, config: { mode: 'local', modeSource: 'option', keySource: 'none' }, warnings };
|
|
102
|
-
}
|
|
103
|
-
if (mode === 'remote') {
|
|
104
|
-
if (key === undefined) {
|
|
105
|
-
return fail('missing-key', input.keyVariable, `mode 'remote' needs a browser key. Set ${input.keyVariable} to a browser key (fw_public_…).`);
|
|
106
|
-
}
|
|
107
|
-
return remote(input, key, 'option', warnings);
|
|
108
|
-
}
|
|
109
|
-
if (key !== undefined)
|
|
110
|
-
return remote(input, key, 'key', warnings);
|
|
111
|
-
if (environment !== undefined && names_js_1.DEV_ENVIRONMENTS.has(environment.value.toLowerCase())) {
|
|
112
|
-
return {
|
|
113
|
-
ok: true,
|
|
114
|
-
config: {
|
|
115
|
-
mode: 'local',
|
|
116
|
-
modeSource: 'environment',
|
|
117
|
-
keySource: 'none',
|
|
118
|
-
environment: environment.value,
|
|
119
|
-
environmentSource: environment.source,
|
|
120
|
-
},
|
|
121
|
-
warnings,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const where = environment === undefined
|
|
125
|
-
? `no environment name is set (checked ${input.environmentChecked})`
|
|
126
|
-
: `the environment is '${environment.value}' (from ${environment.source}), which is not a development name`;
|
|
127
|
-
return fail('missing-key', input.keyVariable, `${input.keyVariable} is not set and ${where}. Set ${input.keyVariable} to a browser key (fw_public_…), or for local work set FIREWEAVE_ENV=development or pass start({ mode: 'local' }).`);
|
|
128
|
-
}
|
package/dist/start/state.d.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* start() and the page-wide singleton behind `fw`.
|
|
3
|
-
*
|
|
4
|
-
* One slot per page (or worker) at a Symbol.for key on globalThis, so HMR
|
|
5
|
-
* re-evaluation and two bundled copies of this package share one client.
|
|
6
|
-
*
|
|
7
|
-
* Nothing here throws or rejects. A browser that fails to start must still
|
|
8
|
-
* render: a configuration fault logs one console.error, sets the state to
|
|
9
|
-
* FAILED and `problem`, and every read serves its default. The build helpers
|
|
10
|
-
* (`/vite`, `/define`) are where a bad configuration fails loudly.
|
|
11
|
-
*/
|
|
12
|
-
import { FireweaveError, type FireweaveWebClient } from '../index.js';
|
|
13
|
-
import { type ControlPointMap } from './control-points.js';
|
|
14
|
-
import { type Identity, type Persistence } from './identity.js';
|
|
15
|
-
import { type InjectedConfig, type PolicyConfig, type PolicyReason, type SdkChannel, type StartMode } from './policy.js';
|
|
16
|
-
export interface StartOptions {
|
|
17
|
-
/** Control points and their local values; import from src/fireweave/control-points.ts. */
|
|
18
|
-
readonly controlPoints?: ControlPointMap;
|
|
19
|
-
/** Force a mode. Without it: a key means remote; no key means local only in a dev environment. */
|
|
20
|
-
readonly mode?: StartMode;
|
|
21
|
-
/** Environment name used to infer the mode. Default: what the fireweave() build plugin injected. */
|
|
22
|
-
readonly environment?: string;
|
|
23
|
-
/** fw-server URL, or a same-origin proxy path such as '/fw'. Default: the build config, else this SDK build's channel host. */
|
|
24
|
-
readonly url?: string;
|
|
25
|
-
/** Browser key (fw_public_…). Default: FIREWEAVE_BROWSER_KEY, injected by the build plugin. */
|
|
26
|
-
readonly key?: string;
|
|
27
|
-
/** Initial storage mode. 'memory' writes nothing until fw.setPersistence('localStorage'). Default 'localStorage'. */
|
|
28
|
-
readonly persistence?: Persistence;
|
|
29
|
-
/** An app-supplied anonymous id (for example the analytics id), used verbatim and not stored. */
|
|
30
|
-
readonly deviceId?: string;
|
|
31
|
-
/** Where [fireweave] lines go. Default: console. */
|
|
32
|
-
readonly log?: (line: string) => void;
|
|
33
|
-
/** Transport override for tests. */
|
|
34
|
-
readonly fetch?: typeof fetch;
|
|
35
|
-
}
|
|
36
|
-
export type StartState = 'NOT_STARTED' | 'INITIALIZING' | 'READY' | 'STALE' | 'ERROR' | 'FAILED' | 'SHUTDOWN';
|
|
37
|
-
export interface StartProblem {
|
|
38
|
-
readonly reason: PolicyReason | 'invalid-control-points' | 'start-failed' | 'key-rejected' | 'unreachable';
|
|
39
|
-
/** The variable or option at fault, when there is one. Never a value. */
|
|
40
|
-
readonly variable?: string;
|
|
41
|
-
}
|
|
42
|
-
export interface FireweaveWebStatus {
|
|
43
|
-
readonly state: StartState;
|
|
44
|
-
readonly mode?: StartMode;
|
|
45
|
-
readonly modeSource?: PolicyConfig['modeSource'];
|
|
46
|
-
readonly channel: SdkChannel;
|
|
47
|
-
readonly sdkVersion: string;
|
|
48
|
-
/** fw-server host name only: never a path or a credential. */
|
|
49
|
-
readonly host?: string;
|
|
50
|
-
readonly endpointSource?: string;
|
|
51
|
-
readonly keySource?: string;
|
|
52
|
-
readonly environment?: string;
|
|
53
|
-
readonly controlPointCount?: number;
|
|
54
|
-
readonly problem?: StartProblem;
|
|
55
|
-
}
|
|
56
|
-
export interface Slot {
|
|
57
|
-
readonly protocol: 1;
|
|
58
|
-
state: StartState;
|
|
59
|
-
/** Bumped by every start and shutdown, so a late result from an older attempt is ignored. */
|
|
60
|
-
generation: number;
|
|
61
|
-
fingerprint?: string | undefined;
|
|
62
|
-
config?: PolicyConfig | undefined;
|
|
63
|
-
controlPoints: ControlPointMap;
|
|
64
|
-
persistence: Persistence;
|
|
65
|
-
appDeviceId: boolean;
|
|
66
|
-
identity?: Identity | undefined;
|
|
67
|
-
/** The key the current decisions were prefetched under. */
|
|
68
|
-
currentKey?: string | undefined;
|
|
69
|
-
client?: FireweaveWebClient | undefined;
|
|
70
|
-
problem?: StartProblem | undefined;
|
|
71
|
-
ready: Promise<void>;
|
|
72
|
-
/** Serializes identify / reset / forget, so the last call wins. */
|
|
73
|
-
chain: Promise<unknown>;
|
|
74
|
-
log: (line: string) => void;
|
|
75
|
-
readonly warned: Set<string>;
|
|
76
|
-
readonly listeners: Set<(state: StartState) => void>;
|
|
77
|
-
detachRuntime?: (() => void) | undefined;
|
|
78
|
-
}
|
|
79
|
-
export declare function slot(): Slot;
|
|
80
|
-
export declare function warnOnce(s: Slot, line: string): void;
|
|
81
|
-
/** console.error once per line. Errors bypass the log sink so they always reach the console. */
|
|
82
|
-
export declare function errorOnce(s: Slot, line: string): void;
|
|
83
|
-
/** Change state and notify. `always` re-notifies an unchanged state (a re-prefetch after identify). */
|
|
84
|
-
export declare function setState(s: Slot, next: StartState, always?: boolean): void;
|
|
85
|
-
/** What the fireweave() build plugin or fireweaveDefine() injected, if anything. */
|
|
86
|
-
export declare function injectedConfig(): InjectedConfig | undefined;
|
|
87
|
-
/**
|
|
88
|
-
* Start FireWeave for this page. Call once, from src/fireweave/start.ts.
|
|
89
|
-
*
|
|
90
|
-
* Resolves when the first prefetch settles (READY, STALE or ERROR) and never
|
|
91
|
-
* rejects, so `await start(...)` cannot blank a page. Check `fw.status()` for
|
|
92
|
-
* the outcome.
|
|
93
|
-
*/
|
|
94
|
-
export declare function start(options?: StartOptions): Promise<void>;
|
|
95
|
-
export declare function currentStatus(): FireweaveWebStatus;
|
|
96
|
-
/** Switch storage at runtime, for consent. Never part of the start() configuration check. */
|
|
97
|
-
export declare function setPersistenceFor(mode: Persistence): void;
|
|
98
|
-
/** Store (or clear) the signed-in key, when persistence allows it. */
|
|
99
|
-
export declare function rememberUser(s: Slot, user: string | undefined): void;
|
|
100
|
-
export declare function shutdownSlot(): Promise<void>;
|
|
101
|
-
/** Test only: shut down and forget the singleton so the next start() begins fresh. */
|
|
102
|
-
export declare function resetForTests(): Promise<void>;
|
|
103
|
-
export declare const notStartedError: (state: StartState) => FireweaveError;
|
package/dist/start/state.js
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.notStartedError = void 0;
|
|
4
|
-
exports.slot = slot;
|
|
5
|
-
exports.warnOnce = warnOnce;
|
|
6
|
-
exports.errorOnce = errorOnce;
|
|
7
|
-
exports.setState = setState;
|
|
8
|
-
exports.injectedConfig = injectedConfig;
|
|
9
|
-
exports.start = start;
|
|
10
|
-
exports.currentStatus = currentStatus;
|
|
11
|
-
exports.setPersistenceFor = setPersistenceFor;
|
|
12
|
-
exports.rememberUser = rememberUser;
|
|
13
|
-
exports.shutdownSlot = shutdownSlot;
|
|
14
|
-
exports.resetForTests = resetForTests;
|
|
15
|
-
/**
|
|
16
|
-
* start() and the page-wide singleton behind `fw`.
|
|
17
|
-
*
|
|
18
|
-
* One slot per page (or worker) at a Symbol.for key on globalThis, so HMR
|
|
19
|
-
* re-evaluation and two bundled copies of this package share one client.
|
|
20
|
-
*
|
|
21
|
-
* Nothing here throws or rejects. A browser that fails to start must still
|
|
22
|
-
* render: a configuration fault logs one console.error, sets the state to
|
|
23
|
-
* FAILED and `problem`, and every read serves its default. The build helpers
|
|
24
|
-
* (`/vite`, `/define`) are where a bad configuration fails loudly.
|
|
25
|
-
*/
|
|
26
|
-
const index_js_1 = require("../index.js");
|
|
27
|
-
const build_info_js_1 = require("./build-info.js");
|
|
28
|
-
const control_points_js_1 = require("./control-points.js");
|
|
29
|
-
const identity_js_1 = require("./identity.js");
|
|
30
|
-
const names_js_1 = require("./names.js");
|
|
31
|
-
const policy_js_1 = require("./policy.js");
|
|
32
|
-
const SLOT_KEY = Symbol.for('@fireweaveai/web-sdk/start');
|
|
33
|
-
let protocolWarned = false;
|
|
34
|
-
const defaultLog = (line) => {
|
|
35
|
-
if (line.startsWith('[fireweave:local]'))
|
|
36
|
-
console.info(line);
|
|
37
|
-
else
|
|
38
|
-
console.warn(line);
|
|
39
|
-
};
|
|
40
|
-
const freshSlot = () => ({
|
|
41
|
-
protocol: 1,
|
|
42
|
-
state: 'NOT_STARTED',
|
|
43
|
-
generation: 0,
|
|
44
|
-
controlPoints: Object.freeze({}),
|
|
45
|
-
persistence: 'localStorage',
|
|
46
|
-
appDeviceId: false,
|
|
47
|
-
ready: Promise.resolve(),
|
|
48
|
-
chain: Promise.resolve(),
|
|
49
|
-
log: defaultLog,
|
|
50
|
-
warned: new Set(),
|
|
51
|
-
listeners: new Set(),
|
|
52
|
-
});
|
|
53
|
-
function slot() {
|
|
54
|
-
const g = globalThis;
|
|
55
|
-
const existing = g[SLOT_KEY];
|
|
56
|
-
if (existing !== undefined) {
|
|
57
|
-
if (existing.protocol !== 1 && !protocolWarned) {
|
|
58
|
-
protocolWarned = true;
|
|
59
|
-
console.warn('[fireweave] Two incompatible copies of @fireweaveai/web-sdk are loaded. Deduplicate the dependency.');
|
|
60
|
-
}
|
|
61
|
-
return existing;
|
|
62
|
-
}
|
|
63
|
-
const created = freshSlot();
|
|
64
|
-
g[SLOT_KEY] = created;
|
|
65
|
-
return created;
|
|
66
|
-
}
|
|
67
|
-
function warnOnce(s, line) {
|
|
68
|
-
if (s.warned.has(line))
|
|
69
|
-
return;
|
|
70
|
-
s.warned.add(line);
|
|
71
|
-
s.log(line);
|
|
72
|
-
}
|
|
73
|
-
/** console.error once per line. Errors bypass the log sink so they always reach the console. */
|
|
74
|
-
function errorOnce(s, line) {
|
|
75
|
-
if (s.warned.has(line))
|
|
76
|
-
return;
|
|
77
|
-
s.warned.add(line);
|
|
78
|
-
console.error(line);
|
|
79
|
-
}
|
|
80
|
-
/** Change state and notify. `always` re-notifies an unchanged state (a re-prefetch after identify). */
|
|
81
|
-
function setState(s, next, always = false) {
|
|
82
|
-
if (s.state === next && !always)
|
|
83
|
-
return;
|
|
84
|
-
s.state = next;
|
|
85
|
-
for (const listener of s.listeners) {
|
|
86
|
-
try {
|
|
87
|
-
listener(next);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// a listener's bug must not break the SDK
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const fromLifecycle = (state) => (state === 'UNINITIALIZED' ? 'INITIALIZING' : state);
|
|
95
|
-
/** What the fireweave() build plugin or fireweaveDefine() injected, if anything. */
|
|
96
|
-
function injectedConfig() {
|
|
97
|
-
let raw;
|
|
98
|
-
try {
|
|
99
|
-
raw = typeof __FIREWEAVE_WEB_CONFIG__ !== 'undefined' ? __FIREWEAVE_WEB_CONFIG__ : undefined;
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
raw = undefined;
|
|
103
|
-
}
|
|
104
|
-
if (typeof raw !== 'object' || raw === null || raw.v !== 1)
|
|
105
|
-
return undefined;
|
|
106
|
-
return raw;
|
|
107
|
-
}
|
|
108
|
-
const hasDom = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
109
|
-
/** Sorted-key JSON, so the fingerprint does not depend on property order. */
|
|
110
|
-
function stableJson(value) {
|
|
111
|
-
if (Array.isArray(value))
|
|
112
|
-
return `[${value.map(stableJson).join(',')}]`;
|
|
113
|
-
if (value !== null && typeof value === 'object') {
|
|
114
|
-
const entries = Object.entries(value)
|
|
115
|
-
.filter(([, v]) => v !== undefined)
|
|
116
|
-
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
117
|
-
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableJson(v)}`).join(',')}}`;
|
|
118
|
-
}
|
|
119
|
-
return JSON.stringify(value ?? null);
|
|
120
|
-
}
|
|
121
|
-
function fingerprintOf(config, controlPoints, deviceId) {
|
|
122
|
-
return stableJson({
|
|
123
|
-
mode: config.mode,
|
|
124
|
-
url: config.url,
|
|
125
|
-
key: config.key,
|
|
126
|
-
allowedHosts: config.allowedHosts,
|
|
127
|
-
deviceId,
|
|
128
|
-
// Local values only matter in local mode.
|
|
129
|
-
controlPoints: config.mode === 'local' ? (0, control_points_js_1.toLocalControlPoints)(controlPoints) : undefined,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
const isActive = (state) => state === 'INITIALIZING' || state === 'READY' || state === 'STALE' || state === 'ERROR';
|
|
133
|
-
function fail(s, problem, message) {
|
|
134
|
-
// A bad repeat start() must not take down the client that is already running.
|
|
135
|
-
if (isActive(s.state)) {
|
|
136
|
-
errorOnce(s, `${message} Keeping the running configuration.`);
|
|
137
|
-
return s.ready;
|
|
138
|
-
}
|
|
139
|
-
s.problem = problem;
|
|
140
|
-
errorOnce(s, `${message} FireWeave is not running; reads serve their defaults.`);
|
|
141
|
-
setState(s, 'FAILED');
|
|
142
|
-
s.ready = Promise.resolve();
|
|
143
|
-
return s.ready;
|
|
144
|
-
}
|
|
145
|
-
/** Same-origin proxy paths ('/fw') resolve against the page origin. */
|
|
146
|
-
function absolutize(config) {
|
|
147
|
-
if (config.url === undefined || !config.url.startsWith('/'))
|
|
148
|
-
return config;
|
|
149
|
-
const origin = globalThis.location.origin;
|
|
150
|
-
return { ...config, url: `${origin}${config.url}`, allowedHosts: [globalThis.location.hostname, ...names_js_1.LOOPBACK_HOSTS] };
|
|
151
|
-
}
|
|
152
|
-
function localLine(config, controlPoints) {
|
|
153
|
-
const why = config.modeSource === 'option'
|
|
154
|
-
? "mode 'local'"
|
|
155
|
-
: `no key; environment '${config.environment ?? ''}' from ${config.environmentSource ?? 'the build config'}`;
|
|
156
|
-
const n = Object.keys(controlPoints).length;
|
|
157
|
-
return `[fireweave:local] Local mode (${why}). Serving ${n} control point${n === 1 ? '' : 's'} from your control-points object; nothing is sent to fw-server.`;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Wrap the transport so faults the core folds into STALE keep their cause:
|
|
161
|
-
* a 401/403 means the key was refused, a rejected fetch means fw-server was
|
|
162
|
-
* not reachable from this page.
|
|
163
|
-
*/
|
|
164
|
-
function observedFetch(s, generation, base, config) {
|
|
165
|
-
const host = config.url !== undefined ? new URL(config.url).host : 'fw-server';
|
|
166
|
-
const wrapped = async (input, init) => {
|
|
167
|
-
try {
|
|
168
|
-
const response = await base(input, init);
|
|
169
|
-
if (s.generation === generation) {
|
|
170
|
-
if (response.status === 401 || response.status === 403) {
|
|
171
|
-
s.problem = { reason: 'key-rejected', variable: config.keySource };
|
|
172
|
-
errorOnce(s, `[fireweave] fw-server refused the browser key from ${config.keySource} (HTTP ${response.status}). Check the key is an active browser key (fw_public_…) for this project. Reads serve their defaults.`);
|
|
173
|
-
}
|
|
174
|
-
else if (response.ok && (s.problem?.reason === 'key-rejected' || s.problem?.reason === 'unreachable')) {
|
|
175
|
-
s.problem = undefined;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return response;
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
if (s.generation === generation) {
|
|
182
|
-
s.problem = { reason: 'unreachable' };
|
|
183
|
-
warnOnce(s, `[fireweave] Could not reach fw-server at ${host}: offline, an ad or tracker blocker, a firewall, or a Content-Security-Policy connect-src rule. Reads serve their defaults. A same-origin proxy set through FIREWEAVE_URL avoids most of these.`);
|
|
184
|
-
}
|
|
185
|
-
throw err;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
return wrapped;
|
|
189
|
-
}
|
|
190
|
-
function registerDevice(s, generation) {
|
|
191
|
-
const { client, identity, config } = s;
|
|
192
|
-
if (client === undefined || identity === undefined || config?.mode !== 'remote' || !identity.durable)
|
|
193
|
-
return;
|
|
194
|
-
if ((0, identity_js_1.readItem)(names_js_1.STORAGE_KEYS.deviceRegistered) === identity.deviceId)
|
|
195
|
-
return;
|
|
196
|
-
void client.registerTarget(identity.deviceId, { kind: 'device' }).then((result) => {
|
|
197
|
-
if (result.ok && s.generation === generation && s.persistence === 'localStorage') {
|
|
198
|
-
(0, identity_js_1.writeItem)(names_js_1.STORAGE_KEYS.deviceRegistered, identity.deviceId);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Start FireWeave for this page. Call once, from src/fireweave/start.ts.
|
|
204
|
-
*
|
|
205
|
-
* Resolves when the first prefetch settles (READY, STALE or ERROR) and never
|
|
206
|
-
* rejects, so `await start(...)` cannot blank a page. Check `fw.status()` for
|
|
207
|
-
* the outcome.
|
|
208
|
-
*/
|
|
209
|
-
function start(options = {}) {
|
|
210
|
-
const s = slot();
|
|
211
|
-
if (options.log !== undefined && (s.state === 'NOT_STARTED' || s.state === 'FAILED' || s.state === 'SHUTDOWN')) {
|
|
212
|
-
s.log = options.log;
|
|
213
|
-
}
|
|
214
|
-
let controlPoints;
|
|
215
|
-
try {
|
|
216
|
-
controlPoints = (0, control_points_js_1.normalizeControlPoints)(options.controlPoints);
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
return fail(s, { reason: 'invalid-control-points', variable: 'controlPoints' }, err.message);
|
|
220
|
-
}
|
|
221
|
-
const injected = injectedConfig();
|
|
222
|
-
const policy = (0, policy_js_1.resolvePolicy)({
|
|
223
|
-
mode: options.mode,
|
|
224
|
-
key: (0, policy_js_1.firstOf)((0, policy_js_1.sourced)(options.key, 'start({ key })'), (0, policy_js_1.sourced)(injected?.key, injected?.keySource ?? names_js_1.BUILD_ENV.key)),
|
|
225
|
-
url: (0, policy_js_1.firstOf)((0, policy_js_1.sourced)(options.url, 'start({ url })'), (0, policy_js_1.sourced)(injected?.url, injected?.urlSource ?? names_js_1.BUILD_ENV.url)),
|
|
226
|
-
environment: (0, policy_js_1.firstOf)((0, policy_js_1.sourced)(options.environment, 'start({ environment })'), (0, policy_js_1.sourced)(injected?.environment, injected?.environmentSource ?? names_js_1.BUILD_ENV.environment)),
|
|
227
|
-
channel: build_info_js_1.SDK_CHANNEL,
|
|
228
|
-
keyVariable: injected !== undefined ? names_js_1.BUILD_ENV.key : `the browser key (start({ key }), or ${names_js_1.BUILD_ENV.key} through the fireweave() build plugin)`,
|
|
229
|
-
environmentChecked: 'start({ environment }) and the build config',
|
|
230
|
-
});
|
|
231
|
-
if (!policy.ok)
|
|
232
|
-
return fail(s, { reason: policy.reason, variable: policy.variable }, policy.message);
|
|
233
|
-
// Remote mode without a DOM (SSR, a worker, a DOM-less test) does nothing:
|
|
234
|
-
// a server-side singleton would share one visitor's identity across requests.
|
|
235
|
-
if (policy.config.mode === 'remote' && !hasDom())
|
|
236
|
-
return Promise.resolve();
|
|
237
|
-
const config = absolutize(policy.config);
|
|
238
|
-
const fingerprint = fingerprintOf(config, controlPoints, options.deviceId);
|
|
239
|
-
if (isActive(s.state)) {
|
|
240
|
-
if (fingerprint !== s.fingerprint) {
|
|
241
|
-
errorOnce(s, '[fireweave] start() was already called with a different configuration; keeping the first one. Call start() once, from src/fireweave/start.ts.');
|
|
242
|
-
}
|
|
243
|
-
return s.ready;
|
|
244
|
-
}
|
|
245
|
-
const generation = s.generation + 1;
|
|
246
|
-
s.generation = generation;
|
|
247
|
-
s.fingerprint = fingerprint;
|
|
248
|
-
s.config = config;
|
|
249
|
-
s.controlPoints = controlPoints;
|
|
250
|
-
s.problem = undefined;
|
|
251
|
-
s.client = undefined;
|
|
252
|
-
s.persistence = options.persistence === 'memory' ? 'memory' : 'localStorage';
|
|
253
|
-
s.appDeviceId = options.deviceId !== undefined;
|
|
254
|
-
s.chain = Promise.resolve();
|
|
255
|
-
for (const w of policy.warnings)
|
|
256
|
-
warnOnce(s, w);
|
|
257
|
-
// Local mode stores nothing and registers nothing: the id lives for this page only.
|
|
258
|
-
const identity = config.mode === 'local'
|
|
259
|
-
? { deviceId: options.deviceId ?? (0, identity_js_1.mintDeviceId)(), durable: false }
|
|
260
|
-
: (0, identity_js_1.loadIdentity)(s.persistence, options.deviceId);
|
|
261
|
-
s.identity = identity;
|
|
262
|
-
s.currentKey = identity.user ?? identity.deviceId;
|
|
263
|
-
if (config.mode === 'local')
|
|
264
|
-
s.log(localLine(config, controlPoints));
|
|
265
|
-
setState(s, 'INITIALIZING');
|
|
266
|
-
const base = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
267
|
-
const init = config.mode === 'local'
|
|
268
|
-
? (0, index_js_1.initFireweave)({
|
|
269
|
-
mode: 'local',
|
|
270
|
-
local: { controlPoints: (0, control_points_js_1.toLocalControlPoints)(controlPoints), log: (line) => s.log(line) },
|
|
271
|
-
context: { targetingKey: s.currentKey },
|
|
272
|
-
})
|
|
273
|
-
: (0, index_js_1.initFireweave)({
|
|
274
|
-
mode: 'remote',
|
|
275
|
-
apiKey: config.key,
|
|
276
|
-
apiUrl: config.url,
|
|
277
|
-
...(config.allowedHosts !== undefined ? { allowedHosts: config.allowedHosts } : {}),
|
|
278
|
-
...(base !== undefined ? { fetch: observedFetch(s, generation, base, config) } : {}),
|
|
279
|
-
context: { targetingKey: s.currentKey },
|
|
280
|
-
});
|
|
281
|
-
s.ready = init.then((client) => {
|
|
282
|
-
if (s.generation !== generation) {
|
|
283
|
-
void client.shutdown();
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
s.client = client;
|
|
287
|
-
s.detachRuntime = client.runtime.onStateChange((state) => {
|
|
288
|
-
if (s.generation === generation)
|
|
289
|
-
setState(s, fromLifecycle(state), true);
|
|
290
|
-
});
|
|
291
|
-
setState(s, fromLifecycle(client.runtime.getState()));
|
|
292
|
-
registerDevice(s, generation);
|
|
293
|
-
}, () => {
|
|
294
|
-
if (s.generation !== generation)
|
|
295
|
-
return;
|
|
296
|
-
s.problem = { reason: 'start-failed' };
|
|
297
|
-
errorOnce(s, '[fireweave] start failed: the core rejected the configuration. FireWeave is not running; reads serve their defaults.');
|
|
298
|
-
setState(s, 'FAILED');
|
|
299
|
-
});
|
|
300
|
-
return s.ready;
|
|
301
|
-
}
|
|
302
|
-
function currentStatus() {
|
|
303
|
-
const s = slot();
|
|
304
|
-
const c = s.config;
|
|
305
|
-
const active = s.state !== 'NOT_STARTED' && s.state !== 'FAILED';
|
|
306
|
-
return {
|
|
307
|
-
state: s.state,
|
|
308
|
-
channel: build_info_js_1.SDK_CHANNEL,
|
|
309
|
-
sdkVersion: build_info_js_1.SDK_VERSION,
|
|
310
|
-
...(c !== undefined && active
|
|
311
|
-
? {
|
|
312
|
-
mode: c.mode,
|
|
313
|
-
modeSource: c.modeSource,
|
|
314
|
-
keySource: c.keySource,
|
|
315
|
-
controlPointCount: Object.keys(s.controlPoints).length,
|
|
316
|
-
...(c.url !== undefined && c.urlSource !== undefined ? { host: new URL(c.url).hostname, endpointSource: c.urlSource } : {}),
|
|
317
|
-
...(c.environment !== undefined ? { environment: c.environment } : {}),
|
|
318
|
-
}
|
|
319
|
-
: {}),
|
|
320
|
-
...(s.problem !== undefined ? { problem: s.problem } : {}),
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
/** Switch storage at runtime, for consent. Never part of the start() configuration check. */
|
|
324
|
-
function setPersistenceFor(mode) {
|
|
325
|
-
const s = slot();
|
|
326
|
-
if (mode !== 'localStorage' && mode !== 'memory')
|
|
327
|
-
return;
|
|
328
|
-
const previous = s.persistence;
|
|
329
|
-
s.persistence = mode;
|
|
330
|
-
if (s.identity === undefined || s.config?.mode !== 'remote' || previous === mode)
|
|
331
|
-
return;
|
|
332
|
-
if (mode === 'memory') {
|
|
333
|
-
(0, identity_js_1.clearStoredIdentity)();
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const user = s.currentKey !== s.identity.deviceId ? s.currentKey : undefined;
|
|
337
|
-
s.identity.durable = (0, identity_js_1.persistIdentity)(s.identity, user, s.appDeviceId);
|
|
338
|
-
registerDevice(s, s.generation);
|
|
339
|
-
}
|
|
340
|
-
/** Store (or clear) the signed-in key, when persistence allows it. */
|
|
341
|
-
function rememberUser(s, user) {
|
|
342
|
-
if (s.config?.mode !== 'remote' || s.persistence !== 'localStorage')
|
|
343
|
-
return;
|
|
344
|
-
if (user === undefined)
|
|
345
|
-
(0, identity_js_1.removeItem)(names_js_1.STORAGE_KEYS.identity);
|
|
346
|
-
else
|
|
347
|
-
(0, identity_js_1.writeItem)(names_js_1.STORAGE_KEYS.identity, user);
|
|
348
|
-
}
|
|
349
|
-
async function shutdownSlot() {
|
|
350
|
-
const s = slot();
|
|
351
|
-
await s.ready;
|
|
352
|
-
const client = s.client;
|
|
353
|
-
s.generation += 1;
|
|
354
|
-
s.detachRuntime?.();
|
|
355
|
-
s.client = undefined;
|
|
356
|
-
s.fingerprint = undefined;
|
|
357
|
-
s.ready = Promise.resolve();
|
|
358
|
-
setState(s, 'SHUTDOWN');
|
|
359
|
-
if (client !== undefined)
|
|
360
|
-
await client.shutdown();
|
|
361
|
-
}
|
|
362
|
-
/** Test only: shut down and forget the singleton so the next start() begins fresh. */
|
|
363
|
-
async function resetForTests() {
|
|
364
|
-
const g = globalThis;
|
|
365
|
-
const s = g[SLOT_KEY];
|
|
366
|
-
if (s !== undefined) {
|
|
367
|
-
s.generation += 1;
|
|
368
|
-
s.detachRuntime?.();
|
|
369
|
-
if (s.client !== undefined)
|
|
370
|
-
await s.client.shutdown().catch(() => undefined);
|
|
371
|
-
}
|
|
372
|
-
delete g[SLOT_KEY];
|
|
373
|
-
}
|
|
374
|
-
const notStartedError = (state) => new index_js_1.FireweaveError(state === 'FAILED' ? 'Configuration' : state === 'SHUTDOWN' ? 'AlreadyClosed' : 'NotReady');
|
|
375
|
-
exports.notStartedError = notStartedError;
|