@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.
Files changed (59) hide show
  1. package/README.md +2 -51
  2. package/package.json +4 -66
  3. package/LICENSE +0 -21
  4. package/dist/application/client.d.ts +0 -104
  5. package/dist/application/client.js +0 -147
  6. package/dist/application/mode.d.ts +0 -99
  7. package/dist/application/mode.js +0 -101
  8. package/dist/application/ports.d.ts +0 -101
  9. package/dist/application/ports.js +0 -2
  10. package/dist/application/runtime.d.ts +0 -70
  11. package/dist/application/runtime.js +0 -348
  12. package/dist/build/node/define.d.ts +0 -14
  13. package/dist/build/node/define.js +0 -38
  14. package/dist/build/node/shared.d.ts +0 -19
  15. package/dist/build/node/shared.js +0 -39
  16. package/dist/build/node/vite.d.ts +0 -53
  17. package/dist/build/node/vite.js +0 -106
  18. package/dist/build/src/start/build-info.d.ts +0 -2
  19. package/dist/build/src/start/build-info.js +0 -9
  20. package/dist/build/src/start/names.d.ts +0 -38
  21. package/dist/build/src/start/names.js +0 -41
  22. package/dist/build/src/start/policy.d.ts +0 -70
  23. package/dist/build/src/start/policy.js +0 -128
  24. package/dist/domain/context.d.ts +0 -51
  25. package/dist/domain/context.js +0 -72
  26. package/dist/domain/errors.d.ts +0 -36
  27. package/dist/domain/errors.js +0 -97
  28. package/dist/domain/target.d.ts +0 -2
  29. package/dist/domain/target.js +0 -2
  30. package/dist/domain/types.d.ts +0 -89
  31. package/dist/domain/types.js +0 -10
  32. package/dist/domain/validation.d.ts +0 -128
  33. package/dist/domain/validation.js +0 -358
  34. package/dist/index.d.ts +0 -50
  35. package/dist/index.js +0 -66
  36. package/dist/infrastructure/adapters/inmemory.d.ts +0 -50
  37. package/dist/infrastructure/adapters/inmemory.js +0 -94
  38. package/dist/infrastructure/adapters/local.d.ts +0 -79
  39. package/dist/infrastructure/adapters/local.js +0 -98
  40. package/dist/infrastructure/adapters/remote.d.ts +0 -44
  41. package/dist/infrastructure/adapters/remote.js +0 -329
  42. package/dist/infrastructure/hosts.d.ts +0 -10
  43. package/dist/infrastructure/hosts.js +0 -84
  44. package/dist/start/build-info.d.ts +0 -2
  45. package/dist/start/build-info.js +0 -9
  46. package/dist/start/control-points.d.ts +0 -24
  47. package/dist/start/control-points.js +0 -61
  48. package/dist/start/fw.d.ts +0 -42
  49. package/dist/start/fw.js +0 -137
  50. package/dist/start/identity.d.ts +0 -19
  51. package/dist/start/identity.js +0 -93
  52. package/dist/start/index.d.ts +0 -34
  53. package/dist/start/index.js +0 -38
  54. package/dist/start/names.d.ts +0 -38
  55. package/dist/start/names.js +0 -41
  56. package/dist/start/policy.d.ts +0 -70
  57. package/dist/start/policy.js +0 -128
  58. package/dist/start/state.d.ts +0 -103
  59. package/dist/start/state.js +0 -375
@@ -1,79 +0,0 @@
1
- /**
2
- * Local development adapter for the browser — the DEV substrate of a
3
- * scaffolded web harness, and the exact peer of the server SDK's
4
- * `FireweaveLocalAdapter`.
5
- *
6
- * A key present in `devFlags` resolves with reason `STATIC`; every other key
7
- * misses, which the runtime turns into the caller's own default. No network,
8
- * no credentials, nothing to configure.
9
- *
10
- * Call-site defaults stay `false` under RAMP-1. Never write `flag(key, true)`
11
- * to dogfood locally — that same `true` is the production fallback when a
12
- * control point is absent from the provider, so it silently ships the ON
13
- * branch. Use `devFlags`.
14
- */
15
- import type { AdapterRuntimeFeatures, PrefetchOptions, PrefetchResult, RegisterTargetOptions, RegisterTargetResult, WebBackendAdapter } from '../../application/ports.js';
16
- import type { TargetKind } from '../../domain/target.js';
17
- import type { CanonicalContext } from '../../domain/types.js';
18
- export interface FireweaveLocalWebAdapterOptions {
19
- readonly devFlags?: Record<string, boolean>;
20
- /**
21
- * Sink for the `[fireweave:local]` registerTarget trace line
22
- * (spec/modes.md "registerTarget in local mode"). Defaults to
23
- * `console.info`.
24
- */
25
- readonly log?: (message: string) => void;
26
- }
27
- /** A target recorded by {@link FireweaveLocalWebAdapter.registerTarget}. */
28
- export interface LocalRegisteredTarget {
29
- readonly targetingKey: string;
30
- readonly kind: TargetKind;
31
- readonly properties: Record<string, unknown>;
32
- readonly environment?: string;
33
- }
34
- export declare class FireweaveLocalWebAdapter implements WebBackendAdapter {
35
- /** Not `inmemory` — that name belongs to the fixture adapter. */
36
- readonly name: "other";
37
- /**
38
- * spec/modes.md "Behaviour per mode": local's unknown-key row is
39
- * `default`/reason `DEFAULT`, not an error. This is the strict `===` seam
40
- * `FireweaveWebRuntime.evaluateSync` checks on a cache miss.
41
- */
42
- readonly missReason: "DEFAULT";
43
- private readonly devFlags;
44
- private readonly log;
45
- private readonly targets;
46
- private closed;
47
- constructor(options?: FireweaveLocalWebAdapterOptions);
48
- initialize(_signal?: AbortSignal): Promise<void>;
49
- /**
50
- * `enabled: true` alongside `reason: 'STATIC'` is deliberate: reporting
51
- * `enabled: false` for an override of `false` would make the runtime label
52
- * the decision `DISABLED`, which means "exists but switched off upstream" —
53
- * not what a local override expresses.
54
- */
55
- prefetch(_context: CanonicalContext, _options?: PrefetchOptions): Promise<PrefetchResult>;
56
- /**
57
- * Records the target in-process and traces it, rather than reporting
58
- * `UnsupportedCapability`.
59
- *
60
- * The capability could instead report Unsupported so a dev harness could
61
- * not *silently* look registered — but the failure being guarded against
62
- * is a developer believing their targeting works because nothing objected.
63
- * A recorded target plus an explicit `[fireweave:local]` line preserves
64
- * that guarantee by a different route: nothing is silent, and local dev
65
- * can exercise targeting rules offline instead of only in production
66
- * (spec/modes.md "registerTarget in local mode").
67
- *
68
- * The trace names the mode, so a line appearing in a production log is
69
- * itself the signal that something booted in local mode by mistake.
70
- *
71
- * No network call is made and nothing reaches fw-server.
72
- */
73
- registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
74
- /** Targets recorded this process, for assertions and dev inspection. */
75
- getRegisteredTargets(): readonly LocalRegisteredTarget[];
76
- shutdown(): Promise<void>;
77
- isClosed(): boolean;
78
- features(): AdapterRuntimeFeatures;
79
- }
@@ -1,98 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FireweaveLocalWebAdapter = void 0;
4
- class FireweaveLocalWebAdapter {
5
- /** Not `inmemory` — that name belongs to the fixture adapter. */
6
- name = 'other';
7
- /**
8
- * spec/modes.md "Behaviour per mode": local's unknown-key row is
9
- * `default`/reason `DEFAULT`, not an error. This is the strict `===` seam
10
- * `FireweaveWebRuntime.evaluateSync` checks on a cache miss.
11
- */
12
- missReason = 'DEFAULT';
13
- devFlags;
14
- log;
15
- targets = new Map();
16
- closed = false;
17
- constructor(options = {}) {
18
- this.devFlags = { ...(options.devFlags ?? {}) };
19
- this.log = options.log ?? ((m) => console.info(m));
20
- }
21
- async initialize(_signal) {
22
- // Nothing to connect to.
23
- }
24
- /**
25
- * `enabled: true` alongside `reason: 'STATIC'` is deliberate: reporting
26
- * `enabled: false` for an override of `false` would make the runtime label
27
- * the decision `DISABLED`, which means "exists but switched off upstream" —
28
- * not what a local override expresses.
29
- */
30
- async prefetch(_context, _options) {
31
- const out = new Map();
32
- for (const [key, value] of Object.entries(this.devFlags)) {
33
- out.set(key, {
34
- found: true,
35
- enabled: true,
36
- value,
37
- variant: value ? 'on' : 'off',
38
- reason: 'STATIC',
39
- flagType: 'boolean',
40
- });
41
- }
42
- return out;
43
- }
44
- /**
45
- * Records the target in-process and traces it, rather than reporting
46
- * `UnsupportedCapability`.
47
- *
48
- * The capability could instead report Unsupported so a dev harness could
49
- * not *silently* look registered — but the failure being guarded against
50
- * is a developer believing their targeting works because nothing objected.
51
- * A recorded target plus an explicit `[fireweave:local]` line preserves
52
- * that guarantee by a different route: nothing is silent, and local dev
53
- * can exercise targeting rules offline instead of only in production
54
- * (spec/modes.md "registerTarget in local mode").
55
- *
56
- * The trace names the mode, so a line appearing in a production log is
57
- * itself the signal that something booted in local mode by mistake.
58
- *
59
- * No network call is made and nothing reaches fw-server.
60
- */
61
- async registerTarget(targetingKey, options = {}) {
62
- const kind = options.kind ?? 'user';
63
- const properties = { ...(options.properties ?? {}) };
64
- const target = {
65
- targetingKey,
66
- kind,
67
- properties,
68
- ...(options.environment === undefined ? {} : { environment: options.environment }),
69
- };
70
- this.targets.set(targetingKey, target);
71
- this.log(`[fireweave:local] registerTarget ${kind} ${targetingKey} ` +
72
- `${JSON.stringify(properties)} — recorded in-process, NOT sent to fw-server`);
73
- return { ok: true };
74
- }
75
- /** Targets recorded this process, for assertions and dev inspection. */
76
- getRegisteredTargets() {
77
- return [...this.targets.values()];
78
- }
79
- async shutdown() {
80
- this.closed = true;
81
- }
82
- isClosed() {
83
- return this.closed;
84
- }
85
- features() {
86
- return {
87
- remoteEvaluation: false,
88
- localEvaluation: true,
89
- localOnly: true,
90
- // No exposure sink exists locally; claiming otherwise would advertise
91
- // emission that silently goes nowhere.
92
- exposureEmission: false,
93
- sideEffectFreeReads: true,
94
- groupAnalytics: false,
95
- };
96
- }
97
- }
98
- exports.FireweaveLocalWebAdapter = FireweaveLocalWebAdapter;
@@ -1,44 +0,0 @@
1
- import type { AdapterRuntimeFeatures, PrefetchOptions, PrefetchResult, RegisterTargetOptions, RegisterTargetResult, WebBackendAdapter } from '../../application/ports.js';
2
- import type { CanonicalContext, Exposure, Signal } from '../../domain/types.js';
3
- export type FireweaveFetchLike = typeof fetch;
4
- export interface FireweaveRemoteWebAdapterOptions {
5
- /** fw-server base URL, e.g. `https://app-server.fireweave.ai`. */
6
- readonly apiUrl: string;
7
- /** Fireweave project key. Public by construction — must be scoped accordingly. */
8
- readonly apiKey: string;
9
- /** SSRF/misconfiguration allowlist override. `['*']` opts out. */
10
- readonly allowedHosts?: readonly string[];
11
- readonly requestTimeoutMs?: number;
12
- /** Injected fetch (tests). Defaults to the global. */
13
- readonly fetch?: FireweaveFetchLike;
14
- }
15
- export declare class FireweaveRemoteWebAdapter implements WebBackendAdapter {
16
- readonly name: "fireweave";
17
- private readonly apiUrl;
18
- private readonly apiKey;
19
- private readonly allowedHosts;
20
- private readonly requestTimeoutMs;
21
- private readonly fetchImpl;
22
- private readonly pending;
23
- private ready;
24
- private closed;
25
- constructor(options: FireweaveRemoteWebAdapterOptions);
26
- initialize(_signal?: AbortSignal): Promise<void>;
27
- prefetch(context: CanonicalContext, options?: PrefetchOptions): Promise<PrefetchResult>;
28
- registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
29
- recordExposure(exposure: Exposure): void;
30
- recordSignal(signal: Signal): void;
31
- /**
32
- * `beacon: true` is the unload path. It must not await anything the browser
33
- * will cancel, so it uses `sendBeacon` when available and a `keepalive`
34
- * fetch otherwise, and it never re-queues on failure — there is no later.
35
- */
36
- flush(options?: {
37
- beacon?: boolean;
38
- }): Promise<void>;
39
- private sendBeaconOrKeepalive;
40
- shutdown(): Promise<void>;
41
- features(): AdapterRuntimeFeatures;
42
- private toResolution;
43
- private requestJson;
44
- }
@@ -1,329 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FireweaveRemoteWebAdapter = void 0;
4
- /**
5
- * Fireweave remote adapter for browsers — the production path.
6
- *
7
- * Speaks only the vendor-neutral Fireweave remote protocol to fw-server:
8
- * POST /v1/control-points/evaluate (batch — one call per context, not per key)
9
- * POST /v1/capture
10
- * POST /v1/targets/register
11
- *
12
- * Auth: `Authorization: Bearer <project key>`. Never a vendor key: the
13
- * constructor rejects `phc_`/`phs_`/`phx_` shapes outright (ADR-0009).
14
- *
15
- * Credentials are EXPLICIT constructor options. This package never reads
16
- * `import.meta.env`, `process.env`, or any other ambient source — the embedding
17
- * app decides what goes into its bundle, and the SDK never picks something up
18
- * on its own.
19
- *
20
- * See spec/remote-protocol.md § Browser clients.
21
- */
22
- const errors_js_1 = require("../../domain/errors.js");
23
- const hosts_js_1 = require("../hosts.js");
24
- const validation_js_1 = require("../../domain/validation.js");
25
- const EVALUATE_PATH = '/v1/control-points/evaluate';
26
- const CAPTURE_PATH = '/v1/capture';
27
- const REGISTER_TARGET_PATH = '/v1/targets/register';
28
- const DEFAULT_REQUEST_TIMEOUT_MS = 3_000;
29
- const ALLOWED_REASONS = new Set([
30
- 'STATIC',
31
- 'DEFAULT',
32
- 'TARGETING_MATCH',
33
- 'SPLIT',
34
- 'CACHED',
35
- 'STALE',
36
- 'DISABLED',
37
- 'UNKNOWN',
38
- 'ERROR',
39
- ]);
40
- function toReason(reason) {
41
- return ALLOWED_REASONS.has(reason) ? reason : undefined;
42
- }
43
- function mapHttpStatus(status) {
44
- if (status === 401)
45
- return new errors_js_1.FireweaveError('Authentication');
46
- if (status === 403)
47
- return new errors_js_1.FireweaveError('Authorization');
48
- if (status === 429)
49
- return new errors_js_1.FireweaveError('RateLimited');
50
- return new errors_js_1.FireweaveError('BackendUnavailable');
51
- }
52
- class FireweaveRemoteWebAdapter {
53
- name = 'fireweave';
54
- apiUrl;
55
- apiKey;
56
- allowedHosts;
57
- requestTimeoutMs;
58
- fetchImpl;
59
- pending = [];
60
- ready = false;
61
- closed = false;
62
- constructor(options) {
63
- // Validate at construction, not at first use: a misconfigured key should
64
- // fail where the developer can see it, not on a user's first evaluation.
65
- (0, hosts_js_1.assertNotSecretKey)(options.apiKey);
66
- this.apiUrl = options.apiUrl.trim().replace(/\/+$/, '');
67
- this.apiKey = options.apiKey.trim();
68
- this.allowedHosts = options.allowedHosts;
69
- this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
70
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
71
- }
72
- async initialize(_signal) {
73
- if (this.closed)
74
- throw new errors_js_1.FireweaveError('AlreadyClosed');
75
- if (this.apiUrl.length === 0)
76
- throw new errors_js_1.FireweaveError('Configuration');
77
- (0, hosts_js_1.assertHostAllowed)(this.apiUrl, this.allowedHosts);
78
- this.ready = true;
79
- }
80
- async prefetch(context, options) {
81
- if (this.closed)
82
- throw new errors_js_1.FireweaveError('AlreadyClosed');
83
- if (!this.ready)
84
- throw new errors_js_1.FireweaveError('NotReady');
85
- const targetingKeyResult = (0, validation_js_1.validateTargetingKey)(context.targetingKey, true);
86
- if (!targetingKeyResult.ok)
87
- throw targetingKeyResult.error;
88
- const targetingKey = targetingKeyResult.value ?? '';
89
- // `$`-prefixed and `fireweave.`-prefixed attributes are backend directives,
90
- // not person properties, and groups travel in their own fields.
91
- const attributes = {};
92
- for (const [k, v] of Object.entries(context.attributes)) {
93
- if (k === 'groups' || k === 'groupProperties')
94
- continue;
95
- if (k.startsWith('$') || k.startsWith('fireweave.'))
96
- continue;
97
- attributes[k] = v;
98
- }
99
- const body = { targetingKey };
100
- if (Object.keys(attributes).length > 0)
101
- body['attributes'] = attributes;
102
- if (context.groups !== undefined)
103
- body['groups'] = context.groups;
104
- if (context.groupProperties !== undefined)
105
- body['groupProperties'] = context.groupProperties;
106
- if (options?.controlPointKeys !== undefined && options.controlPointKeys.length > 0) {
107
- body['controlPointKeys'] = options.controlPointKeys;
108
- }
109
- const response = await this.requestJson(EVALUATE_PATH, body, options?.signal);
110
- const out = new Map();
111
- for (const item of response.decisions ?? []) {
112
- out.set(item.controlPointKey, this.toResolution(item, response.quotaLimited === true));
113
- }
114
- return out;
115
- }
116
- async registerTarget(targetingKey, options = {}) {
117
- if (this.closed)
118
- return { ok: false, error: new errors_js_1.FireweaveError('AlreadyClosed') };
119
- if (!this.ready)
120
- return { ok: false, error: new errors_js_1.FireweaveError('NotReady') };
121
- const targetingKeyResult = (0, validation_js_1.validateTargetingKey)(targetingKey.trim() === '' ? undefined : targetingKey, true);
122
- if (!targetingKeyResult.ok)
123
- return { ok: false, error: targetingKeyResult.error };
124
- const body = { targetingKey };
125
- if (options.kind !== undefined)
126
- body['kind'] = options.kind;
127
- if (options.environment !== undefined)
128
- body['environment'] = options.environment;
129
- if (options.properties !== undefined && Object.keys(options.properties).length > 0) {
130
- body['properties'] = options.properties;
131
- }
132
- // Retried once, and only when the taxonomy says a retry could succeed: a
133
- // rejected payload or a bad key would be rejected identically.
134
- let lastError;
135
- for (let attempt = 0; attempt < 2; attempt += 1) {
136
- try {
137
- await this.requestJson(REGISTER_TARGET_PATH, body, options.signal);
138
- return { ok: true };
139
- }
140
- catch (err) {
141
- lastError = err instanceof errors_js_1.FireweaveError ? err : new errors_js_1.FireweaveError('BackendUnavailable');
142
- if (!lastError.retryable)
143
- break;
144
- }
145
- }
146
- return { ok: false, ...(lastError !== undefined ? { error: lastError } : {}) };
147
- }
148
- recordExposure(exposure) {
149
- if (this.closed || !this.ready)
150
- return;
151
- const event = {
152
- type: 'exposure',
153
- targetingKey: exposure.targetingKey,
154
- controlPointKey: exposure.controlPointKey,
155
- value: exposure.value,
156
- };
157
- if (exposure.variant !== undefined)
158
- event.variant = exposure.variant;
159
- if (exposure.rolloutId !== undefined)
160
- event.rolloutId = exposure.rolloutId;
161
- if (exposure.changeId !== undefined)
162
- event.changeId = exposure.changeId;
163
- if (exposure.stampId !== undefined)
164
- event.stampId = exposure.stampId;
165
- this.pending.push(event);
166
- }
167
- recordSignal(signal) {
168
- if (this.closed || !this.ready)
169
- return;
170
- const event = {
171
- type: 'signal',
172
- targetingKey: signal.targetingKey ?? signal.rolloutId ?? 'fireweave-web-sdk',
173
- name: signal.name,
174
- properties: {
175
- kind: signal.kind,
176
- ...(signal.status !== undefined ? { status: signal.status } : {}),
177
- ...(signal.errorKind !== undefined ? { errorKind: signal.errorKind } : {}),
178
- ...(signal.message !== undefined ? { message: signal.message } : {}),
179
- ...(signal.value !== undefined ? { value: signal.value } : {}),
180
- },
181
- };
182
- if (signal.controlPointKey !== undefined)
183
- event.controlPointKey = signal.controlPointKey;
184
- if (signal.variant !== undefined)
185
- event.variant = signal.variant;
186
- if (signal.rolloutId !== undefined)
187
- event.rolloutId = signal.rolloutId;
188
- if (signal.timestamp !== undefined)
189
- event.timestamp = signal.timestamp;
190
- this.pending.push(event);
191
- }
192
- /**
193
- * `beacon: true` is the unload path. It must not await anything the browser
194
- * will cancel, so it uses `sendBeacon` when available and a `keepalive`
195
- * fetch otherwise, and it never re-queues on failure — there is no later.
196
- */
197
- async flush(options = {}) {
198
- if (this.closed || this.pending.length === 0)
199
- return;
200
- const batch = this.pending.splice(0, this.pending.length);
201
- const payload = JSON.stringify({ events: batch });
202
- if (options.beacon === true) {
203
- this.sendBeaconOrKeepalive(payload);
204
- return;
205
- }
206
- try {
207
- await this.requestJson(CAPTURE_PATH, { events: batch });
208
- }
209
- catch {
210
- // Never throw out of flush; re-queue so a later flush can retry.
211
- this.pending.unshift(...batch);
212
- }
213
- }
214
- sendBeaconOrKeepalive(payload) {
215
- const url = `${this.apiUrl}${CAPTURE_PATH}`;
216
- const nav = globalThis.navigator;
217
- // sendBeacon cannot set an Authorization header, so it is only usable where
218
- // the server accepts the key another way. Prefer keepalive fetch, which
219
- // can, and fall back to a beacon only if fetch is unavailable.
220
- try {
221
- void this.fetchImpl(url, {
222
- method: 'POST',
223
- headers: { 'content-type': 'application/json', authorization: `Bearer ${this.apiKey}` },
224
- body: payload,
225
- keepalive: true,
226
- }).catch(() => undefined);
227
- return;
228
- }
229
- catch {
230
- // fall through to beacon
231
- }
232
- if (typeof nav?.sendBeacon === 'function') {
233
- try {
234
- nav.sendBeacon(url, new Blob([payload], { type: 'application/json' }));
235
- }
236
- catch {
237
- // Unload-time delivery is best-effort by definition.
238
- }
239
- }
240
- }
241
- async shutdown() {
242
- if (this.closed)
243
- return;
244
- await this.flush();
245
- this.closed = true;
246
- this.ready = false;
247
- }
248
- features() {
249
- return {
250
- remoteEvaluation: true,
251
- // Structural, not configurable — the browser never evaluates locally.
252
- localEvaluation: false,
253
- localOnly: false,
254
- exposureEmission: true,
255
- sideEffectFreeReads: true,
256
- groupAnalytics: true,
257
- };
258
- }
259
- toResolution(item, quotaLimited) {
260
- if (item.found === false) {
261
- return quotaLimited ? { found: false, quotaLimited: true } : { found: false };
262
- }
263
- const resolution = {
264
- found: true,
265
- value: item.value,
266
- enabled: item.enabled ?? true,
267
- };
268
- if (item.variant != null)
269
- resolution.variant = item.variant;
270
- const reason = toReason(item.reason);
271
- if (reason !== undefined)
272
- resolution.reason = reason;
273
- if (item.payload !== undefined)
274
- resolution.payload = item.payload;
275
- const meta = item.controlPointMetadata ?? {};
276
- if (typeof meta['fireweave.controlPointVersion'] === 'number') {
277
- resolution.version = meta['fireweave.controlPointVersion'];
278
- }
279
- if (typeof meta['fireweave.reasonCode'] === 'string') {
280
- resolution.reasonCode = meta['fireweave.reasonCode'];
281
- }
282
- if (quotaLimited || meta['fireweave.quotaLimited'] === true)
283
- resolution.quotaLimited = true;
284
- return resolution;
285
- }
286
- async requestJson(path, body, outerSignal) {
287
- const controller = new AbortController();
288
- const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
289
- const onOuterAbort = () => controller.abort();
290
- if (outerSignal !== undefined) {
291
- if (outerSignal.aborted)
292
- controller.abort();
293
- else
294
- outerSignal.addEventListener('abort', onOuterAbort, { once: true });
295
- }
296
- try {
297
- let response;
298
- try {
299
- response = await this.fetchImpl(`${this.apiUrl}${path}`, {
300
- method: 'POST',
301
- headers: {
302
- 'content-type': 'application/json',
303
- authorization: `Bearer ${this.apiKey}`,
304
- },
305
- body: JSON.stringify(body),
306
- signal: controller.signal,
307
- });
308
- }
309
- catch (err) {
310
- const aborted = err instanceof Error && err.name === 'AbortError';
311
- throw aborted ? new errors_js_1.FireweaveError('Timeout') : new errors_js_1.FireweaveError('Network');
312
- }
313
- if (response.status >= 400)
314
- throw mapHttpStatus(response.status);
315
- try {
316
- return (await response.json());
317
- }
318
- catch {
319
- throw new errors_js_1.FireweaveError('MalformedResponse');
320
- }
321
- }
322
- finally {
323
- clearTimeout(timer);
324
- if (outerSignal !== undefined)
325
- outerSignal.removeEventListener('abort', onOuterAbort);
326
- }
327
- }
328
- }
329
- exports.FireweaveRemoteWebAdapter = FireweaveRemoteWebAdapter;
@@ -1,10 +0,0 @@
1
- export declare const DEFAULT_ALLOWED_HOSTS: readonly string[];
2
- export declare function isLoopbackHostname(hostname: string): boolean;
3
- /**
4
- * Validate a backend URL against the allowlist.
5
- * Throws `FireweaveError('Configuration')` — fixed message, no host echoed,
6
- * because this message can land in a shared browser console.
7
- */
8
- export declare function assertHostAllowed(host: string, allowedHosts?: readonly string[]): void;
9
- /** Throw if `apiKey` looks like a vendor or secret key rather than a Fireweave project key. */
10
- export declare function assertNotSecretKey(apiKey: string): void;
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_ALLOWED_HOSTS = void 0;
4
- exports.isLoopbackHostname = isLoopbackHostname;
5
- exports.assertHostAllowed = assertHostAllowed;
6
- exports.assertNotSecretKey = assertNotSecretKey;
7
- /**
8
- * Backend host allowlist — the same SSRF guard the server SDK applies, kept
9
- * for a different reason.
10
- *
11
- * A browser cannot be tricked into reaching a cloud metadata endpoint the way a
12
- * server can; the browser's own origin model already prevents that. What this
13
- * guard buys here is narrower and still worth having: a mistyped or tampered
14
- * `apiUrl` cannot quietly send a project key, a targeting key, and a user's
15
- * evaluation attributes to an unintended host. The allowlist is ON by default,
16
- * so reaching a self-hosted fw-server is a deliberate, explicit act.
17
- *
18
- * Scheme policy matches the server SDK: https everywhere except loopback,
19
- * where plain http is allowed for local development against a dev server.
20
- */
21
- const errors_js_1 = require("../domain/errors.js");
22
- exports.DEFAULT_ALLOWED_HOSTS = Object.freeze([
23
- 'app-server.fireweave.ai',
24
- 'staging-app-server.fireweave.ai',
25
- 'localhost',
26
- '127.0.0.1',
27
- '::1',
28
- ]);
29
- function isLoopbackHostname(hostname) {
30
- return (hostname === 'localhost' ||
31
- hostname === '127.0.0.1' ||
32
- hostname === '::1' ||
33
- hostname === '[::1]');
34
- }
35
- /**
36
- * Validate a backend URL against the allowlist.
37
- * Throws `FireweaveError('Configuration')` — fixed message, no host echoed,
38
- * because this message can land in a shared browser console.
39
- */
40
- function assertHostAllowed(host, allowedHosts) {
41
- let url;
42
- try {
43
- url = new URL(host);
44
- }
45
- catch {
46
- throw new errors_js_1.FireweaveError('Configuration');
47
- }
48
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
49
- throw new errors_js_1.FireweaveError('Configuration');
50
- }
51
- const loopback = isLoopbackHostname(url.hostname);
52
- if (url.protocol === 'http:' && !loopback) {
53
- throw new errors_js_1.FireweaveError('Configuration');
54
- }
55
- const list = allowedHosts !== undefined && allowedHosts.length > 0 ? allowedHosts : exports.DEFAULT_ALLOWED_HOSTS;
56
- if (list.includes('*'))
57
- return; // explicit opt-out
58
- const allowed = list.some((h) => url.hostname === h ||
59
- (h === 'localhost' && loopback) ||
60
- (h === '::1' && url.hostname === '[::1]'));
61
- if (!allowed) {
62
- throw new errors_js_1.FireweaveError('Configuration');
63
- }
64
- }
65
- /**
66
- * Key shapes this SDK refuses to accept.
67
- *
68
- * The whole security argument for a browser package (ADR-0009) is that it never
69
- * wants a secret. Enforcing that at the door — rather than trusting that no
70
- * code path asks for one — turns the claim into something a caller cannot
71
- * accidentally violate by pasting the wrong value into their bundle config.
72
- */
73
- const FORBIDDEN_KEY_PREFIXES = Object.freeze(['phs_', 'phx_', 'phc_']);
74
- /** Throw if `apiKey` looks like a vendor or secret key rather than a Fireweave project key. */
75
- function assertNotSecretKey(apiKey) {
76
- const key = apiKey.trim();
77
- if (key.length === 0)
78
- throw new errors_js_1.FireweaveError('Configuration');
79
- for (const prefix of FORBIDDEN_KEY_PREFIXES) {
80
- if (key.startsWith(prefix)) {
81
- throw new errors_js_1.FireweaveError('Configuration');
82
- }
83
- }
84
- }
@@ -1,2 +0,0 @@
1
- export declare const SDK_VERSION = "3.0.0";
2
- export declare const SDK_CHANNEL: 'staging' | 'production';
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SDK_CHANNEL = exports.SDK_VERSION = void 0;
4
- // GENERATED by tools/release/version.sh apply web — do not edit by hand.
5
- // The start profile reads SDK_CHANNEL to choose its default fw-server host
6
- // (docs/adr/0012-start-profile.md). test/unit/start-build-info.test.ts pins
7
- // SDK_VERSION to package.json, so a release that forgets to stamp fails CI.
8
- exports.SDK_VERSION = '3.0.0';
9
- exports.SDK_CHANNEL = 'production';