@fireweaveai/web-sdk-cjs 0.0.0-reserve.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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/application/client.d.ts +104 -0
- package/dist/application/client.js +147 -0
- package/dist/application/mode.d.ts +99 -0
- package/dist/application/mode.js +101 -0
- package/dist/application/ports.d.ts +101 -0
- package/dist/application/ports.js +2 -0
- package/dist/application/runtime.d.ts +70 -0
- package/dist/application/runtime.js +348 -0
- package/dist/build/node/define.d.ts +14 -0
- package/dist/build/node/define.js +38 -0
- package/dist/build/node/shared.d.ts +19 -0
- package/dist/build/node/shared.js +39 -0
- package/dist/build/node/vite.d.ts +53 -0
- package/dist/build/node/vite.js +106 -0
- package/dist/build/src/start/build-info.d.ts +2 -0
- package/dist/build/src/start/build-info.js +9 -0
- package/dist/build/src/start/names.d.ts +38 -0
- package/dist/build/src/start/names.js +41 -0
- package/dist/build/src/start/policy.d.ts +70 -0
- package/dist/build/src/start/policy.js +128 -0
- package/dist/domain/context.d.ts +51 -0
- package/dist/domain/context.js +72 -0
- package/dist/domain/errors.d.ts +36 -0
- package/dist/domain/errors.js +97 -0
- package/dist/domain/target.d.ts +2 -0
- package/dist/domain/target.js +2 -0
- package/dist/domain/types.d.ts +89 -0
- package/dist/domain/types.js +10 -0
- package/dist/domain/validation.d.ts +128 -0
- package/dist/domain/validation.js +358 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +66 -0
- package/dist/infrastructure/adapters/inmemory.d.ts +50 -0
- package/dist/infrastructure/adapters/inmemory.js +94 -0
- package/dist/infrastructure/adapters/local.d.ts +79 -0
- package/dist/infrastructure/adapters/local.js +98 -0
- package/dist/infrastructure/adapters/remote.d.ts +44 -0
- package/dist/infrastructure/adapters/remote.js +329 -0
- package/dist/infrastructure/hosts.d.ts +10 -0
- package/dist/infrastructure/hosts.js +84 -0
- package/dist/start/build-info.d.ts +2 -0
- package/dist/start/build-info.js +9 -0
- package/dist/start/control-points.d.ts +24 -0
- package/dist/start/control-points.js +61 -0
- package/dist/start/fw.d.ts +42 -0
- package/dist/start/fw.js +137 -0
- package/dist/start/identity.d.ts +19 -0
- package/dist/start/identity.js +93 -0
- package/dist/start/index.d.ts +34 -0
- package/dist/start/index.js +38 -0
- package/dist/start/names.d.ts +38 -0
- package/dist/start/names.js +41 -0
- package/dist/start/policy.d.ts +70 -0
- package/dist/start/policy.js +128 -0
- package/dist/start/state.d.ts +103 -0
- package/dist/start/state.js +375 -0
- package/package.json +68 -0
package/dist/start/fw.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fw = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `fw`: the page-wide accessor beside start(). Safe to import anywhere, in any
|
|
6
|
+
* order, and to destructure at module scope.
|
|
7
|
+
*
|
|
8
|
+
* Reads are synchronous (a browser renders from a prefetched cache) and never
|
|
9
|
+
* throw. Before start settles, or if it failed, they return the caller's
|
|
10
|
+
* default, and the *Details forms an ERROR decision saying why.
|
|
11
|
+
*/
|
|
12
|
+
const index_js_1 = require("../index.js");
|
|
13
|
+
const identity_js_1 = require("./identity.js");
|
|
14
|
+
const names_js_1 = require("./names.js");
|
|
15
|
+
const state_js_1 = require("./state.js");
|
|
16
|
+
function errorDecision(controlPointKey, defaultValue, err) {
|
|
17
|
+
return {
|
|
18
|
+
controlPointKey,
|
|
19
|
+
value: defaultValue,
|
|
20
|
+
reason: 'ERROR',
|
|
21
|
+
errorKind: err.kind,
|
|
22
|
+
errorCode: err.openFeatureErrorCode,
|
|
23
|
+
errorMessage: err.message,
|
|
24
|
+
metadata: { 'fireweave.errorKind': err.kind },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function notes(controlPointKey, context) {
|
|
28
|
+
const s = (0, state_js_1.slot)();
|
|
29
|
+
if (s.config?.mode === 'local' && !Object.prototype.hasOwnProperty.call(s.controlPoints, controlPointKey)) {
|
|
30
|
+
(0, state_js_1.warnOnce)(s, `[fireweave:local] '${controlPointKey}' is not in your control points (${names_js_1.CONTROL_POINTS_FILE}), so it gets its default. Add it there to try it locally.`);
|
|
31
|
+
}
|
|
32
|
+
const perCall = context?.targetingKey;
|
|
33
|
+
if (typeof perCall === 'string' && perCall !== s.currentKey) {
|
|
34
|
+
(0, state_js_1.warnOnce)(s, '[fireweave] A per-call targetingKey does not change the decision in the browser: decisions follow fw.identify(). Drop the context argument.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function read(controlPointKey, context, fallback, run) {
|
|
38
|
+
const s = (0, state_js_1.slot)();
|
|
39
|
+
if (s.client === undefined) {
|
|
40
|
+
if (s.state === 'NOT_STARTED') {
|
|
41
|
+
(0, state_js_1.warnOnce)(s, "[fireweave] A control point was read before start(). Import './fireweave/start' first in your entry module; reads serve their defaults until then.");
|
|
42
|
+
}
|
|
43
|
+
return fallback((0, state_js_1.notStartedError)(s.state));
|
|
44
|
+
}
|
|
45
|
+
notes(controlPointKey, context);
|
|
46
|
+
return run(s.client.controlPoints);
|
|
47
|
+
}
|
|
48
|
+
const controlPoints = {
|
|
49
|
+
evaluate: (controlPointKey, expectedType, defaultValue, context, options) => read(controlPointKey, context, (e) => errorDecision(controlPointKey, defaultValue, e), (c) => c.evaluate(controlPointKey, expectedType, defaultValue, context, options)),
|
|
50
|
+
getBooleanValue: (controlPointKey, defaultValue, context) => read(controlPointKey, context, () => defaultValue, (c) => c.getBooleanValue(controlPointKey, defaultValue, context)),
|
|
51
|
+
getStringValue: (controlPointKey, defaultValue, context) => read(controlPointKey, context, () => defaultValue, (c) => c.getStringValue(controlPointKey, defaultValue, context)),
|
|
52
|
+
getNumberValue: (controlPointKey, defaultValue, context) => read(controlPointKey, context, () => defaultValue, (c) => c.getNumberValue(controlPointKey, defaultValue, context)),
|
|
53
|
+
getObjectValue: (controlPointKey, defaultValue, context) => read(controlPointKey, context, () => defaultValue, (c) => c.getObjectValue(controlPointKey, defaultValue, context)),
|
|
54
|
+
getBooleanDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, context, (e) => errorDecision(controlPointKey, defaultValue, e), (c) => c.getBooleanDetails(controlPointKey, defaultValue, context)),
|
|
55
|
+
getStringDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, context, (e) => errorDecision(controlPointKey, defaultValue, e), (c) => c.getStringDetails(controlPointKey, defaultValue, context)),
|
|
56
|
+
getNumberDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, context, (e) => errorDecision(controlPointKey, defaultValue, e), (c) => c.getNumberDetails(controlPointKey, defaultValue, context)),
|
|
57
|
+
getObjectDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, context, (e) => errorDecision(controlPointKey, defaultValue, e), (c) => c.getObjectDetails(controlPointKey, defaultValue, context)),
|
|
58
|
+
};
|
|
59
|
+
/** Run identity changes one at a time, after start settles. */
|
|
60
|
+
function serialized(task) {
|
|
61
|
+
const s = (0, state_js_1.slot)();
|
|
62
|
+
const run = s.chain.then(() => s.ready).then(task);
|
|
63
|
+
s.chain = run.catch(() => undefined);
|
|
64
|
+
return run;
|
|
65
|
+
}
|
|
66
|
+
/** Re-prefetch under `key` when it differs from the current one. */
|
|
67
|
+
async function switchTo(key) {
|
|
68
|
+
const s = (0, state_js_1.slot)();
|
|
69
|
+
if (s.client === undefined || key === s.currentKey)
|
|
70
|
+
return;
|
|
71
|
+
s.currentKey = key;
|
|
72
|
+
try {
|
|
73
|
+
await s.client.runtime.setContext({ targetingKey: key });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// the runtime reports a failed prefetch through its state; never throw from here
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.fw = Object.freeze({
|
|
80
|
+
controlPoints: Object.freeze(controlPoints),
|
|
81
|
+
identify(targetingKey, properties, options = {}) {
|
|
82
|
+
return serialized(async () => {
|
|
83
|
+
const s = (0, state_js_1.slot)();
|
|
84
|
+
if (s.client === undefined) {
|
|
85
|
+
(0, state_js_1.warnOnce)(s, '[fireweave] fw.identify() ran before FireWeave started; the user was not registered.');
|
|
86
|
+
return { ok: false, error: (0, state_js_1.notStartedError)(s.state) };
|
|
87
|
+
}
|
|
88
|
+
if (typeof targetingKey !== 'string' || targetingKey.trim() === '') {
|
|
89
|
+
return { ok: false, error: new index_js_1.FireweaveError('InvalidContext') };
|
|
90
|
+
}
|
|
91
|
+
const result = await s.client.registerTarget(targetingKey, {
|
|
92
|
+
kind: options.kind ?? 'user',
|
|
93
|
+
...(properties !== undefined ? { properties } : {}),
|
|
94
|
+
});
|
|
95
|
+
await switchTo(targetingKey);
|
|
96
|
+
(0, state_js_1.rememberUser)(s, targetingKey);
|
|
97
|
+
return result;
|
|
98
|
+
}).catch(() => ({ ok: false, error: new index_js_1.FireweaveError('Internal') }));
|
|
99
|
+
},
|
|
100
|
+
reset() {
|
|
101
|
+
return serialized(async () => {
|
|
102
|
+
const s = (0, state_js_1.slot)();
|
|
103
|
+
(0, state_js_1.rememberUser)(s, undefined);
|
|
104
|
+
if (s.identity !== undefined)
|
|
105
|
+
await switchTo(s.identity.deviceId);
|
|
106
|
+
}).catch(() => undefined);
|
|
107
|
+
},
|
|
108
|
+
deviceId: () => (0, state_js_1.slot)().identity?.deviceId,
|
|
109
|
+
setPersistence(mode) {
|
|
110
|
+
(0, state_js_1.setPersistenceFor)(mode);
|
|
111
|
+
},
|
|
112
|
+
forget() {
|
|
113
|
+
return serialized(async () => {
|
|
114
|
+
const s = (0, state_js_1.slot)();
|
|
115
|
+
(0, state_js_1.setPersistenceFor)('memory');
|
|
116
|
+
(0, identity_js_1.clearStoredIdentity)();
|
|
117
|
+
if (s.identity === undefined)
|
|
118
|
+
return;
|
|
119
|
+
s.identity = { deviceId: (0, identity_js_1.mintDeviceId)(), durable: false };
|
|
120
|
+
s.appDeviceId = false;
|
|
121
|
+
await switchTo(s.identity.deviceId);
|
|
122
|
+
}).catch(() => undefined);
|
|
123
|
+
},
|
|
124
|
+
get ready() {
|
|
125
|
+
return (0, state_js_1.slot)().ready;
|
|
126
|
+
},
|
|
127
|
+
status: () => (0, state_js_1.currentStatus)(),
|
|
128
|
+
subscribe(listener) {
|
|
129
|
+
const s = (0, state_js_1.slot)();
|
|
130
|
+
s.listeners.add(listener);
|
|
131
|
+
return () => {
|
|
132
|
+
s.listeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
client: () => (0, state_js_1.slot)().client,
|
|
136
|
+
shutdown: () => (0, state_js_1.shutdownSlot)(),
|
|
137
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Persistence = 'localStorage' | 'memory';
|
|
2
|
+
export interface Identity {
|
|
3
|
+
/** The anonymous key. */
|
|
4
|
+
deviceId: string;
|
|
5
|
+
/** True when the id outlives this page (stored, or supplied by the app). Only durable ids are registered. */
|
|
6
|
+
durable: boolean;
|
|
7
|
+
/** The signed-in key restored from storage, if any. */
|
|
8
|
+
user?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function readItem(name: string): string | undefined;
|
|
11
|
+
export declare function writeItem(name: string, value: string): boolean;
|
|
12
|
+
export declare function removeItem(name: string): void;
|
|
13
|
+
export declare function mintDeviceId(): string;
|
|
14
|
+
/** Load (or mint) the identity a start boots under. Writes nothing in memory mode. */
|
|
15
|
+
export declare function loadIdentity(persistence: Persistence, deviceIdOption: string | undefined): Identity;
|
|
16
|
+
/** Write the current ids (consent given). Returns whether the device id is now durable. */
|
|
17
|
+
export declare function persistIdentity(identity: Identity, user: string | undefined, appSuppliedDeviceId: boolean): boolean;
|
|
18
|
+
/** Remove everything the start profile stores (consent withdrawn). */
|
|
19
|
+
export declare function clearStoredIdentity(): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readItem = readItem;
|
|
4
|
+
exports.writeItem = writeItem;
|
|
5
|
+
exports.removeItem = removeItem;
|
|
6
|
+
exports.mintDeviceId = mintDeviceId;
|
|
7
|
+
exports.loadIdentity = loadIdentity;
|
|
8
|
+
exports.persistIdentity = persistIdentity;
|
|
9
|
+
exports.clearStoredIdentity = clearStoredIdentity;
|
|
10
|
+
/**
|
|
11
|
+
* Browser identity for the start profile: the anonymous device id, the signed-in
|
|
12
|
+
* key, and how (or whether) they persist.
|
|
13
|
+
*
|
|
14
|
+
* The device id key and its dev_ prefix are the scaffolded harness's, so an app
|
|
15
|
+
* that migrates keeps every returning visitor in the same ramp bucket. Every
|
|
16
|
+
* storage access is guarded: storage that throws (privacy modes, sandboxed
|
|
17
|
+
* iframes) means one id per page load, never an error.
|
|
18
|
+
*/
|
|
19
|
+
const names_js_1 = require("./names.js");
|
|
20
|
+
const storage = () => {
|
|
21
|
+
try {
|
|
22
|
+
return globalThis.localStorage ?? undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function readItem(name) {
|
|
29
|
+
try {
|
|
30
|
+
const value = storage()?.getItem(name);
|
|
31
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeItem(name, value) {
|
|
38
|
+
try {
|
|
39
|
+
const s = storage();
|
|
40
|
+
if (s === undefined)
|
|
41
|
+
return false;
|
|
42
|
+
s.setItem(name, value);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function removeItem(name) {
|
|
50
|
+
try {
|
|
51
|
+
storage()?.removeItem(name);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// nothing to clean up if storage is unavailable
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function mintDeviceId() {
|
|
58
|
+
try {
|
|
59
|
+
return `dev_${globalThis.crypto.randomUUID()}`;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// randomUUID needs a secure context; the fallback is per page and never stored as stronger than it is.
|
|
63
|
+
return `dev_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Load (or mint) the identity a start boots under. Writes nothing in memory mode. */
|
|
67
|
+
function loadIdentity(persistence, deviceIdOption) {
|
|
68
|
+
const user = persistence === 'localStorage' ? readItem(names_js_1.STORAGE_KEYS.identity) : undefined;
|
|
69
|
+
const withUser = (identity) => (user !== undefined ? { ...identity, user } : identity);
|
|
70
|
+
if (deviceIdOption !== undefined)
|
|
71
|
+
return withUser({ deviceId: deviceIdOption, durable: true });
|
|
72
|
+
if (persistence === 'memory')
|
|
73
|
+
return { deviceId: mintDeviceId(), durable: false };
|
|
74
|
+
const stored = readItem(names_js_1.STORAGE_KEYS.deviceId);
|
|
75
|
+
if (stored !== undefined)
|
|
76
|
+
return withUser({ deviceId: stored, durable: true });
|
|
77
|
+
const deviceId = mintDeviceId();
|
|
78
|
+
return withUser({ deviceId, durable: writeItem(names_js_1.STORAGE_KEYS.deviceId, deviceId) });
|
|
79
|
+
}
|
|
80
|
+
/** Write the current ids (consent given). Returns whether the device id is now durable. */
|
|
81
|
+
function persistIdentity(identity, user, appSuppliedDeviceId) {
|
|
82
|
+
if (user !== undefined)
|
|
83
|
+
writeItem(names_js_1.STORAGE_KEYS.identity, user);
|
|
84
|
+
if (appSuppliedDeviceId)
|
|
85
|
+
return true;
|
|
86
|
+
return writeItem(names_js_1.STORAGE_KEYS.deviceId, identity.deviceId);
|
|
87
|
+
}
|
|
88
|
+
/** Remove everything the start profile stores (consent withdrawn). */
|
|
89
|
+
function clearStoredIdentity() {
|
|
90
|
+
removeItem(names_js_1.STORAGE_KEYS.deviceId);
|
|
91
|
+
removeItem(names_js_1.STORAGE_KEYS.identity);
|
|
92
|
+
removeItem(names_js_1.STORAGE_KEYS.deviceRegistered);
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fireweaveai/web-sdk/start: one start() call for a browser app.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* // src/fireweave/control-points.ts
|
|
6
|
+
* import { defineControlPoints } from '@fireweaveai/web-sdk/start';
|
|
7
|
+
* export const controlPoints = defineControlPoints({ 'new-checkout': { local: true } });
|
|
8
|
+
*
|
|
9
|
+
* // src/fireweave/start.ts — the first import of your entry module
|
|
10
|
+
* import { start } from '@fireweaveai/web-sdk/start';
|
|
11
|
+
* import { controlPoints } from './control-points';
|
|
12
|
+
* export const ready = start({ controlPoints });
|
|
13
|
+
*
|
|
14
|
+
* // anywhere
|
|
15
|
+
* import { fw } from '@fireweaveai/web-sdk/start';
|
|
16
|
+
* if (fw.controlPoints.getBooleanValue('new-checkout', false)) { ... }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The key, endpoint and environment come from the fireweave() Vite plugin
|
|
20
|
+
* (`@fireweaveai/web-sdk/vite`) or fireweaveDefine() (`/define`) at build
|
|
21
|
+
* time, or from start() options. This module reads no environment.
|
|
22
|
+
*
|
|
23
|
+
* A layer over the unchanged core: it imports only the public barrel
|
|
24
|
+
* (docs/adr/0012-start-profile.md).
|
|
25
|
+
*/
|
|
26
|
+
export { start, resetForTests } from './state.js';
|
|
27
|
+
export type { StartOptions, StartState, StartProblem, FireweaveWebStatus } from './state.js';
|
|
28
|
+
export { fw } from './fw.js';
|
|
29
|
+
export type { FireweaveWebStart, ControlPoints, IdentifyOptions } from './fw.js';
|
|
30
|
+
export { defineControlPoints } from './control-points.js';
|
|
31
|
+
export type { ControlPointDefinition, ControlPointMap } from './control-points.js';
|
|
32
|
+
export type { Persistence } from './identity.js';
|
|
33
|
+
export type { StartMode, SdkChannel } from './policy.js';
|
|
34
|
+
export { SDK_VERSION, SDK_CHANNEL } from './build-info.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SDK_CHANNEL = exports.SDK_VERSION = exports.defineControlPoints = exports.fw = exports.resetForTests = exports.start = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @fireweaveai/web-sdk/start: one start() call for a browser app.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // src/fireweave/control-points.ts
|
|
9
|
+
* import { defineControlPoints } from '@fireweaveai/web-sdk/start';
|
|
10
|
+
* export const controlPoints = defineControlPoints({ 'new-checkout': { local: true } });
|
|
11
|
+
*
|
|
12
|
+
* // src/fireweave/start.ts — the first import of your entry module
|
|
13
|
+
* import { start } from '@fireweaveai/web-sdk/start';
|
|
14
|
+
* import { controlPoints } from './control-points';
|
|
15
|
+
* export const ready = start({ controlPoints });
|
|
16
|
+
*
|
|
17
|
+
* // anywhere
|
|
18
|
+
* import { fw } from '@fireweaveai/web-sdk/start';
|
|
19
|
+
* if (fw.controlPoints.getBooleanValue('new-checkout', false)) { ... }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* The key, endpoint and environment come from the fireweave() Vite plugin
|
|
23
|
+
* (`@fireweaveai/web-sdk/vite`) or fireweaveDefine() (`/define`) at build
|
|
24
|
+
* time, or from start() options. This module reads no environment.
|
|
25
|
+
*
|
|
26
|
+
* A layer over the unchanged core: it imports only the public barrel
|
|
27
|
+
* (docs/adr/0012-start-profile.md).
|
|
28
|
+
*/
|
|
29
|
+
var state_js_1 = require("./state.js");
|
|
30
|
+
Object.defineProperty(exports, "start", { enumerable: true, get: function () { return state_js_1.start; } });
|
|
31
|
+
Object.defineProperty(exports, "resetForTests", { enumerable: true, get: function () { return state_js_1.resetForTests; } });
|
|
32
|
+
var fw_js_1 = require("./fw.js");
|
|
33
|
+
Object.defineProperty(exports, "fw", { enumerable: true, get: function () { return fw_js_1.fw; } });
|
|
34
|
+
var control_points_js_1 = require("./control-points.js");
|
|
35
|
+
Object.defineProperty(exports, "defineControlPoints", { enumerable: true, get: function () { return control_points_js_1.defineControlPoints; } });
|
|
36
|
+
var build_info_js_1 = require("./build-info.js");
|
|
37
|
+
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return build_info_js_1.SDK_VERSION; } });
|
|
38
|
+
Object.defineProperty(exports, "SDK_CHANNEL", { enumerable: true, get: function () { return build_info_js_1.SDK_CHANNEL; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every name the web start profile and its build helpers use, in one place, so
|
|
3
|
+
* the README, the initialise skill and the error messages cannot drift apart.
|
|
4
|
+
*
|
|
5
|
+
* Imported by browser code and by the Node build helpers (node/), so it holds
|
|
6
|
+
* constants only.
|
|
7
|
+
*/
|
|
8
|
+
/** Build-time variables the Vite plugin and the define helper read. The browser reads none. */
|
|
9
|
+
export declare const BUILD_ENV: Readonly<{
|
|
10
|
+
key: "FIREWEAVE_BROWSER_KEY";
|
|
11
|
+
url: "FIREWEAVE_URL";
|
|
12
|
+
environment: "FIREWEAVE_ENV";
|
|
13
|
+
}>;
|
|
14
|
+
/** Fallback environment name after FIREWEAVE_ENV, at build time. */
|
|
15
|
+
export declare const ENVIRONMENT_FALLBACK = "APP_ENV";
|
|
16
|
+
/** Server secrets the Vite plugin looks for in output chunks. Never injected, never printed. */
|
|
17
|
+
export declare const SERVER_KEY_NAMES: readonly string[];
|
|
18
|
+
/** Environment names that mean "local development" when no key is set. Case-insensitive. */
|
|
19
|
+
export declare const DEV_ENVIRONMENTS: ReadonlySet<string>;
|
|
20
|
+
/** fw-server host for each release channel of this package. */
|
|
21
|
+
export declare const CHANNEL_URLS: Readonly<{
|
|
22
|
+
production: "https://app-server.fireweave.ai";
|
|
23
|
+
staging: "https://staging-app-server.fireweave.ai";
|
|
24
|
+
}>;
|
|
25
|
+
/** Hosts always allowed beside a custom endpoint, so local stacks keep working. */
|
|
26
|
+
export declare const LOOPBACK_HOSTS: readonly string[];
|
|
27
|
+
/** The only key family a browser may hold. */
|
|
28
|
+
export declare const BROWSER_KEY_PREFIX = "fw_public_";
|
|
29
|
+
/** localStorage keys. The device id key and its dev_ prefix match the scaffolded harness, so returning visitors keep their bucket. */
|
|
30
|
+
export declare const STORAGE_KEYS: Readonly<{
|
|
31
|
+
deviceId: "fireweave.device-id";
|
|
32
|
+
identity: "fireweave.identity";
|
|
33
|
+
deviceRegistered: "fireweave.device-registered";
|
|
34
|
+
}>;
|
|
35
|
+
/** Where the control-points object lives by convention; named in warnings. */
|
|
36
|
+
export declare const CONTROL_POINTS_FILE = "src/fireweave/control-points.ts";
|
|
37
|
+
/** The identifier the build helpers define and the browser reads behind a typeof guard. */
|
|
38
|
+
export declare const INJECTED_CONFIG_NAME = "__FIREWEAVE_WEB_CONFIG__";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Every name the web start profile and its build helpers use, in one place, so
|
|
4
|
+
* the README, the initialise skill and the error messages cannot drift apart.
|
|
5
|
+
*
|
|
6
|
+
* Imported by browser code and by the Node build helpers (node/), so it holds
|
|
7
|
+
* constants only.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.INJECTED_CONFIG_NAME = exports.CONTROL_POINTS_FILE = exports.STORAGE_KEYS = exports.BROWSER_KEY_PREFIX = exports.LOOPBACK_HOSTS = exports.CHANNEL_URLS = exports.DEV_ENVIRONMENTS = exports.SERVER_KEY_NAMES = exports.ENVIRONMENT_FALLBACK = exports.BUILD_ENV = void 0;
|
|
11
|
+
/** Build-time variables the Vite plugin and the define helper read. The browser reads none. */
|
|
12
|
+
exports.BUILD_ENV = Object.freeze({
|
|
13
|
+
key: 'FIREWEAVE_BROWSER_KEY',
|
|
14
|
+
url: 'FIREWEAVE_URL',
|
|
15
|
+
environment: 'FIREWEAVE_ENV',
|
|
16
|
+
});
|
|
17
|
+
/** Fallback environment name after FIREWEAVE_ENV, at build time. */
|
|
18
|
+
exports.ENVIRONMENT_FALLBACK = 'APP_ENV';
|
|
19
|
+
/** Server secrets the Vite plugin looks for in output chunks. Never injected, never printed. */
|
|
20
|
+
exports.SERVER_KEY_NAMES = Object.freeze(['FIREWEAVE_KEY', 'FW_PROJECT_API_KEY']);
|
|
21
|
+
/** Environment names that mean "local development" when no key is set. Case-insensitive. */
|
|
22
|
+
exports.DEV_ENVIRONMENTS = new Set(['development', 'dev', 'local', 'test']);
|
|
23
|
+
/** fw-server host for each release channel of this package. */
|
|
24
|
+
exports.CHANNEL_URLS = Object.freeze({
|
|
25
|
+
production: 'https://app-server.fireweave.ai',
|
|
26
|
+
staging: 'https://staging-app-server.fireweave.ai',
|
|
27
|
+
});
|
|
28
|
+
/** Hosts always allowed beside a custom endpoint, so local stacks keep working. */
|
|
29
|
+
exports.LOOPBACK_HOSTS = Object.freeze(['localhost', '127.0.0.1', '::1']);
|
|
30
|
+
/** The only key family a browser may hold. */
|
|
31
|
+
exports.BROWSER_KEY_PREFIX = 'fw_public_';
|
|
32
|
+
/** localStorage keys. The device id key and its dev_ prefix match the scaffolded harness, so returning visitors keep their bucket. */
|
|
33
|
+
exports.STORAGE_KEYS = Object.freeze({
|
|
34
|
+
deviceId: 'fireweave.device-id',
|
|
35
|
+
identity: 'fireweave.identity',
|
|
36
|
+
deviceRegistered: 'fireweave.device-registered',
|
|
37
|
+
});
|
|
38
|
+
/** Where the control-points object lives by convention; named in warnings. */
|
|
39
|
+
exports.CONTROL_POINTS_FILE = 'src/fireweave/control-points.ts';
|
|
40
|
+
/** The identifier the build helpers define and the browser reads behind a typeof guard. */
|
|
41
|
+
exports.INJECTED_CONFIG_NAME = '__FIREWEAVE_WEB_CONFIG__';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type StartMode = 'remote' | 'local';
|
|
2
|
+
export type SdkChannel = 'staging' | 'production';
|
|
3
|
+
/** A value and where it came from ('start({ key })', 'FIREWEAVE_BROWSER_KEY', ...). */
|
|
4
|
+
export interface Sourced {
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly source: string;
|
|
7
|
+
}
|
|
8
|
+
export type PolicyReason = 'invalid-mode' | 'missing-key' | 'server-key' | 'wrong-key-family' | 'insecure-url';
|
|
9
|
+
export interface PolicyInput {
|
|
10
|
+
readonly mode?: unknown;
|
|
11
|
+
readonly key?: Sourced | undefined;
|
|
12
|
+
readonly url?: Sourced | undefined;
|
|
13
|
+
readonly environment?: Sourced | undefined;
|
|
14
|
+
readonly channel: SdkChannel;
|
|
15
|
+
/** The variable or option a fix should name, e.g. FIREWEAVE_BROWSER_KEY. */
|
|
16
|
+
readonly keyVariable: string;
|
|
17
|
+
/** Where an environment name was looked for, for the missing-key message. */
|
|
18
|
+
readonly environmentChecked: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PolicyConfig {
|
|
21
|
+
readonly mode: StartMode;
|
|
22
|
+
/** Why this mode: the mode option, a present key, or the environment name. */
|
|
23
|
+
readonly modeSource: 'option' | 'key' | 'environment';
|
|
24
|
+
/** Remote only. A value starting with '/' is a same-origin proxy path. */
|
|
25
|
+
readonly url?: string;
|
|
26
|
+
readonly urlSource?: string;
|
|
27
|
+
/** Remote only, and absent for the channel default (the core's own list covers it). */
|
|
28
|
+
readonly allowedHosts?: readonly string[];
|
|
29
|
+
/** Remote only. Never logged. */
|
|
30
|
+
readonly key?: string;
|
|
31
|
+
readonly keySource: string;
|
|
32
|
+
readonly environment?: string;
|
|
33
|
+
readonly environmentSource?: string;
|
|
34
|
+
}
|
|
35
|
+
export type PolicyResult = {
|
|
36
|
+
readonly ok: true;
|
|
37
|
+
readonly config: PolicyConfig;
|
|
38
|
+
readonly warnings: readonly string[];
|
|
39
|
+
} | {
|
|
40
|
+
readonly ok: false;
|
|
41
|
+
readonly reason: PolicyReason;
|
|
42
|
+
readonly variable: string;
|
|
43
|
+
readonly message: string;
|
|
44
|
+
};
|
|
45
|
+
/** A trimmed, non-empty string with its source, or undefined. Non-strings count as unset. */
|
|
46
|
+
export declare function sourced(value: unknown, source: string): Sourced | undefined;
|
|
47
|
+
/** The first candidate that is set. */
|
|
48
|
+
export declare function firstOf(...candidates: ReadonlyArray<Sourced | undefined>): Sourced | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve mode, key and endpoint.
|
|
51
|
+
*
|
|
52
|
+
* An explicit mode wins. Otherwise a key means remote; no key and a
|
|
53
|
+
* development environment name means local; anything else fails closed, so a
|
|
54
|
+
* missing key never turns into silent local evaluation for real visitors.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolvePolicy(input: PolicyInput): PolicyResult;
|
|
57
|
+
/**
|
|
58
|
+
* What the build helpers inject as __FIREWEAVE_WEB_CONFIG__: raw values and
|
|
59
|
+
* their source names, re-resolved in the browser so explicit start() options
|
|
60
|
+
* still win. Never carries a server key or the control-points object.
|
|
61
|
+
*/
|
|
62
|
+
export interface InjectedConfig {
|
|
63
|
+
readonly v: 1;
|
|
64
|
+
readonly key?: string;
|
|
65
|
+
readonly keySource?: string;
|
|
66
|
+
readonly url?: string;
|
|
67
|
+
readonly urlSource?: string;
|
|
68
|
+
readonly environment?: string;
|
|
69
|
+
readonly environmentSource?: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
}
|