@genn-inc/cluebase-frontend-sdk 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_generated/correlation-attribute-keys.js +1 -0
- package/dist/exporter/batch.js +8 -1
- package/dist/exporter/client.js +2 -0
- package/dist/exporter/delivery-pipeline.d.ts +1 -0
- package/dist/exporter/delivery-pipeline.js +3 -0
- package/dist/exporter/delivery-response-contract.d.ts +4 -0
- package/dist/exporter/delivery-response-contract.js +6 -0
- package/dist/exporter/delivery-response.d.ts +1 -0
- package/dist/exporter/delivery-response.js +3 -0
- package/dist/exporter/parameter-encryption.d.ts +10 -0
- package/dist/exporter/parameter-encryption.js +219 -0
- package/dist/exporter/parameter-snapshot.js +25 -3
- package/dist/exporter/sdk-core-delivery.d.ts +1 -0
- package/dist/exporter/sdk-core-wasm.d.ts +17 -0
- package/dist/exporter/sdk-core-wasm.js +8 -1
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/contracts.d.ts +2 -0
- package/dist/provider/contracts.js +2 -0
- package/dist/provider/sdk-encryption-store.d.ts +7 -0
- package/dist/provider/sdk-encryption-store.js +15 -0
- package/dist/provider/types.d.ts +2 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +6 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +53 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +2 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS = new Set([
|
|
|
11
11
|
"cluebase.actor.user_id",
|
|
12
12
|
"cluebase.action.id",
|
|
13
13
|
"cluebase.action_id",
|
|
14
|
+
"cluebase.entity_id_masked",
|
|
14
15
|
"cluebase.interaction.id",
|
|
15
16
|
"cluebase.interaction_id",
|
|
16
17
|
"cluebase.organization.id",
|
package/dist/exporter/batch.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_CONFIG } from "../provider/constants.js";
|
|
2
2
|
import { applyDeliveredPrivacyPolicy, resetPrivacyPolicy, } from "../provider/privacy-policy-store.js";
|
|
3
|
+
import { applyDeliveredSdkEncryption, resetSdkEncryption, } from "../provider/sdk-encryption-store.js";
|
|
3
4
|
import { BrowserOutbox } from "./browser-outbox.js";
|
|
4
5
|
import { ownEvent, sentByteLength } from "./browser-outbox-records.js";
|
|
5
6
|
import { DeliveryPipeline, } from "./delivery-pipeline.js";
|
|
6
7
|
import { DiagnosticStore } from "./diagnostics.js";
|
|
8
|
+
import { settlePendingEncryptions } from "./parameter-encryption.js";
|
|
7
9
|
const CLOSE_DEADLINE_MS = 5000;
|
|
8
10
|
const CORE_CONSENT = {
|
|
9
11
|
unknown: "unknown",
|
|
@@ -112,9 +114,10 @@ export class BatchTransport {
|
|
|
112
114
|
}
|
|
113
115
|
const snapshot = owned.value();
|
|
114
116
|
void this.queueOutboxMutation(async () => {
|
|
117
|
+
const settled = await settlePendingEncryptions(snapshot);
|
|
115
118
|
const draft = await this.outbox.withDeliveryLock(async () => {
|
|
116
119
|
this.adoptStoredRecords();
|
|
117
|
-
return this.outbox.holdDraft(
|
|
120
|
+
return this.outbox.holdDraft(settled);
|
|
118
121
|
});
|
|
119
122
|
if (!draft) {
|
|
120
123
|
this.metrics?.increment("dropped_count");
|
|
@@ -311,6 +314,10 @@ export class BatchTransport {
|
|
|
311
314
|
}
|
|
312
315
|
else if (observation.directiveUnreadable) {
|
|
313
316
|
resetPrivacyPolicy();
|
|
317
|
+
resetSdkEncryption();
|
|
318
|
+
}
|
|
319
|
+
if (observation.sdkEncryption !== undefined) {
|
|
320
|
+
applyDeliveredSdkEncryption(observation.sdkEncryption);
|
|
314
321
|
}
|
|
315
322
|
const progress = observation.progress;
|
|
316
323
|
if (progress.kind !== "retry_scheduled") {
|
package/dist/exporter/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { canonicalize as canonicalJsonStringify } from "json-canonicalize";
|
|
2
2
|
import { CONSENT_DENIED_ABORT_REASON, DEFAULT_FRONTEND_SOURCE_IDENTIFIER, } from "../provider/constants.js";
|
|
3
3
|
import { appliedPrivacyPolicyToken } from "../provider/privacy-policy-store.js";
|
|
4
|
+
import { currentSdkEncryption } from "../provider/sdk-encryption-store.js";
|
|
4
5
|
import { classifyIngestHttpStatus, } from "./delivery-response.js";
|
|
5
6
|
import { generateBatchId } from "./event-id.js";
|
|
6
7
|
import { describeIngestEndpoint, } from "./sdk-core-wasm.js";
|
|
@@ -109,6 +110,7 @@ export class IngestClient {
|
|
|
109
110
|
idempotencyKey: metadata.idempotency_key,
|
|
110
111
|
sentAt: metadata.sent_at,
|
|
111
112
|
privacyPolicyToken: appliedPrivacyPolicyToken(),
|
|
113
|
+
sdkEncryptionKeyVersion: currentSdkEncryption()?.keyVersion,
|
|
112
114
|
producerMetadata: {
|
|
113
115
|
producer_id: this.producerId,
|
|
114
116
|
sdk_type: this.sdkType,
|
|
@@ -31,6 +31,7 @@ export type DeliveryObservation = {
|
|
|
31
31
|
readonly httpStatus: number | null;
|
|
32
32
|
readonly reason: string | null;
|
|
33
33
|
readonly privacyPolicy?: unknown;
|
|
34
|
+
readonly sdkEncryption?: unknown;
|
|
34
35
|
readonly directiveUnreadable: boolean;
|
|
35
36
|
readonly diagnosticCodes: readonly DiagnosticCode[];
|
|
36
37
|
};
|
|
@@ -190,6 +190,9 @@ const observe = (report) => {
|
|
|
190
190
|
...(report.decision?.privacy_policy === undefined
|
|
191
191
|
? {}
|
|
192
192
|
: { privacyPolicy: report.decision.privacy_policy }),
|
|
193
|
+
...(report.decision?.sdk_encryption === undefined
|
|
194
|
+
? {}
|
|
195
|
+
: { sdkEncryption: report.decision.sdk_encryption }),
|
|
193
196
|
directiveUnreadable: report.decision?.directive_unreadable === true,
|
|
194
197
|
diagnosticCodes: report.decision?.diagnostic_codes ?? [],
|
|
195
198
|
};
|
|
@@ -20,4 +20,8 @@ export declare const ingestPrivacyPolicySchema: z.ZodObject<{
|
|
|
20
20
|
}, z.core.$strict>>;
|
|
21
21
|
}, z.core.$strict>;
|
|
22
22
|
export type IngestPrivacyPolicy = z.output<typeof ingestPrivacyPolicySchema>;
|
|
23
|
+
export declare const ingestSdkEncryptionSchema: z.ZodObject<{
|
|
24
|
+
key_version: z.ZodNumber;
|
|
25
|
+
public_key: z.ZodString;
|
|
26
|
+
}, z.core.$strict>;
|
|
23
27
|
export {};
|
|
@@ -5,6 +5,7 @@ export type { IngestRetryStrategy } from "./delivery-response-contract.js";
|
|
|
5
5
|
export type BrowserDeliveryDecision = {
|
|
6
6
|
readonly ok: boolean;
|
|
7
7
|
readonly privacyPolicy?: unknown;
|
|
8
|
+
readonly sdkEncryption?: unknown;
|
|
8
9
|
readonly directiveUnreadable: boolean;
|
|
9
10
|
readonly retryStrategy: IngestRetryStrategy | null;
|
|
10
11
|
readonly observationIngestBatchId?: string;
|
|
@@ -37,6 +37,9 @@ const project = (decision) => {
|
|
|
37
37
|
...(decision.privacy_policy === undefined
|
|
38
38
|
? {}
|
|
39
39
|
: { privacyPolicy: decision.privacy_policy }),
|
|
40
|
+
...(decision.sdk_encryption === undefined
|
|
41
|
+
? {}
|
|
42
|
+
: { sdkEncryption: decision.sdk_encryption }),
|
|
40
43
|
directiveUnreadable: decision.directive_unreadable === true,
|
|
41
44
|
...(batchId === undefined ? {} : { observationIngestBatchId: batchId }),
|
|
42
45
|
droppedEventCount: decision.result.dropped_event_count ?? 0,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ParameterValueFact } from "./sdk-core-wasm.js";
|
|
2
|
+
export declare const parameterValueFact: (value: unknown) => ParameterValueFact;
|
|
3
|
+
export declare const beginParameterCaptureDecision: (key: string, value: unknown) => string;
|
|
4
|
+
export declare const canEncryptParameters: () => boolean;
|
|
5
|
+
export declare const beginParameterEncryption: (plaintext: string) => {
|
|
6
|
+
handle: string;
|
|
7
|
+
keyVersion: number;
|
|
8
|
+
} | null;
|
|
9
|
+
export declare const settleParameterCaptureDecisions: (node: unknown) => void;
|
|
10
|
+
export declare const settlePendingEncryptions: <T>(value: T) => Promise<T>;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { currentSdkEncryption, } from "../provider/sdk-encryption-store.js";
|
|
2
|
+
import { decideParameterCaptures, } from "./sdk-core-wasm.js";
|
|
3
|
+
const PENDING_PREFIX = "cbenc~";
|
|
4
|
+
const PENDING_DECISION_PREFIX = "cbdec~";
|
|
5
|
+
const MAX_PENDING_ENCRYPTIONS = 1000;
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
const pendingDecisions = new Map();
|
|
8
|
+
const importedKeys = new Map();
|
|
9
|
+
const toBase64 = (bytes) => {
|
|
10
|
+
let binary = "";
|
|
11
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
12
|
+
binary += String.fromCharCode(bytes[index] ?? 0);
|
|
13
|
+
}
|
|
14
|
+
return btoa(binary);
|
|
15
|
+
};
|
|
16
|
+
const fromBase64 = (text) => {
|
|
17
|
+
const binary = atob(text);
|
|
18
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
19
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
20
|
+
bytes[index] = binary.charCodeAt(index);
|
|
21
|
+
}
|
|
22
|
+
return bytes;
|
|
23
|
+
};
|
|
24
|
+
const subtle = () => typeof crypto !== "undefined" && crypto.subtle ? crypto.subtle : null;
|
|
25
|
+
const importPublicKey = (config) => {
|
|
26
|
+
const cacheKey = `${config.keyVersion}:${config.publicKey}`;
|
|
27
|
+
const cached = importedKeys.get(cacheKey);
|
|
28
|
+
if (cached)
|
|
29
|
+
return cached;
|
|
30
|
+
const api = subtle();
|
|
31
|
+
const imported = api
|
|
32
|
+
? api
|
|
33
|
+
.importKey("spki", fromBase64(config.publicKey), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"])
|
|
34
|
+
.catch(() => null)
|
|
35
|
+
: Promise.resolve(null);
|
|
36
|
+
importedKeys.set(cacheKey, imported);
|
|
37
|
+
return imported;
|
|
38
|
+
};
|
|
39
|
+
const encryptNow = async (config, plaintext) => {
|
|
40
|
+
const api = subtle();
|
|
41
|
+
const bytes = new TextEncoder().encode(plaintext);
|
|
42
|
+
try {
|
|
43
|
+
const key = await importPublicKey(config);
|
|
44
|
+
if (!api || !key)
|
|
45
|
+
return null;
|
|
46
|
+
const encrypted = await api.encrypt({ name: "RSA-OAEP" }, key, bytes);
|
|
47
|
+
return {
|
|
48
|
+
keyVersion: config.keyVersion,
|
|
49
|
+
ciphertext: toBase64(new Uint8Array(encrypted)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
bytes.fill(0);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const newHandle = (prefix) => {
|
|
60
|
+
const random = new Uint8Array(12);
|
|
61
|
+
crypto.getRandomValues(random);
|
|
62
|
+
return `${prefix}${toBase64(random).replace(/[^A-Za-z0-9]/g, "")}`;
|
|
63
|
+
};
|
|
64
|
+
export const parameterValueFact = (value) => {
|
|
65
|
+
if (typeof value === "boolean")
|
|
66
|
+
return { type: "boolean" };
|
|
67
|
+
if (typeof value === "number")
|
|
68
|
+
return { type: "number", number: value };
|
|
69
|
+
if (typeof value === "string")
|
|
70
|
+
return { type: "text", text: value };
|
|
71
|
+
return { type: "other" };
|
|
72
|
+
};
|
|
73
|
+
export const beginParameterCaptureDecision = (key, value) => {
|
|
74
|
+
const handle = newHandle(PENDING_DECISION_PREFIX);
|
|
75
|
+
if (pendingDecisions.size >= MAX_PENDING_ENCRYPTIONS) {
|
|
76
|
+
const oldest = pendingDecisions.keys().next().value;
|
|
77
|
+
if (oldest !== undefined)
|
|
78
|
+
pendingDecisions.delete(oldest);
|
|
79
|
+
}
|
|
80
|
+
pendingDecisions.set(handle, {
|
|
81
|
+
key,
|
|
82
|
+
value,
|
|
83
|
+
fact: parameterValueFact(value),
|
|
84
|
+
});
|
|
85
|
+
return handle;
|
|
86
|
+
};
|
|
87
|
+
export const canEncryptParameters = () => currentSdkEncryption() !== null && subtle() !== null;
|
|
88
|
+
export const beginParameterEncryption = (plaintext) => {
|
|
89
|
+
const config = currentSdkEncryption();
|
|
90
|
+
if (!config || !subtle())
|
|
91
|
+
return null;
|
|
92
|
+
const handle = newHandle(PENDING_PREFIX);
|
|
93
|
+
if (pending.size >= MAX_PENDING_ENCRYPTIONS) {
|
|
94
|
+
const oldest = pending.keys().next().value;
|
|
95
|
+
if (oldest !== undefined)
|
|
96
|
+
pending.delete(oldest);
|
|
97
|
+
}
|
|
98
|
+
pending.set(handle, encryptNow(config, plaintext));
|
|
99
|
+
return { handle, keyVersion: config.keyVersion };
|
|
100
|
+
};
|
|
101
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
102
|
+
const MASKED_FALLBACK = "__cluebase_masked__";
|
|
103
|
+
const settleLeaf = async (leaf) => {
|
|
104
|
+
const handle = leaf.ciphertext;
|
|
105
|
+
if (typeof handle !== "string" || !handle.startsWith(PENDING_PREFIX))
|
|
106
|
+
return;
|
|
107
|
+
const result = await pending.get(handle);
|
|
108
|
+
pending.delete(handle);
|
|
109
|
+
if (result) {
|
|
110
|
+
leaf.key_version = result.keyVersion;
|
|
111
|
+
leaf.ciphertext = result.ciphertext;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
leaf.capture_mode = "masked_fingerprint";
|
|
115
|
+
leaf.value = MASKED_FALLBACK;
|
|
116
|
+
delete leaf.key_version;
|
|
117
|
+
delete leaf.ciphertext;
|
|
118
|
+
};
|
|
119
|
+
const collectPendingDecisionLeaves = (node, found) => {
|
|
120
|
+
if (Array.isArray(node)) {
|
|
121
|
+
for (const child of node)
|
|
122
|
+
collectPendingDecisionLeaves(child, found);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!isRecord(node))
|
|
126
|
+
return;
|
|
127
|
+
const handle = node.ciphertext;
|
|
128
|
+
if (typeof handle === "string" &&
|
|
129
|
+
handle.startsWith(PENDING_DECISION_PREFIX)) {
|
|
130
|
+
found.push({ leaf: node, handle });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const child of Object.values(node)) {
|
|
134
|
+
collectPendingDecisionLeaves(child, found);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const applyCaptureDecision = (leaf, value, decision) => {
|
|
138
|
+
if (decision === "plain") {
|
|
139
|
+
leaf.value = value;
|
|
140
|
+
leaf.capture_mode = "plain";
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (decision !== "encrypt" || typeof value !== "string")
|
|
144
|
+
return;
|
|
145
|
+
const encryption = beginParameterEncryption(value);
|
|
146
|
+
if (!encryption)
|
|
147
|
+
return;
|
|
148
|
+
leaf.capture_mode = "encrypted";
|
|
149
|
+
leaf.key_version = encryption.keyVersion;
|
|
150
|
+
leaf.ciphertext = encryption.handle;
|
|
151
|
+
};
|
|
152
|
+
export const settleParameterCaptureDecisions = (node) => {
|
|
153
|
+
const leaves = [];
|
|
154
|
+
collectPendingDecisionLeaves(node, leaves);
|
|
155
|
+
if (leaves.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
const requested = leaves.map(({ handle }) => pendingDecisions.get(handle));
|
|
158
|
+
const decisions = decideParameterCaptures(requested
|
|
159
|
+
.filter((entry) => entry !== undefined)
|
|
160
|
+
.map(({ key, fact }) => ({ key, value: fact })));
|
|
161
|
+
let answered = 0;
|
|
162
|
+
for (const [index, { leaf, handle }] of leaves.entries()) {
|
|
163
|
+
delete leaf.ciphertext;
|
|
164
|
+
pendingDecisions.delete(handle);
|
|
165
|
+
const entry = requested[index];
|
|
166
|
+
if (entry === undefined)
|
|
167
|
+
continue;
|
|
168
|
+
applyCaptureDecision(leaf, entry.value, decisions[answered]);
|
|
169
|
+
answered += 1;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const settleTree = async (node) => {
|
|
173
|
+
if (Array.isArray(node)) {
|
|
174
|
+
for (const child of node)
|
|
175
|
+
await settleTree(child);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isRecord(node))
|
|
179
|
+
return;
|
|
180
|
+
if (node.capture_mode === "encrypted") {
|
|
181
|
+
await settleLeaf(node);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
for (const child of Object.values(node))
|
|
185
|
+
await settleTree(child);
|
|
186
|
+
};
|
|
187
|
+
const settleText = async (text) => {
|
|
188
|
+
if (!text.includes(PENDING_PREFIX) &&
|
|
189
|
+
!text.includes(PENDING_DECISION_PREFIX)) {
|
|
190
|
+
return text;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(text);
|
|
194
|
+
settleParameterCaptureDecisions(parsed);
|
|
195
|
+
await settleTree(parsed);
|
|
196
|
+
return JSON.stringify(parsed);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return text;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
export const settlePendingEncryptions = async (value) => {
|
|
203
|
+
if (typeof value === "string")
|
|
204
|
+
return (await settleText(value));
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
const settled = [];
|
|
207
|
+
for (const child of value)
|
|
208
|
+
settled.push(await settlePendingEncryptions(child));
|
|
209
|
+
return settled;
|
|
210
|
+
}
|
|
211
|
+
if (isRecord(value)) {
|
|
212
|
+
const settled = {};
|
|
213
|
+
for (const [key, child] of Object.entries(value)) {
|
|
214
|
+
settled[key] = await settlePendingEncryptions(child);
|
|
215
|
+
}
|
|
216
|
+
return settled;
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { hasParameterSnapshotLeafShape } from "../_generated/parameter-snapshot-leaf.js";
|
|
2
2
|
import { FORBIDDEN_PARAMETER_VALUE, MASKED_VALUE, MAX_SANITIZE_DEPTH, TRUNCATED_PARAMETER_VALUE, } from "../provider/constants.js";
|
|
3
3
|
import { PARAMETER_CAPTURE_MODE } from "../provider/contracts.js";
|
|
4
|
+
import { beginParameterCaptureDecision, canEncryptParameters, settleParameterCaptureDecisions, } from "./parameter-encryption.js";
|
|
4
5
|
import { isDeniedKey } from "./sanitize.js";
|
|
6
|
+
import { isSdkCoreWasmReady } from "./sdk-core-wasm.js";
|
|
5
7
|
const SNAPSHOT_UNAVAILABLE_KEY = "_cluebase_snapshot_unavailable";
|
|
6
8
|
const normalizeParameterLookupPath = (path) => {
|
|
7
9
|
return path.replace(/\[\d+\]/g, "[]");
|
|
@@ -60,6 +62,19 @@ const buildLeaf = (value, captureMode, released = false) => {
|
|
|
60
62
|
: {}),
|
|
61
63
|
};
|
|
62
64
|
};
|
|
65
|
+
const leafKeyOf = (path) => {
|
|
66
|
+
const trimmed = path.replace(/(?:\[\])+$/, "");
|
|
67
|
+
return trimmed.slice(trimmed.lastIndexOf(".") + 1);
|
|
68
|
+
};
|
|
69
|
+
const buildClassifiedLeaf = (path, value) => {
|
|
70
|
+
const masked = buildLeaf(value, PARAMETER_CAPTURE_MODE.maskedFingerprint);
|
|
71
|
+
if (!path)
|
|
72
|
+
return masked;
|
|
73
|
+
return {
|
|
74
|
+
...masked,
|
|
75
|
+
ciphertext: beginParameterCaptureDecision(leafKeyOf(path), value),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
63
78
|
export const buildUnavailableParameterSnapshot = () => ({
|
|
64
79
|
[SNAPSHOT_UNAVAILABLE_KEY]: buildLeaf("snapshot_unavailable", PARAMETER_CAPTURE_MODE.maskedFingerprint),
|
|
65
80
|
});
|
|
@@ -173,10 +188,15 @@ export const buildParameterSnapshotBundle = (value, options) => {
|
|
|
173
188
|
const matchedAllowedPath = path
|
|
174
189
|
? (allowedPathLookup.get(normalizeParameterLookupPath(path)) ?? null)
|
|
175
190
|
: null;
|
|
176
|
-
|
|
191
|
+
if (matchedAllowedPath !== null && !canEncryptParameters()) {
|
|
192
|
+
return {
|
|
193
|
+
snapshot: buildLeaf(current, PARAMETER_CAPTURE_MODE.maskedFingerprint, true),
|
|
194
|
+
allowedValueKeys: [matchedAllowedPath],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
177
197
|
return {
|
|
178
|
-
snapshot:
|
|
179
|
-
allowedValueKeys:
|
|
198
|
+
snapshot: buildClassifiedLeaf(path, current),
|
|
199
|
+
allowedValueKeys: [],
|
|
180
200
|
};
|
|
181
201
|
};
|
|
182
202
|
const rootBundle = walk(value, rootPathPrefix, 0);
|
|
@@ -185,6 +205,8 @@ export const buildParameterSnapshotBundle = (value, options) => {
|
|
|
185
205
|
!Array.isArray(rootBundle.snapshot)
|
|
186
206
|
? rootBundle.snapshot
|
|
187
207
|
: {};
|
|
208
|
+
if (isSdkCoreWasmReady())
|
|
209
|
+
settleParameterCaptureDecisions(snapshot);
|
|
188
210
|
return {
|
|
189
211
|
snapshot,
|
|
190
212
|
allowedValueKeys: [...new Set(rootBundle.allowedValueKeys)].sort(),
|
|
@@ -181,6 +181,7 @@ export type DeliveryDecision = {
|
|
|
181
181
|
readonly used_status_fallback?: boolean;
|
|
182
182
|
readonly dropped_indexes?: readonly number[];
|
|
183
183
|
readonly privacy_policy?: unknown;
|
|
184
|
+
readonly sdk_encryption?: unknown;
|
|
184
185
|
readonly directive_unreadable?: boolean;
|
|
185
186
|
};
|
|
186
187
|
export type DeliveryReport = {
|
|
@@ -4,6 +4,23 @@ export declare const initSdkCoreWasm: (input?: SdkCoreWasmInitInput) => Promise<
|
|
|
4
4
|
export declare const isSdkCoreWasmReady: () => boolean;
|
|
5
5
|
export declare const spanToObservationSourceEventJson: (spanJson: string, optionsJson: string) => string;
|
|
6
6
|
export declare const deliveryCommandJson: (requestJson: string) => string;
|
|
7
|
+
export type ParameterCaptureDecision = "plain" | "encrypt" | "mask";
|
|
8
|
+
export type ParameterValueFact = {
|
|
9
|
+
readonly type: "boolean";
|
|
10
|
+
} | {
|
|
11
|
+
readonly type: "number";
|
|
12
|
+
readonly number: number;
|
|
13
|
+
} | {
|
|
14
|
+
readonly type: "text";
|
|
15
|
+
readonly text: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "other";
|
|
18
|
+
};
|
|
19
|
+
export type ParameterCaptureRequestEntry = {
|
|
20
|
+
readonly key: string;
|
|
21
|
+
readonly value: ParameterValueFact;
|
|
22
|
+
};
|
|
23
|
+
export declare const decideParameterCaptures: (entries: readonly ParameterCaptureRequestEntry[]) => readonly ParameterCaptureDecision[];
|
|
7
24
|
export type IngestEndpointDescription = {
|
|
8
25
|
readonly ingestBatchUrl: string;
|
|
9
26
|
readonly browserTokenUrl: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import init, { deliveryCommandJson as wasmDeliveryCommandJson, spanToObservationSourceEventJson as wasmSpanToObservationSourceEventJson, } from "../wasm/pkg-web/cluebase_sdk_core_wasm.js";
|
|
1
|
+
import init, { deliveryCommandJson as wasmDeliveryCommandJson, parameterCaptureDecisionsJson as wasmParameterCaptureDecisionsJson, spanToObservationSourceEventJson as wasmSpanToObservationSourceEventJson, } from "../wasm/pkg-web/cluebase_sdk_core_wasm.js";
|
|
2
2
|
let readyPromise = null;
|
|
3
3
|
let ready = false;
|
|
4
4
|
export const initSdkCoreWasm = (input) => {
|
|
@@ -23,6 +23,13 @@ export const deliveryCommandJson = (requestJson) => {
|
|
|
23
23
|
requireReady();
|
|
24
24
|
return wasmDeliveryCommandJson(requestJson);
|
|
25
25
|
};
|
|
26
|
+
export const decideParameterCaptures = (entries) => {
|
|
27
|
+
if (entries.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
requireReady();
|
|
30
|
+
const reply = JSON.parse(wasmParameterCaptureDecisionsJson(JSON.stringify({ entries })));
|
|
31
|
+
return reply.decisions;
|
|
32
|
+
};
|
|
26
33
|
export const describeIngestEndpoint = async (endpoint) => {
|
|
27
34
|
if (!isSdkCoreWasmReady()) {
|
|
28
35
|
await initSdkCoreWasm();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION: "0.0.
|
|
1
|
+
export declare const SDK_VERSION: "0.0.10";
|
|
@@ -47,5 +47,7 @@ export declare const PARAMETER_CAPTURE_MODE: {
|
|
|
47
47
|
readonly maskedFingerprint: "masked_fingerprint";
|
|
48
48
|
readonly forbidden: "forbidden";
|
|
49
49
|
readonly truncated: "truncated";
|
|
50
|
+
readonly encrypted: "encrypted";
|
|
51
|
+
readonly plain: "plain";
|
|
50
52
|
};
|
|
51
53
|
export type ParameterCaptureMode = (typeof PARAMETER_CAPTURE_MODE)[keyof typeof PARAMETER_CAPTURE_MODE];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SdkEncryptionConfig = {
|
|
2
|
+
readonly keyVersion: number;
|
|
3
|
+
readonly publicKey: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const applyDeliveredSdkEncryption: (value: unknown) => void;
|
|
6
|
+
export declare const resetSdkEncryption: () => void;
|
|
7
|
+
export declare const currentSdkEncryption: () => SdkEncryptionConfig | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ingestSdkEncryptionSchema } from "../exporter/delivery-response-contract.js";
|
|
2
|
+
let appliedEncryption = null;
|
|
3
|
+
export const applyDeliveredSdkEncryption = (value) => {
|
|
4
|
+
const parsed = ingestSdkEncryptionSchema.safeParse(value);
|
|
5
|
+
appliedEncryption = parsed.success
|
|
6
|
+
? {
|
|
7
|
+
keyVersion: parsed.data.key_version,
|
|
8
|
+
publicKey: parsed.data.public_key,
|
|
9
|
+
}
|
|
10
|
+
: null;
|
|
11
|
+
};
|
|
12
|
+
export const resetSdkEncryption = () => {
|
|
13
|
+
appliedEncryption = null;
|
|
14
|
+
};
|
|
15
|
+
export const currentSdkEncryption = () => appliedEncryption;
|
package/dist/provider/types.d.ts
CHANGED
|
@@ -137,6 +137,8 @@ export type ParameterSnapshotLeaf = {
|
|
|
137
137
|
capture_mode: ParameterCaptureMode;
|
|
138
138
|
truncated_reason?: "max_event_bytes";
|
|
139
139
|
original_size_bytes?: number;
|
|
140
|
+
key_version?: number;
|
|
141
|
+
ciphertext?: string;
|
|
140
142
|
};
|
|
141
143
|
export type ParameterSnapshotValue = ParameterSnapshotLeaf | ParameterSnapshotValue[] | {
|
|
142
144
|
[key: string]: ParameterSnapshotValue;
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
export function deliveryCommandJson(request_json: string): string;
|
|
5
5
|
|
|
6
|
+
export function isCompleteAcceptedJson(result_json: string): boolean;
|
|
7
|
+
|
|
8
|
+
export function parameterCaptureDecisionsJson(request_json: string): string;
|
|
9
|
+
|
|
6
10
|
export function spanToObservationSourceEventJson(span_json: string, options_json: string): string;
|
|
7
11
|
|
|
8
12
|
export function spanToSdkSignalJson(span_json: string, options_json: string): string;
|
|
@@ -12,6 +16,8 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
12
16
|
export interface InitOutput {
|
|
13
17
|
readonly memory: WebAssembly.Memory;
|
|
14
18
|
readonly deliveryCommandJson: (a: number, b: number, c: number) => void;
|
|
19
|
+
readonly isCompleteAcceptedJson: (a: number, b: number, c: number) => void;
|
|
20
|
+
readonly parameterCaptureDecisionsJson: (a: number, b: number, c: number) => void;
|
|
15
21
|
readonly spanToObservationSourceEventJson: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
16
22
|
readonly spanToSdkSignalJson: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
17
23
|
readonly __wbindgen_export: (a: number) => void;
|
|
@@ -31,6 +31,59 @@ export function deliveryCommandJson(request_json) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} result_json
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
export function isCompleteAcceptedJson(result_json) {
|
|
39
|
+
try {
|
|
40
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
41
|
+
const ptr0 = passStringToWasm0(result_json, wasm.__wbindgen_export2, wasm.__wbindgen_export3);
|
|
42
|
+
const len0 = WASM_VECTOR_LEN;
|
|
43
|
+
wasm.isCompleteAcceptedJson(retptr, ptr0, len0);
|
|
44
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
45
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
46
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
47
|
+
if (r2) {
|
|
48
|
+
throw takeObject(r1);
|
|
49
|
+
}
|
|
50
|
+
return r0 !== 0;
|
|
51
|
+
} finally {
|
|
52
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} request_json
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function parameterCaptureDecisionsJson(request_json) {
|
|
61
|
+
let deferred3_0;
|
|
62
|
+
let deferred3_1;
|
|
63
|
+
try {
|
|
64
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
65
|
+
const ptr0 = passStringToWasm0(request_json, wasm.__wbindgen_export2, wasm.__wbindgen_export3);
|
|
66
|
+
const len0 = WASM_VECTOR_LEN;
|
|
67
|
+
wasm.parameterCaptureDecisionsJson(retptr, ptr0, len0);
|
|
68
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
69
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
70
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
71
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
72
|
+
var ptr2 = r0;
|
|
73
|
+
var len2 = r1;
|
|
74
|
+
if (r3) {
|
|
75
|
+
ptr2 = 0; len2 = 0;
|
|
76
|
+
throw takeObject(r2);
|
|
77
|
+
}
|
|
78
|
+
deferred3_0 = ptr2;
|
|
79
|
+
deferred3_1 = len2;
|
|
80
|
+
return getStringFromWasm0(ptr2, len2);
|
|
81
|
+
} finally {
|
|
82
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
83
|
+
wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
/**
|
|
35
88
|
* @param {string} span_json
|
|
36
89
|
* @param {string} options_json
|
|
Binary file
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const deliveryCommandJson: (a: number, b: number, c: number) => void;
|
|
5
|
+
export const isCompleteAcceptedJson: (a: number, b: number, c: number) => void;
|
|
6
|
+
export const parameterCaptureDecisionsJson: (a: number, b: number, c: number) => void;
|
|
5
7
|
export const spanToObservationSourceEventJson: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
6
8
|
export const spanToSdkSignalJson: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
7
9
|
export const __wbindgen_export: (a: number) => void;
|