@genn-inc/cluebase-frontend-sdk 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/_generated/correlation-attribute-keys.js +4 -0
- package/dist/_generated/otel-span-conversion.d.ts +0 -34
- package/dist/_generated/otel-span-conversion.js +0 -32
- package/dist/context/action-context.d.ts +39 -0
- package/dist/context/action-context.js +298 -0
- package/dist/context/browser-storage.d.ts +0 -12
- package/dist/context/browser-storage.js +0 -13
- package/dist/context/identity.js +0 -4
- package/dist/context/organization.d.ts +0 -4
- package/dist/context/organization.js +0 -7
- package/dist/context/session.js +0 -3
- package/dist/context/tab.js +0 -5
- package/dist/context/trace.d.ts +13 -1
- package/dist/context/trace.js +119 -21
- package/dist/exporter/batch.d.ts +0 -12
- package/dist/exporter/batch.js +0 -27
- package/dist/exporter/browser-outbox.js +0 -17
- package/dist/exporter/client.d.ts +0 -15
- package/dist/exporter/client.js +0 -43
- package/dist/exporter/delivery-response-contract.js +0 -6
- package/dist/exporter/delivery-response.d.ts +0 -16
- package/dist/exporter/delivery-response.js +0 -19
- package/dist/exporter/frontend-resource.d.ts +0 -9
- package/dist/exporter/frontend-resource.js +0 -9
- package/dist/exporter/observation-source-event-exporter.d.ts +0 -28
- package/dist/exporter/observation-source-event-exporter.js +0 -18
- package/dist/exporter/parameter-snapshot.js +1 -24
- package/dist/exporter/raw-event-to-source-signal.js +18 -11
- package/dist/exporter/sanitize.d.ts +1 -12
- package/dist/exporter/sanitize.js +6 -39
- package/dist/exporter/source-event.d.ts +2 -0
- package/dist/exporter/span-to-source-signal.js +7 -3
- package/dist/exporter/text-fingerprint.d.ts +0 -8
- package/dist/exporter/text-fingerprint.js +0 -8
- package/dist/index.d.ts +0 -4
- package/dist/index.js +9 -39
- package/dist/instrumentation/action.js +3 -2
- package/dist/instrumentation/cluebase-authored-attributes.d.ts +0 -2
- package/dist/instrumentation/cluebase-authored-attributes.js +0 -23
- package/dist/instrumentation/common.d.ts +0 -63
- package/dist/instrumentation/common.js +0 -48
- package/dist/instrumentation/dom.d.ts +0 -25
- package/dist/instrumentation/dom.js +5 -41
- package/dist/instrumentation/drag.js +1 -2
- package/dist/instrumentation/form.js +1 -2
- package/dist/instrumentation/input.js +10 -8
- package/dist/instrumentation/naming-evidence.d.ts +0 -12
- package/dist/instrumentation/navigation.js +0 -2
- package/dist/instrumentation/network-request.d.ts +2 -2
- package/dist/instrumentation/network-request.js +1 -2
- package/dist/instrumentation/network-span-attributes.d.ts +0 -7
- package/dist/instrumentation/network-span-attributes.js +1 -3
- package/dist/instrumentation/network.js +6 -1
- package/dist/instrumentation/page.js +0 -2
- package/dist/instrumentation/performance.js +0 -7
- package/dist/integrations/react-router.js +0 -3
- package/dist/internal/internal-log.d.ts +0 -7
- package/dist/internal/internal-log.js +0 -11
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.d.ts +0 -7
- package/dist/provider/config.js +3 -16
- package/dist/provider/constants.d.ts +1 -17
- package/dist/provider/constants.js +1 -31
- package/dist/provider/privacy-policy-store.d.ts +0 -12
- package/dist/provider/privacy-policy-store.js +0 -12
- package/dist/provider/sdk-event-pipeline.d.ts +1 -1
- package/dist/provider/sdk-event-pipeline.js +41 -54
- package/dist/provider/sdk-otel-pipeline.js +50 -4
- package/dist/provider/sdk-runtime-surfaces.js +3 -1
- package/dist/provider/sdk.js +3 -23
- package/dist/provider/types.d.ts +3 -39
- package/dist/provider/usage-governor.js +0 -2
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@ After `pnpm add @genn-inc/cluebase-frontend-sdk`, the intended Next.js integrati
|
|
|
10
10
|
import cluebase from "@genn-inc/cluebase-frontend-sdk";
|
|
11
11
|
|
|
12
12
|
cluebase.init({
|
|
13
|
-
endpoint: process.env.NEXT_PUBLIC_CLUEBASE_API_BASE_URL!,
|
|
14
13
|
projectKey: process.env.NEXT_PUBLIC_CLUEBASE_PROJECT_KEY!,
|
|
15
14
|
});
|
|
16
15
|
|
|
@@ -18,16 +17,19 @@ cluebase.identify(user.id, { name: user.name });
|
|
|
18
17
|
cluebase.group("organization", organization.id, { name: organization.name });
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
For Vite, use `import.meta.env.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
For Vite, use `import.meta.env.VITE_CLUEBASE_PROJECT_KEY`. For React apps that
|
|
21
|
+
use the Create React App env contract, use
|
|
22
|
+
`process.env.REACT_APP_CLUEBASE_PROJECT_KEY`. Other frontend frameworks should
|
|
23
|
+
use the public project-key env name generated by Cluebase setup.
|
|
24
|
+
|
|
25
|
+
`endpoint` is optional and defaults to `https://api.cluebase.io`. Set it only
|
|
26
|
+
when the SDK must send to another Cluebase API base URL, such as a local
|
|
27
|
+
environment.
|
|
26
28
|
|
|
27
29
|
`projectKey` is the public ingest key. The API resolves authoritative
|
|
28
|
-
`tenantId`, `projectId`, and dev/prod `ProjectService` from that key.
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
`tenantId`, `projectId`, and dev/prod `ProjectService` from that key. The SDK
|
|
31
|
+
appends `/api/v1/ingest/browser-tokens` and the canonical browser observation
|
|
32
|
+
batch path internally.
|
|
31
33
|
|
|
32
34
|
## Responsibility boundary
|
|
33
35
|
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
export const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS = new Set([
|
|
10
10
|
"cluebase.actor.anonymous_id",
|
|
11
11
|
"cluebase.actor.user_id",
|
|
12
|
+
"cluebase.action.id",
|
|
13
|
+
"cluebase.action_id",
|
|
12
14
|
"cluebase.interaction.id",
|
|
13
15
|
"cluebase.interaction_id",
|
|
14
16
|
"cluebase.organization.id",
|
|
@@ -26,6 +28,8 @@ export const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS = new Set([
|
|
|
26
28
|
"cluebase.tab.id",
|
|
27
29
|
"cluebase.tab_id",
|
|
28
30
|
"cluebase.trace_id",
|
|
31
|
+
"http.request.header.x-cluebase-action-id",
|
|
32
|
+
"http.request.header.x_cluebase_action_id",
|
|
29
33
|
"enduser.id",
|
|
30
34
|
"mcp.client.name",
|
|
31
35
|
"mcp.client.version",
|
|
@@ -1,48 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 計測の区間 (span) を送信の形へ移す時の、時刻と属性の読み方の**正本**。
|
|
3
|
-
*
|
|
4
|
-
* ここは配る部品 2 つ(サーバー側・画面側)へ**書き出される元**です。
|
|
5
|
-
* `scripts/generate-shared-contract-tables.mjs` が、この中身をそのまま
|
|
6
|
-
* 各部品の `src/_generated/otel-span-conversion.ts` へ出します。
|
|
7
|
-
* 出した先を手で直しても、`--check` が落とします。
|
|
8
|
-
*
|
|
9
|
-
* なぜ写しではなく生成なのか。配る部品は顧客の手元で `tsc` だけで組み立てられ、
|
|
10
|
-
* まとめ込み (bundle) をしません。社内の共有部品は `private` なので、
|
|
11
|
-
* 「配る部品から共有部品を取り込む」形は取れません(顧客の手元で解決できない
|
|
12
|
-
* 取り込みになります)。**これが写しが存在していた理由です。**
|
|
13
|
-
*
|
|
14
|
-
* 中身の型を計測の枠組みから取らず、必要な形だけを書いています。そうしないと、
|
|
15
|
-
* 出した先の 2 つで属性の型の名前が違う(`SdkSignalAttributes` と
|
|
16
|
-
* `CanonicalJsonObject`)ため、同じ 1 つの文章にできません。
|
|
17
|
-
*/
|
|
18
|
-
/** 計測の枠組みが渡す時刻。[秒, ナノ秒] の 2 つ組。 */
|
|
19
1
|
type SpanHrTime = readonly [number, number] | undefined;
|
|
20
|
-
/** 属性の入れ物。中身の型は読む側が決めるので、ここでは問いません。 */
|
|
21
2
|
type SpanAttributes = {
|
|
22
3
|
readonly [key: string]: unknown;
|
|
23
4
|
};
|
|
24
|
-
/** 親の区間を持つかもしれない区間。 */
|
|
25
5
|
type SpanWithParent = {
|
|
26
6
|
readonly parentSpanContext?: {
|
|
27
7
|
readonly spanId?: string;
|
|
28
8
|
};
|
|
29
9
|
};
|
|
30
|
-
/**
|
|
31
|
-
* 時刻をナノ秒の文字列にする。
|
|
32
|
-
*
|
|
33
|
-
* 時刻が無い・数でない場合は「いま」で埋める。**送る形が壊れるより、
|
|
34
|
-
* 近い時刻で送るほうがよい**(受け取る側は時刻の欠けを直せない)。
|
|
35
|
-
*/
|
|
36
10
|
export declare function hrTimeToUnixNano(time: SpanHrTime): string;
|
|
37
|
-
/** ナノ秒の文字列を、読める時刻の文字列にする。 */
|
|
38
11
|
export declare function nanosToIso(nanos: string): string;
|
|
39
|
-
/** 属性から「中身のある文字列」を取り出す。空白だけなら無いものとして扱う。 */
|
|
40
12
|
export declare function attrString(attributes: SpanAttributes, key: string): string | null;
|
|
41
|
-
/**
|
|
42
|
-
* 親の区間の識別子。
|
|
43
|
-
*
|
|
44
|
-
* 属性に明示されていればそれを採る。計測の枠組みが持つ親より属性を優先するのは、
|
|
45
|
-
* 画面と受付をまたぐ繋がりを、こちらで組み直して入れているため。
|
|
46
|
-
*/
|
|
47
13
|
export declare function parentSpanId(span: SpanWithParent, attributes: SpanAttributes): string | null;
|
|
48
14
|
export {};
|
|
@@ -5,29 +5,6 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 正本を直したらこの file も一緒に commit してください。CI が
|
|
7
7
|
// `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
|
|
8
|
-
/**
|
|
9
|
-
* 計測の区間 (span) を送信の形へ移す時の、時刻と属性の読み方の**正本**。
|
|
10
|
-
*
|
|
11
|
-
* ここは配る部品 2 つ(サーバー側・画面側)へ**書き出される元**です。
|
|
12
|
-
* `scripts/generate-shared-contract-tables.mjs` が、この中身をそのまま
|
|
13
|
-
* 各部品の `src/_generated/otel-span-conversion.ts` へ出します。
|
|
14
|
-
* 出した先を手で直しても、`--check` が落とします。
|
|
15
|
-
*
|
|
16
|
-
* なぜ写しではなく生成なのか。配る部品は顧客の手元で `tsc` だけで組み立てられ、
|
|
17
|
-
* まとめ込み (bundle) をしません。社内の共有部品は `private` なので、
|
|
18
|
-
* 「配る部品から共有部品を取り込む」形は取れません(顧客の手元で解決できない
|
|
19
|
-
* 取り込みになります)。**これが写しが存在していた理由です。**
|
|
20
|
-
*
|
|
21
|
-
* 中身の型を計測の枠組みから取らず、必要な形だけを書いています。そうしないと、
|
|
22
|
-
* 出した先の 2 つで属性の型の名前が違う(`SdkSignalAttributes` と
|
|
23
|
-
* `CanonicalJsonObject`)ため、同じ 1 つの文章にできません。
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* 時刻をナノ秒の文字列にする。
|
|
27
|
-
*
|
|
28
|
-
* 時刻が無い・数でない場合は「いま」で埋める。**送る形が壊れるより、
|
|
29
|
-
* 近い時刻で送るほうがよい**(受け取る側は時刻の欠けを直せない)。
|
|
30
|
-
*/
|
|
31
8
|
export function hrTimeToUnixNano(time) {
|
|
32
9
|
if (!time)
|
|
33
10
|
return `${Date.now()}000000`;
|
|
@@ -37,7 +14,6 @@ export function hrTimeToUnixNano(time) {
|
|
|
37
14
|
}
|
|
38
15
|
return (BigInt(seconds) * 1000000000n + BigInt(nanos)).toString();
|
|
39
16
|
}
|
|
40
|
-
/** ナノ秒の文字列を、読める時刻の文字列にする。 */
|
|
41
17
|
export function nanosToIso(nanos) {
|
|
42
18
|
try {
|
|
43
19
|
const ms = Number(BigInt(nanos) / 1000000n);
|
|
@@ -45,23 +21,15 @@ export function nanosToIso(nanos) {
|
|
|
45
21
|
return new Date(ms).toISOString();
|
|
46
22
|
}
|
|
47
23
|
catch {
|
|
48
|
-
// 試験の身代わりが壊れた時刻を渡すことがある。送る形は保つ。
|
|
49
24
|
}
|
|
50
25
|
return new Date().toISOString();
|
|
51
26
|
}
|
|
52
|
-
/** 属性から「中身のある文字列」を取り出す。空白だけなら無いものとして扱う。 */
|
|
53
27
|
export function attrString(attributes, key) {
|
|
54
28
|
const value = attributes[key];
|
|
55
29
|
return typeof value === "string" && value.trim().length > 0
|
|
56
30
|
? value.trim()
|
|
57
31
|
: null;
|
|
58
32
|
}
|
|
59
|
-
/**
|
|
60
|
-
* 親の区間の識別子。
|
|
61
|
-
*
|
|
62
|
-
* 属性に明示されていればそれを採る。計測の枠組みが持つ親より属性を優先するのは、
|
|
63
|
-
* 画面と受付をまたぐ繋がりを、こちらで組み直して入れているため。
|
|
64
|
-
*/
|
|
65
33
|
export function parentSpanId(span, attributes) {
|
|
66
34
|
return (attrString(attributes, "cluebase.parent_span_id") ??
|
|
67
35
|
span.parentSpanContext?.spanId ??
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ActionContextSnapshot = {
|
|
2
|
+
actionId: string | null;
|
|
3
|
+
interactionId: string;
|
|
4
|
+
interactionInitiator: string | null;
|
|
5
|
+
interactionStartEventKind: string | null;
|
|
6
|
+
targetStableKey: string | null;
|
|
7
|
+
formKey: string | null;
|
|
8
|
+
interactionScopeKind: string | null;
|
|
9
|
+
interactionScopeKey: string | null;
|
|
10
|
+
traceId: string;
|
|
11
|
+
activeSpanId: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare class PrivateActionContextManager {
|
|
14
|
+
private readonly readAmbientContext;
|
|
15
|
+
private current;
|
|
16
|
+
private synchronousEventContext;
|
|
17
|
+
private synchronousEventGeneration;
|
|
18
|
+
private enabled;
|
|
19
|
+
private eventContexts;
|
|
20
|
+
private eventRegistrations;
|
|
21
|
+
private eventCleanups;
|
|
22
|
+
private restoreFunctions;
|
|
23
|
+
private ownedWrapperRecords;
|
|
24
|
+
constructor(readAmbientContext: () => ActionContextSnapshot | null);
|
|
25
|
+
enable(): void;
|
|
26
|
+
disable(): void;
|
|
27
|
+
getCurrent(): ActionContextSnapshot | null;
|
|
28
|
+
getSynchronousEventContext(): ActionContextSnapshot | null;
|
|
29
|
+
isEnabled(): boolean;
|
|
30
|
+
bindEvent(event: Event): void;
|
|
31
|
+
run<T>(snapshot: ActionContextSnapshot, callback: () => T): T;
|
|
32
|
+
private capture;
|
|
33
|
+
private wrapCallback;
|
|
34
|
+
private ownWrapper;
|
|
35
|
+
private patchPromiseCallbacks;
|
|
36
|
+
private patchTimerCallbacks;
|
|
37
|
+
private patchGlobalCallback;
|
|
38
|
+
private patchEventCallbacks;
|
|
39
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
const ownedWrappers = new WeakMap();
|
|
2
|
+
const resolveRestoredFunction = (candidate) => {
|
|
3
|
+
let current = candidate;
|
|
4
|
+
let record = ownedWrappers.get(current);
|
|
5
|
+
while (record && !record.active) {
|
|
6
|
+
current = record.original;
|
|
7
|
+
record = ownedWrappers.get(current);
|
|
8
|
+
}
|
|
9
|
+
return current;
|
|
10
|
+
};
|
|
11
|
+
const captureFlag = (options) => typeof options === "boolean" ? options : options?.capture === true;
|
|
12
|
+
const isCallable = (value) => typeof value === "function";
|
|
13
|
+
const invokeListener = (listener, thisValue, event) => {
|
|
14
|
+
if (typeof listener === "function") {
|
|
15
|
+
return listener.call(thisValue, event);
|
|
16
|
+
}
|
|
17
|
+
return listener.handleEvent.call(listener, event);
|
|
18
|
+
};
|
|
19
|
+
export class PrivateActionContextManager {
|
|
20
|
+
constructor(readAmbientContext) {
|
|
21
|
+
this.readAmbientContext = readAmbientContext;
|
|
22
|
+
this.current = null;
|
|
23
|
+
this.synchronousEventContext = null;
|
|
24
|
+
this.synchronousEventGeneration = 0;
|
|
25
|
+
this.enabled = false;
|
|
26
|
+
this.eventContexts = new WeakMap();
|
|
27
|
+
this.eventRegistrations = new WeakMap();
|
|
28
|
+
this.eventCleanups = [];
|
|
29
|
+
this.restoreFunctions = [];
|
|
30
|
+
this.ownedWrapperRecords = [];
|
|
31
|
+
}
|
|
32
|
+
enable() {
|
|
33
|
+
if (this.enabled)
|
|
34
|
+
return;
|
|
35
|
+
this.enabled = true;
|
|
36
|
+
this.patchPromiseCallbacks();
|
|
37
|
+
this.patchTimerCallbacks();
|
|
38
|
+
this.patchEventCallbacks();
|
|
39
|
+
}
|
|
40
|
+
disable() {
|
|
41
|
+
if (!this.enabled)
|
|
42
|
+
return;
|
|
43
|
+
this.enabled = false;
|
|
44
|
+
for (const record of this.ownedWrapperRecords)
|
|
45
|
+
record.active = false;
|
|
46
|
+
for (const cleanup of this.eventCleanups.splice(0))
|
|
47
|
+
cleanup();
|
|
48
|
+
for (let index = this.restoreFunctions.length - 1; index >= 0; index -= 1) {
|
|
49
|
+
this.restoreFunctions[index]?.();
|
|
50
|
+
}
|
|
51
|
+
this.restoreFunctions = [];
|
|
52
|
+
this.eventRegistrations = new WeakMap();
|
|
53
|
+
this.eventContexts = new WeakMap();
|
|
54
|
+
this.current = null;
|
|
55
|
+
this.synchronousEventContext = null;
|
|
56
|
+
this.synchronousEventGeneration += 1;
|
|
57
|
+
this.ownedWrapperRecords = [];
|
|
58
|
+
}
|
|
59
|
+
getCurrent() {
|
|
60
|
+
return this.current;
|
|
61
|
+
}
|
|
62
|
+
getSynchronousEventContext() {
|
|
63
|
+
return this.synchronousEventContext;
|
|
64
|
+
}
|
|
65
|
+
isEnabled() {
|
|
66
|
+
return this.enabled;
|
|
67
|
+
}
|
|
68
|
+
bindEvent(event) {
|
|
69
|
+
const snapshot = this.current ?? this.readAmbientContext();
|
|
70
|
+
if (!snapshot)
|
|
71
|
+
return;
|
|
72
|
+
this.eventContexts.set(event, snapshot);
|
|
73
|
+
this.synchronousEventContext = snapshot;
|
|
74
|
+
const generation = ++this.synchronousEventGeneration;
|
|
75
|
+
void Promise.resolve().then(() => {
|
|
76
|
+
if (generation !== this.synchronousEventGeneration)
|
|
77
|
+
return;
|
|
78
|
+
this.synchronousEventContext = null;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
run(snapshot, callback) {
|
|
82
|
+
const previous = this.current;
|
|
83
|
+
this.current = snapshot;
|
|
84
|
+
try {
|
|
85
|
+
return callback();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
this.current = previous;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
capture() {
|
|
92
|
+
return this.current;
|
|
93
|
+
}
|
|
94
|
+
wrapCallback(callback, snapshot) {
|
|
95
|
+
if (!snapshot || !isCallable(callback))
|
|
96
|
+
return callback;
|
|
97
|
+
const manager = this;
|
|
98
|
+
return function (...args) {
|
|
99
|
+
if (!manager.enabled)
|
|
100
|
+
return callback.apply(this, args);
|
|
101
|
+
return manager.run(snapshot, () => callback.apply(this, args));
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
ownWrapper(wrapper, original) {
|
|
105
|
+
const record = { original, active: true };
|
|
106
|
+
ownedWrappers.set(wrapper, record);
|
|
107
|
+
this.ownedWrapperRecords.push(record);
|
|
108
|
+
}
|
|
109
|
+
patchPromiseCallbacks() {
|
|
110
|
+
const manager = this;
|
|
111
|
+
const promisePrototype = Promise.prototype;
|
|
112
|
+
for (const method of ["then", "catch", "finally"]) {
|
|
113
|
+
const original = promisePrototype[method];
|
|
114
|
+
if (!isCallable(original))
|
|
115
|
+
continue;
|
|
116
|
+
const wrapped = function (...args) {
|
|
117
|
+
if (!manager.enabled)
|
|
118
|
+
return original.call(this, ...args);
|
|
119
|
+
const snapshot = manager.capture();
|
|
120
|
+
if (method === "finally") {
|
|
121
|
+
return original.call(this, manager.wrapCallback(args[0], snapshot));
|
|
122
|
+
}
|
|
123
|
+
return original.call(this, manager.wrapCallback(args[0], snapshot), manager.wrapCallback(args[1], snapshot));
|
|
124
|
+
};
|
|
125
|
+
Object.defineProperty(promisePrototype, method, {
|
|
126
|
+
configurable: true,
|
|
127
|
+
enumerable: false,
|
|
128
|
+
writable: true,
|
|
129
|
+
value: wrapped,
|
|
130
|
+
});
|
|
131
|
+
this.ownWrapper(wrapped, original);
|
|
132
|
+
this.restoreFunctions.push(() => {
|
|
133
|
+
if (promisePrototype[method] === wrapped) {
|
|
134
|
+
Object.defineProperty(promisePrototype, method, {
|
|
135
|
+
configurable: true,
|
|
136
|
+
enumerable: false,
|
|
137
|
+
writable: true,
|
|
138
|
+
value: resolveRestoredFunction(original),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
patchTimerCallbacks() {
|
|
145
|
+
const globalObject = globalThis;
|
|
146
|
+
this.patchGlobalCallback("setTimeout", globalObject, "setTimeout");
|
|
147
|
+
this.patchGlobalCallback("setInterval", globalObject, "setInterval");
|
|
148
|
+
this.patchGlobalCallback("queueMicrotask", globalObject, "queueMicrotask");
|
|
149
|
+
this.patchGlobalCallback("requestAnimationFrame", globalObject, "requestAnimationFrame");
|
|
150
|
+
this.patchGlobalCallback("requestIdleCallback", globalObject, "requestIdleCallback");
|
|
151
|
+
this.patchGlobalCallback("setImmediate", globalObject, "setImmediate");
|
|
152
|
+
}
|
|
153
|
+
patchGlobalCallback(property, globalObject, methodName) {
|
|
154
|
+
const original = globalObject[property];
|
|
155
|
+
if (!isCallable(original))
|
|
156
|
+
return;
|
|
157
|
+
const manager = this;
|
|
158
|
+
const wrapped = function (...args) {
|
|
159
|
+
if (!manager.enabled)
|
|
160
|
+
return original.call(this, ...args);
|
|
161
|
+
const snapshot = manager.capture();
|
|
162
|
+
if (methodName === "queueMicrotask") {
|
|
163
|
+
return original.call(this, manager.wrapCallback(args[0], snapshot));
|
|
164
|
+
}
|
|
165
|
+
return original.call(this, manager.wrapCallback(args[0], snapshot), ...args.slice(1));
|
|
166
|
+
};
|
|
167
|
+
globalObject[property] = wrapped;
|
|
168
|
+
this.ownWrapper(wrapped, original);
|
|
169
|
+
this.restoreFunctions.push(() => {
|
|
170
|
+
if (globalObject[property] === wrapped) {
|
|
171
|
+
globalObject[property] =
|
|
172
|
+
resolveRestoredFunction(original);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
patchEventCallbacks() {
|
|
177
|
+
if (typeof EventTarget === "undefined")
|
|
178
|
+
return;
|
|
179
|
+
const prototype = EventTarget.prototype;
|
|
180
|
+
const originalAdd = prototype.addEventListener;
|
|
181
|
+
const originalRemove = prototype.removeEventListener;
|
|
182
|
+
const manager = this;
|
|
183
|
+
const wrappedAdd = function (type, listener, options) {
|
|
184
|
+
if (!manager.enabled) {
|
|
185
|
+
originalAdd.call(this, type, listener, options);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!listener) {
|
|
189
|
+
originalAdd.call(this, type, listener, options);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const registrations = manager.eventRegistrations.get(this) ?? [];
|
|
193
|
+
if (registrations.some((registration) => registration.type === type &&
|
|
194
|
+
registration.listener === listener &&
|
|
195
|
+
captureFlag(registration.options) === captureFlag(options))) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (typeof options === "object" &&
|
|
199
|
+
options !== null &&
|
|
200
|
+
options.signal?.aborted) {
|
|
201
|
+
originalAdd.call(this, type, listener, options);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const registrationSnapshot = manager.capture();
|
|
205
|
+
const target = this;
|
|
206
|
+
const wrappedListener = function (event) {
|
|
207
|
+
const eventSnapshot = manager.eventContexts.get(event);
|
|
208
|
+
const snapshot = eventSnapshot ?? registrationSnapshot;
|
|
209
|
+
if (!manager.enabled)
|
|
210
|
+
return invokeListener(listener, this, event);
|
|
211
|
+
if (!snapshot)
|
|
212
|
+
return invokeListener(listener, this, event);
|
|
213
|
+
return manager.run(snapshot, () => invokeListener(listener, this, event));
|
|
214
|
+
};
|
|
215
|
+
let registration;
|
|
216
|
+
const abortHandler = () => registration.cleanup();
|
|
217
|
+
const cleanup = () => {
|
|
218
|
+
if (registration.removed)
|
|
219
|
+
return;
|
|
220
|
+
registration.removed = true;
|
|
221
|
+
originalRemove.call(target, type, wrappedListener, options);
|
|
222
|
+
const currentRegistrations = manager.eventRegistrations.get(target) ?? [];
|
|
223
|
+
const registrationIndex = currentRegistrations.indexOf(registration);
|
|
224
|
+
if (registrationIndex >= 0)
|
|
225
|
+
currentRegistrations.splice(registrationIndex, 1);
|
|
226
|
+
const cleanupIndex = manager.eventCleanups.indexOf(cleanup);
|
|
227
|
+
if (cleanupIndex >= 0)
|
|
228
|
+
manager.eventCleanups.splice(cleanupIndex, 1);
|
|
229
|
+
if (typeof options === "object" && options?.signal) {
|
|
230
|
+
originalRemove.call(options.signal, "abort", abortHandler);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
registration = {
|
|
234
|
+
type,
|
|
235
|
+
listener,
|
|
236
|
+
wrapped: wrappedListener,
|
|
237
|
+
options,
|
|
238
|
+
cleanup,
|
|
239
|
+
removed: false,
|
|
240
|
+
};
|
|
241
|
+
originalAdd.call(target, type, wrappedListener, options);
|
|
242
|
+
registrations.push(registration);
|
|
243
|
+
manager.eventCleanups.push(cleanup);
|
|
244
|
+
manager.eventRegistrations.set(target, registrations);
|
|
245
|
+
if (typeof options === "object" && options?.signal) {
|
|
246
|
+
originalAdd.call(options.signal, "abort", abortHandler, { once: true });
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const wrappedRemove = function (type, listener, options) {
|
|
250
|
+
if (!manager.enabled) {
|
|
251
|
+
originalRemove.call(this, type, listener, options);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!listener) {
|
|
255
|
+
originalRemove.call(this, type, listener, options);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const registrations = manager.eventRegistrations.get(this) ?? [];
|
|
259
|
+
const index = registrations.findIndex((registration) => registration.type === type &&
|
|
260
|
+
registration.listener === listener &&
|
|
261
|
+
captureFlag(registration.options) === captureFlag(options));
|
|
262
|
+
if (index < 0) {
|
|
263
|
+
originalRemove.call(this, type, listener, options);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const registration = registrations[index];
|
|
267
|
+
registrations.splice(index, 1);
|
|
268
|
+
const cleanupIndex = manager.eventCleanups.indexOf(registration.cleanup);
|
|
269
|
+
if (cleanupIndex >= 0)
|
|
270
|
+
manager.eventCleanups.splice(cleanupIndex, 1);
|
|
271
|
+
registration.cleanup();
|
|
272
|
+
};
|
|
273
|
+
Object.defineProperty(prototype, "addEventListener", {
|
|
274
|
+
configurable: true,
|
|
275
|
+
value: wrappedAdd,
|
|
276
|
+
});
|
|
277
|
+
Object.defineProperty(prototype, "removeEventListener", {
|
|
278
|
+
configurable: true,
|
|
279
|
+
value: wrappedRemove,
|
|
280
|
+
});
|
|
281
|
+
this.ownWrapper(wrappedAdd, originalAdd);
|
|
282
|
+
this.ownWrapper(wrappedRemove, originalRemove);
|
|
283
|
+
this.restoreFunctions.push(() => {
|
|
284
|
+
if (prototype.addEventListener === wrappedAdd) {
|
|
285
|
+
Object.defineProperty(prototype, "addEventListener", {
|
|
286
|
+
configurable: true,
|
|
287
|
+
value: resolveRestoredFunction(originalAdd),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (prototype.removeEventListener === wrappedRemove) {
|
|
291
|
+
Object.defineProperty(prototype, "removeEventListener", {
|
|
292
|
+
configurable: true,
|
|
293
|
+
value: resolveRestoredFunction(originalRemove),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 画面側の保管先を 1 か所で決める。
|
|
3
|
-
*
|
|
4
|
-
* 4 つの入れ物(利用者・組織・滞在・タブ)が同じ「使えるか試して、駄目なら
|
|
5
|
-
* メモリへ落とす」規則をそれぞれ持っていた。規則が 4 通りに分かれると、
|
|
6
|
-
* 5 つ目の入れ物を足す人がどれを写すか選べない(CLAUDE.md §3)。
|
|
7
|
-
*/
|
|
8
1
|
export type StorageLike = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
9
|
-
/** 保管先が使えないときの落とし先。ページを離れると消える。 */
|
|
10
2
|
export declare const createMemoryStorage: () => StorageLike;
|
|
11
|
-
/**
|
|
12
|
-
* 実際に 1 度書いて消してみてから返す。存在の確認だけでは足りない
|
|
13
|
-
* (private browsing や容量上限では、存在しても `setItem` が投げる)。
|
|
14
|
-
*/
|
|
15
3
|
export declare const resolveWebStorage: (kind: "local" | "session", probeKey: string) => StorageLike;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 画面側の保管先を 1 か所で決める。
|
|
3
|
-
*
|
|
4
|
-
* 4 つの入れ物(利用者・組織・滞在・タブ)が同じ「使えるか試して、駄目なら
|
|
5
|
-
* メモリへ落とす」規則をそれぞれ持っていた。規則が 4 通りに分かれると、
|
|
6
|
-
* 5 つ目の入れ物を足す人がどれを写すか選べない(CLAUDE.md §3)。
|
|
7
|
-
*/
|
|
8
|
-
/** 保管先が使えないときの落とし先。ページを離れると消える。 */
|
|
9
1
|
export const createMemoryStorage = () => {
|
|
10
2
|
const store = new Map();
|
|
11
3
|
return {
|
|
@@ -18,10 +10,6 @@ export const createMemoryStorage = () => {
|
|
|
18
10
|
},
|
|
19
11
|
};
|
|
20
12
|
};
|
|
21
|
-
/**
|
|
22
|
-
* 実際に 1 度書いて消してみてから返す。存在の確認だけでは足りない
|
|
23
|
-
* (private browsing や容量上限では、存在しても `setItem` が投げる)。
|
|
24
|
-
*/
|
|
25
13
|
export const resolveWebStorage = (kind, probeKey) => {
|
|
26
14
|
try {
|
|
27
15
|
const storage = kind === "local" ? globalThis.localStorage : globalThis.sessionStorage;
|
|
@@ -32,7 +20,6 @@ export const resolveWebStorage = (kind, probeKey) => {
|
|
|
32
20
|
}
|
|
33
21
|
}
|
|
34
22
|
catch {
|
|
35
|
-
// Ignore and fallback.
|
|
36
23
|
}
|
|
37
24
|
return createMemoryStorage();
|
|
38
25
|
};
|
package/dist/context/identity.js
CHANGED
|
@@ -90,7 +90,6 @@ export class IdentityManager {
|
|
|
90
90
|
this.storage.setItem(STORAGE_KEYS.userId, userId);
|
|
91
91
|
}
|
|
92
92
|
catch {
|
|
93
|
-
// Swallow to keep SDK non-blocking.
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
clearUser() {
|
|
@@ -100,7 +99,6 @@ export class IdentityManager {
|
|
|
100
99
|
this.storage.removeItem(STORAGE_KEYS.userId);
|
|
101
100
|
}
|
|
102
101
|
catch {
|
|
103
|
-
// Swallow to keep SDK non-blocking.
|
|
104
102
|
}
|
|
105
103
|
}
|
|
106
104
|
rotateAnonymousId() {
|
|
@@ -110,7 +108,6 @@ export class IdentityManager {
|
|
|
110
108
|
this.storage.setItem(STORAGE_KEYS.anonymousId, anonymousId);
|
|
111
109
|
}
|
|
112
110
|
catch {
|
|
113
|
-
// Swallow to keep SDK non-blocking.
|
|
114
111
|
}
|
|
115
112
|
return anonymousId;
|
|
116
113
|
}
|
|
@@ -126,7 +123,6 @@ export class IdentityManager {
|
|
|
126
123
|
this.storage.setItem(STORAGE_KEYS.anonymousId, anonymousId);
|
|
127
124
|
}
|
|
128
125
|
catch {
|
|
129
|
-
// Swallow to keep SDK non-blocking.
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
128
|
const userId = this.storage.getItem(STORAGE_KEYS.userId);
|
|
@@ -6,10 +6,6 @@ export declare class OrganizationManager {
|
|
|
6
6
|
private state;
|
|
7
7
|
constructor(storage?: StorageLike);
|
|
8
8
|
getContext(): OrganizationContext;
|
|
9
|
-
/**
|
|
10
|
-
* Set the MVP company context directly. Public `cluebase.group("organization")`
|
|
11
|
-
* must not create or mutate future account/workspace context.
|
|
12
|
-
*/
|
|
13
9
|
setOrganization(organizationId: string, traits?: ContextTraitInput): void;
|
|
14
10
|
reset(): void;
|
|
15
11
|
private loadState;
|
|
@@ -12,10 +12,6 @@ export class OrganizationManager {
|
|
|
12
12
|
traits: { ...this.state.traits },
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Set the MVP company context directly. Public `cluebase.group("organization")`
|
|
17
|
-
* must not create or mutate future account/workspace context.
|
|
18
|
-
*/
|
|
19
15
|
setOrganization(organizationId, traits) {
|
|
20
16
|
const sanitizedTraits = sanitizeContextTraits(traits);
|
|
21
17
|
this.state.organizationId = organizationId;
|
|
@@ -27,7 +23,6 @@ export class OrganizationManager {
|
|
|
27
23
|
this.storage.setItem(STORAGE_KEYS.organizationId, organizationId);
|
|
28
24
|
}
|
|
29
25
|
catch {
|
|
30
|
-
// Swallow to keep SDK non-blocking.
|
|
31
26
|
}
|
|
32
27
|
}
|
|
33
28
|
reset() {
|
|
@@ -39,7 +34,6 @@ export class OrganizationManager {
|
|
|
39
34
|
this.storage.removeItem(STORAGE_KEYS.organizationId);
|
|
40
35
|
}
|
|
41
36
|
catch {
|
|
42
|
-
// Swallow to keep SDK non-blocking.
|
|
43
37
|
}
|
|
44
38
|
}
|
|
45
39
|
loadState() {
|
|
@@ -50,7 +44,6 @@ export class OrganizationManager {
|
|
|
50
44
|
}
|
|
51
45
|
}
|
|
52
46
|
catch {
|
|
53
|
-
// Swallow to keep SDK non-blocking.
|
|
54
47
|
}
|
|
55
48
|
return {
|
|
56
49
|
organizationId,
|
package/dist/context/session.js
CHANGED
|
@@ -59,7 +59,6 @@ export class SessionManager {
|
|
|
59
59
|
this.storage.setItem(STORAGE_KEYS.sessionLastActivityAt, String(nowMs));
|
|
60
60
|
}
|
|
61
61
|
catch {
|
|
62
|
-
// Swallow to keep SDK non-blocking.
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
endSession(nowMs = Date.now()) {
|
|
@@ -91,7 +90,6 @@ export class SessionManager {
|
|
|
91
90
|
this.storage.setItem(STORAGE_KEYS.sessionLastActivityAt, String(nowMs));
|
|
92
91
|
}
|
|
93
92
|
catch {
|
|
94
|
-
// Swallow to keep SDK non-blocking.
|
|
95
93
|
}
|
|
96
94
|
return sessionId;
|
|
97
95
|
}
|
|
@@ -107,7 +105,6 @@ export class SessionManager {
|
|
|
107
105
|
this.storage.removeItem(STORAGE_KEYS.sessionLastActivityAt);
|
|
108
106
|
}
|
|
109
107
|
catch {
|
|
110
|
-
// Swallow to keep SDK non-blocking.
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
loadState() {
|