@copilotkit/web-inspector 1.76.0 → 1.77.1
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/index.cjs +257 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +259 -141
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +419 -158
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/hud-config.cjs +93 -0
- package/dist/lib/hud-config.cjs.map +1 -0
- package/dist/lib/hud-config.mjs +92 -0
- package/dist/lib/hud-config.mjs.map +1 -0
- package/dist/lib/hud-defaults.cjs +19 -0
- package/dist/lib/hud-defaults.cjs.map +1 -0
- package/dist/lib/hud-defaults.mjs +18 -0
- package/dist/lib/hud-defaults.mjs.map +1 -0
- package/dist/lib/hud-loader.cjs +26 -0
- package/dist/lib/hud-loader.cjs.map +1 -0
- package/dist/lib/hud-loader.mjs +26 -0
- package/dist/lib/hud-loader.mjs.map +1 -0
- package/dist/lib/inspector-nav.cjs +1 -0
- package/dist/lib/inspector-nav.mjs +1 -1
- package/dist/lib/persistence.cjs +24 -7
- package/dist/lib/persistence.cjs.map +1 -1
- package/dist/lib/persistence.mjs +24 -8
- package/dist/lib/persistence.mjs.map +1 -1
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/styles/generated.cjs +1 -1
- package/dist/styles/generated.cjs.map +1 -1
- package/dist/styles/generated.mjs +1 -1
- package/dist/styles/generated.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
let semver = require("semver");
|
|
2
|
+
|
|
3
|
+
//#region src/lib/hud-config.ts
|
|
4
|
+
const HUD_FEATURE_IDS = ["threads", "learning"];
|
|
5
|
+
const HUD_LABEL_MAX_LENGTH = 32;
|
|
6
|
+
const HUD_DESCRIPTION_MAX_LENGTH = 140;
|
|
7
|
+
const frameworkPattern = /^[a-z][a-z0-9-]{0,31}$/;
|
|
8
|
+
const featureIdPattern = /^[a-z][a-z0-9-]{0,31}$/;
|
|
9
|
+
const destinationPattern = /^[a-z][a-z0-9-]{0,63}$/;
|
|
10
|
+
const controlCharacters = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
11
|
+
function record(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function onlyKeys(value, keys) {
|
|
15
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
16
|
+
}
|
|
17
|
+
function text(value, max) {
|
|
18
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max && !controlCharacters.test(value);
|
|
19
|
+
}
|
|
20
|
+
function parseFeature(value) {
|
|
21
|
+
if (!record(value) || !Object.keys(value).length || !onlyKeys(value, [
|
|
22
|
+
"label",
|
|
23
|
+
"description",
|
|
24
|
+
"destination"
|
|
25
|
+
])) return null;
|
|
26
|
+
if (value.label !== void 0 && !text(value.label, HUD_LABEL_MAX_LENGTH)) return null;
|
|
27
|
+
if (value.description !== void 0 && !text(value.description, HUD_DESCRIPTION_MAX_LENGTH)) return null;
|
|
28
|
+
if (value.destination !== void 0 && (typeof value.destination !== "string" || !destinationPattern.test(value.destination))) return null;
|
|
29
|
+
return {
|
|
30
|
+
...value.label === void 0 ? {} : { label: value.label },
|
|
31
|
+
...value.description === void 0 ? {} : { description: value.description },
|
|
32
|
+
...value.destination === void 0 ? {} : { destination: value.destination }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function parseRule(value) {
|
|
36
|
+
if (!record(value) || !onlyKeys(value, [
|
|
37
|
+
"framework",
|
|
38
|
+
"sdkVersion",
|
|
39
|
+
"features"
|
|
40
|
+
]) || typeof value.framework !== "string" || !frameworkPattern.test(value.framework) || !text(value.sdkVersion, 256) || (0, semver.validRange)(value.sdkVersion) === null || !record(value.features) || !Object.keys(value.features).length) return null;
|
|
41
|
+
const features = {};
|
|
42
|
+
for (const [id, entry] of Object.entries(value.features)) {
|
|
43
|
+
if (!featureIdPattern.test(id)) return null;
|
|
44
|
+
const feature = parseFeature(entry);
|
|
45
|
+
if (!feature) return null;
|
|
46
|
+
features[id] = feature;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
framework: value.framework,
|
|
50
|
+
sdkVersion: value.sdkVersion,
|
|
51
|
+
features
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Parse the public envelope. Any invalid entry rejects the whole feed. */
|
|
55
|
+
function parseHudFeed(value) {
|
|
56
|
+
if (!record(value) || !onlyKeys(value, ["schemaVersion", "rules"]) || value.schemaVersion !== 1 || !Array.isArray(value.rules) || value.rules.length > 100) return null;
|
|
57
|
+
const rules = [];
|
|
58
|
+
for (const entry of value.rules) {
|
|
59
|
+
const rule = parseRule(entry);
|
|
60
|
+
if (!rule) return null;
|
|
61
|
+
rules.push(rule);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
schemaVersion: 1,
|
|
65
|
+
rules
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Return the first rule for a development client with a known stable SDK. */
|
|
69
|
+
function matchHudRule(feed, context) {
|
|
70
|
+
const { development, framework, sdkVersion } = context;
|
|
71
|
+
if (!development || !framework || !sdkVersion || (0, semver.valid)(sdkVersion) === null || (0, semver.prerelease)(sdkVersion) !== null) return null;
|
|
72
|
+
return feed.rules.find((rule) => rule.framework === framework && (0, semver.satisfies)(sdkVersion, rule.sdkVersion)) ?? null;
|
|
73
|
+
}
|
|
74
|
+
/** Overlay the matching rule on the built-in content, field by field. */
|
|
75
|
+
function resolveHudContent(feed, context, options) {
|
|
76
|
+
const rule = feed ? matchHudRule(feed, context) : null;
|
|
77
|
+
const content = {};
|
|
78
|
+
for (const id of HUD_FEATURE_IDS) {
|
|
79
|
+
const fallback = options.defaults[id];
|
|
80
|
+
const override = rule?.features[id];
|
|
81
|
+
content[id] = {
|
|
82
|
+
label: override?.label ?? fallback.label,
|
|
83
|
+
description: override?.description ?? fallback.description,
|
|
84
|
+
destination: override?.destination !== void 0 && options.supportedDestinations.includes(override.destination) ? override.destination : fallback.destination
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
exports.parseHudFeed = parseHudFeed;
|
|
92
|
+
exports.resolveHudContent = resolveHudContent;
|
|
93
|
+
//# sourceMappingURL=hud-config.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-config.cjs","names":[],"sources":["../../src/lib/hud-config.ts"],"sourcesContent":["import { prerelease, satisfies, valid, validRange } from \"semver\";\nimport type { NotificationContext } from \"./notifications.js\";\n\n/** Versioned CDN HUD contract. Keep the Intelligence copy and fixtures aligned. */\nexport type HudFramework = \"react\" | \"vue\" | \"angular\";\nexport type HudFeatureId = \"threads\" | \"learning\";\nexport interface HudFeatureContent {\n label: string;\n description: string;\n destination: string;\n}\nexport type HudContent = Record<HudFeatureId, HudFeatureContent>;\nexport type HudFeatureOverride = Partial<HudFeatureContent>;\nexport interface HudRule {\n /** Frameworks unknown to this SDK are kept but never match. */\n framework: string;\n sdkVersion: string;\n /** Keyed by feature ID; IDs unknown to this SDK are kept but never applied. */\n features: Record<string, HudFeatureOverride>;\n}\nexport interface HudFeed {\n schemaVersion: 1;\n rules: HudRule[];\n}\nexport type HudContext = Pick<\n NotificationContext,\n \"development\" | \"framework\" | \"sdkVersion\"\n>;\nexport interface HudResolveOptions {\n defaults: HudContent;\n /** Destination keys this SDK can open; anything else keeps the default. */\n supportedDestinations: readonly string[];\n}\n\nexport const HUD_FEATURE_IDS: readonly HudFeatureId[] = [\"threads\", \"learning\"];\nexport const HUD_LABEL_MAX_LENGTH = 32;\nexport const HUD_DESCRIPTION_MAX_LENGTH = 140;\nconst frameworkPattern = /^[a-z][a-z0-9-]{0,31}$/;\nconst featureIdPattern = /^[a-z][a-z0-9-]{0,31}$/;\nconst destinationPattern = /^[a-z][a-z0-9-]{0,63}$/;\n// eslint-disable-next-line no-control-regex\nconst controlCharacters = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/;\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction onlyKeys(value: Record<string, unknown>, keys: string[]): boolean {\n return Object.keys(value).every((key) => keys.includes(key));\n}\nfunction text(value: unknown, max: number): value is string {\n return (\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= max &&\n !controlCharacters.test(value)\n );\n}\nfunction parseFeature(value: unknown): HudFeatureOverride | null {\n if (\n !record(value) ||\n !Object.keys(value).length ||\n !onlyKeys(value, [\"label\", \"description\", \"destination\"])\n )\n return null;\n if (value.label !== undefined && !text(value.label, HUD_LABEL_MAX_LENGTH))\n return null;\n if (\n value.description !== undefined &&\n !text(value.description, HUD_DESCRIPTION_MAX_LENGTH)\n )\n return null;\n if (\n value.destination !== undefined &&\n (typeof value.destination !== \"string\" ||\n !destinationPattern.test(value.destination))\n )\n return null;\n return {\n ...(value.label === undefined ? {} : { label: value.label }),\n ...(value.description === undefined\n ? {}\n : { description: value.description }),\n ...(value.destination === undefined\n ? {}\n : { destination: value.destination }),\n };\n}\nfunction parseRule(value: unknown): HudRule | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"framework\", \"sdkVersion\", \"features\"]) ||\n typeof value.framework !== \"string\" ||\n !frameworkPattern.test(value.framework) ||\n !text(value.sdkVersion, 256) ||\n validRange(value.sdkVersion) === null ||\n !record(value.features) ||\n !Object.keys(value.features).length\n )\n return null;\n const features: Record<string, HudFeatureOverride> = {};\n for (const [id, entry] of Object.entries(value.features)) {\n if (!featureIdPattern.test(id)) return null;\n const feature = parseFeature(entry);\n if (!feature) return null;\n features[id] = feature;\n }\n return {\n framework: value.framework,\n sdkVersion: value.sdkVersion,\n features,\n };\n}\n\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseHudFeed(value: unknown): HudFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"rules\"]) ||\n value.schemaVersion !== 1 ||\n !Array.isArray(value.rules) ||\n value.rules.length > 100\n )\n return null;\n const rules: HudRule[] = [];\n for (const entry of value.rules) {\n const rule = parseRule(entry);\n if (!rule) return null;\n rules.push(rule);\n }\n return { schemaVersion: 1, rules };\n}\n\n/** Return the first rule for a development client with a known stable SDK. */\nexport function matchHudRule(\n feed: HudFeed,\n context: HudContext,\n): HudRule | null {\n const { development, framework, sdkVersion } = context;\n if (\n !development ||\n !framework ||\n !sdkVersion ||\n valid(sdkVersion) === null ||\n prerelease(sdkVersion) !== null\n )\n return null;\n return (\n feed.rules.find(\n (rule) =>\n rule.framework === framework && satisfies(sdkVersion, rule.sdkVersion),\n ) ?? null\n );\n}\n\n/** Overlay the matching rule on the built-in content, field by field. */\nexport function resolveHudContent(\n feed: HudFeed | null,\n context: HudContext,\n options: HudResolveOptions,\n): HudContent {\n const rule = feed ? matchHudRule(feed, context) : null;\n const content = {} as HudContent;\n for (const id of HUD_FEATURE_IDS) {\n const fallback = options.defaults[id];\n const override = rule?.features[id];\n content[id] = {\n label: override?.label ?? fallback.label,\n description: override?.description ?? fallback.description,\n destination:\n override?.destination !== undefined &&\n options.supportedDestinations.includes(override.destination)\n ? override.destination\n : fallback.destination,\n };\n }\n return content;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { prerelease, satisfies, valid, validRange } from "semver";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/hud-config.ts
|
|
4
|
+
const HUD_FEATURE_IDS = ["threads", "learning"];
|
|
5
|
+
const HUD_LABEL_MAX_LENGTH = 32;
|
|
6
|
+
const HUD_DESCRIPTION_MAX_LENGTH = 140;
|
|
7
|
+
const frameworkPattern = /^[a-z][a-z0-9-]{0,31}$/;
|
|
8
|
+
const featureIdPattern = /^[a-z][a-z0-9-]{0,31}$/;
|
|
9
|
+
const destinationPattern = /^[a-z][a-z0-9-]{0,63}$/;
|
|
10
|
+
const controlCharacters = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
11
|
+
function record(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function onlyKeys(value, keys) {
|
|
15
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
16
|
+
}
|
|
17
|
+
function text(value, max) {
|
|
18
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max && !controlCharacters.test(value);
|
|
19
|
+
}
|
|
20
|
+
function parseFeature(value) {
|
|
21
|
+
if (!record(value) || !Object.keys(value).length || !onlyKeys(value, [
|
|
22
|
+
"label",
|
|
23
|
+
"description",
|
|
24
|
+
"destination"
|
|
25
|
+
])) return null;
|
|
26
|
+
if (value.label !== void 0 && !text(value.label, HUD_LABEL_MAX_LENGTH)) return null;
|
|
27
|
+
if (value.description !== void 0 && !text(value.description, HUD_DESCRIPTION_MAX_LENGTH)) return null;
|
|
28
|
+
if (value.destination !== void 0 && (typeof value.destination !== "string" || !destinationPattern.test(value.destination))) return null;
|
|
29
|
+
return {
|
|
30
|
+
...value.label === void 0 ? {} : { label: value.label },
|
|
31
|
+
...value.description === void 0 ? {} : { description: value.description },
|
|
32
|
+
...value.destination === void 0 ? {} : { destination: value.destination }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function parseRule(value) {
|
|
36
|
+
if (!record(value) || !onlyKeys(value, [
|
|
37
|
+
"framework",
|
|
38
|
+
"sdkVersion",
|
|
39
|
+
"features"
|
|
40
|
+
]) || typeof value.framework !== "string" || !frameworkPattern.test(value.framework) || !text(value.sdkVersion, 256) || validRange(value.sdkVersion) === null || !record(value.features) || !Object.keys(value.features).length) return null;
|
|
41
|
+
const features = {};
|
|
42
|
+
for (const [id, entry] of Object.entries(value.features)) {
|
|
43
|
+
if (!featureIdPattern.test(id)) return null;
|
|
44
|
+
const feature = parseFeature(entry);
|
|
45
|
+
if (!feature) return null;
|
|
46
|
+
features[id] = feature;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
framework: value.framework,
|
|
50
|
+
sdkVersion: value.sdkVersion,
|
|
51
|
+
features
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Parse the public envelope. Any invalid entry rejects the whole feed. */
|
|
55
|
+
function parseHudFeed(value) {
|
|
56
|
+
if (!record(value) || !onlyKeys(value, ["schemaVersion", "rules"]) || value.schemaVersion !== 1 || !Array.isArray(value.rules) || value.rules.length > 100) return null;
|
|
57
|
+
const rules = [];
|
|
58
|
+
for (const entry of value.rules) {
|
|
59
|
+
const rule = parseRule(entry);
|
|
60
|
+
if (!rule) return null;
|
|
61
|
+
rules.push(rule);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
schemaVersion: 1,
|
|
65
|
+
rules
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Return the first rule for a development client with a known stable SDK. */
|
|
69
|
+
function matchHudRule(feed, context) {
|
|
70
|
+
const { development, framework, sdkVersion } = context;
|
|
71
|
+
if (!development || !framework || !sdkVersion || valid(sdkVersion) === null || prerelease(sdkVersion) !== null) return null;
|
|
72
|
+
return feed.rules.find((rule) => rule.framework === framework && satisfies(sdkVersion, rule.sdkVersion)) ?? null;
|
|
73
|
+
}
|
|
74
|
+
/** Overlay the matching rule on the built-in content, field by field. */
|
|
75
|
+
function resolveHudContent(feed, context, options) {
|
|
76
|
+
const rule = feed ? matchHudRule(feed, context) : null;
|
|
77
|
+
const content = {};
|
|
78
|
+
for (const id of HUD_FEATURE_IDS) {
|
|
79
|
+
const fallback = options.defaults[id];
|
|
80
|
+
const override = rule?.features[id];
|
|
81
|
+
content[id] = {
|
|
82
|
+
label: override?.label ?? fallback.label,
|
|
83
|
+
description: override?.description ?? fallback.description,
|
|
84
|
+
destination: override?.destination !== void 0 && options.supportedDestinations.includes(override.destination) ? override.destination : fallback.destination
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { parseHudFeed, resolveHudContent };
|
|
92
|
+
//# sourceMappingURL=hud-config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-config.mjs","names":[],"sources":["../../src/lib/hud-config.ts"],"sourcesContent":["import { prerelease, satisfies, valid, validRange } from \"semver\";\nimport type { NotificationContext } from \"./notifications.js\";\n\n/** Versioned CDN HUD contract. Keep the Intelligence copy and fixtures aligned. */\nexport type HudFramework = \"react\" | \"vue\" | \"angular\";\nexport type HudFeatureId = \"threads\" | \"learning\";\nexport interface HudFeatureContent {\n label: string;\n description: string;\n destination: string;\n}\nexport type HudContent = Record<HudFeatureId, HudFeatureContent>;\nexport type HudFeatureOverride = Partial<HudFeatureContent>;\nexport interface HudRule {\n /** Frameworks unknown to this SDK are kept but never match. */\n framework: string;\n sdkVersion: string;\n /** Keyed by feature ID; IDs unknown to this SDK are kept but never applied. */\n features: Record<string, HudFeatureOverride>;\n}\nexport interface HudFeed {\n schemaVersion: 1;\n rules: HudRule[];\n}\nexport type HudContext = Pick<\n NotificationContext,\n \"development\" | \"framework\" | \"sdkVersion\"\n>;\nexport interface HudResolveOptions {\n defaults: HudContent;\n /** Destination keys this SDK can open; anything else keeps the default. */\n supportedDestinations: readonly string[];\n}\n\nexport const HUD_FEATURE_IDS: readonly HudFeatureId[] = [\"threads\", \"learning\"];\nexport const HUD_LABEL_MAX_LENGTH = 32;\nexport const HUD_DESCRIPTION_MAX_LENGTH = 140;\nconst frameworkPattern = /^[a-z][a-z0-9-]{0,31}$/;\nconst featureIdPattern = /^[a-z][a-z0-9-]{0,31}$/;\nconst destinationPattern = /^[a-z][a-z0-9-]{0,63}$/;\n// eslint-disable-next-line no-control-regex\nconst controlCharacters = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/;\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction onlyKeys(value: Record<string, unknown>, keys: string[]): boolean {\n return Object.keys(value).every((key) => keys.includes(key));\n}\nfunction text(value: unknown, max: number): value is string {\n return (\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= max &&\n !controlCharacters.test(value)\n );\n}\nfunction parseFeature(value: unknown): HudFeatureOverride | null {\n if (\n !record(value) ||\n !Object.keys(value).length ||\n !onlyKeys(value, [\"label\", \"description\", \"destination\"])\n )\n return null;\n if (value.label !== undefined && !text(value.label, HUD_LABEL_MAX_LENGTH))\n return null;\n if (\n value.description !== undefined &&\n !text(value.description, HUD_DESCRIPTION_MAX_LENGTH)\n )\n return null;\n if (\n value.destination !== undefined &&\n (typeof value.destination !== \"string\" ||\n !destinationPattern.test(value.destination))\n )\n return null;\n return {\n ...(value.label === undefined ? {} : { label: value.label }),\n ...(value.description === undefined\n ? {}\n : { description: value.description }),\n ...(value.destination === undefined\n ? {}\n : { destination: value.destination }),\n };\n}\nfunction parseRule(value: unknown): HudRule | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"framework\", \"sdkVersion\", \"features\"]) ||\n typeof value.framework !== \"string\" ||\n !frameworkPattern.test(value.framework) ||\n !text(value.sdkVersion, 256) ||\n validRange(value.sdkVersion) === null ||\n !record(value.features) ||\n !Object.keys(value.features).length\n )\n return null;\n const features: Record<string, HudFeatureOverride> = {};\n for (const [id, entry] of Object.entries(value.features)) {\n if (!featureIdPattern.test(id)) return null;\n const feature = parseFeature(entry);\n if (!feature) return null;\n features[id] = feature;\n }\n return {\n framework: value.framework,\n sdkVersion: value.sdkVersion,\n features,\n };\n}\n\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseHudFeed(value: unknown): HudFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"rules\"]) ||\n value.schemaVersion !== 1 ||\n !Array.isArray(value.rules) ||\n value.rules.length > 100\n )\n return null;\n const rules: HudRule[] = [];\n for (const entry of value.rules) {\n const rule = parseRule(entry);\n if (!rule) return null;\n rules.push(rule);\n }\n return { schemaVersion: 1, rules };\n}\n\n/** Return the first rule for a development client with a known stable SDK. */\nexport function matchHudRule(\n feed: HudFeed,\n context: HudContext,\n): HudRule | null {\n const { development, framework, sdkVersion } = context;\n if (\n !development ||\n !framework ||\n !sdkVersion ||\n valid(sdkVersion) === null ||\n prerelease(sdkVersion) !== null\n )\n return null;\n return (\n feed.rules.find(\n (rule) =>\n rule.framework === framework && satisfies(sdkVersion, rule.sdkVersion),\n ) ?? null\n );\n}\n\n/** Overlay the matching rule on the built-in content, field by field. */\nexport function resolveHudContent(\n feed: HudFeed | null,\n context: HudContext,\n options: HudResolveOptions,\n): HudContent {\n const rule = feed ? matchHudRule(feed, context) : null;\n const content = {} as HudContent;\n for (const id of HUD_FEATURE_IDS) {\n const fallback = options.defaults[id];\n const override = rule?.features[id];\n content[id] = {\n label: override?.label ?? fallback.label,\n description: override?.description ?? fallback.description,\n destination:\n override?.destination !== undefined &&\n options.supportedDestinations.includes(override.destination)\n ? override.destination\n : fallback.destination,\n };\n }\n return content;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/lib/hud-defaults.ts
|
|
3
|
+
/** Built-in HUD copy and destinations; a matching feed rule overrides fields. */
|
|
4
|
+
const HUD_DEFAULT_CONTENT = {
|
|
5
|
+
threads: {
|
|
6
|
+
label: "Rich Threads",
|
|
7
|
+
description: "Click to learn more",
|
|
8
|
+
destination: "threads"
|
|
9
|
+
},
|
|
10
|
+
learning: {
|
|
11
|
+
label: "Automatic Learning",
|
|
12
|
+
description: "Click to learn more",
|
|
13
|
+
destination: "memories"
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
exports.HUD_DEFAULT_CONTENT = HUD_DEFAULT_CONTENT;
|
|
19
|
+
//# sourceMappingURL=hud-defaults.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-defaults.cjs","names":[],"sources":["../../src/lib/hud-defaults.ts"],"sourcesContent":["import type { HudContent } from \"./hud-config.js\";\n\n/** Built-in HUD copy and destinations; a matching feed rule overrides fields. */\nexport const HUD_DEFAULT_CONTENT: HudContent = {\n threads: {\n label: \"Rich Threads\",\n description: \"Click to learn more\",\n destination: \"threads\",\n },\n learning: {\n label: \"Automatic Learning\",\n description: \"Click to learn more\",\n destination: \"memories\",\n },\n};\n"],"mappings":";;;AAGA,MAAa,sBAAkC;CAC7C,SAAS;EACP,OAAO;EACP,aAAa;EACb,aAAa;EACd;CACD,UAAU;EACR,OAAO;EACP,aAAa;EACb,aAAa;EACd;CACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/lib/hud-defaults.ts
|
|
2
|
+
/** Built-in HUD copy and destinations; a matching feed rule overrides fields. */
|
|
3
|
+
const HUD_DEFAULT_CONTENT = {
|
|
4
|
+
threads: {
|
|
5
|
+
label: "Rich Threads",
|
|
6
|
+
description: "Click to learn more",
|
|
7
|
+
destination: "threads"
|
|
8
|
+
},
|
|
9
|
+
learning: {
|
|
10
|
+
label: "Automatic Learning",
|
|
11
|
+
description: "Click to learn more",
|
|
12
|
+
destination: "memories"
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { HUD_DEFAULT_CONTENT };
|
|
18
|
+
//# sourceMappingURL=hud-defaults.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-defaults.mjs","names":[],"sources":["../../src/lib/hud-defaults.ts"],"sourcesContent":["import type { HudContent } from \"./hud-config.js\";\n\n/** Built-in HUD copy and destinations; a matching feed rule overrides fields. */\nexport const HUD_DEFAULT_CONTENT: HudContent = {\n threads: {\n label: \"Rich Threads\",\n description: \"Click to learn more\",\n destination: \"threads\",\n },\n learning: {\n label: \"Automatic Learning\",\n description: \"Click to learn more\",\n destination: \"memories\",\n },\n};\n"],"mappings":";;AAGA,MAAa,sBAAkC;CAC7C,SAAS;EACP,OAAO;EACP,aAAa;EACb,aAAa;EACd;CACD,UAAU;EACR,OAAO;EACP,aAAa;EACb,aAAa;EACd;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const require_hud_config = require('./hud-config.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/lib/hud-loader.ts
|
|
4
|
+
const HUD_FEED_URL = "https://cdn.copilotkit.ai/inspector-hud/v1.json";
|
|
5
|
+
let request = null;
|
|
6
|
+
/** Load the HUD feed once per page. The request carries no client data. */
|
|
7
|
+
function loadHudFeed() {
|
|
8
|
+
if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
9
|
+
request ??= Promise.resolve().then(() => fetch(HUD_FEED_URL, {
|
|
10
|
+
cache: "no-cache",
|
|
11
|
+
credentials: "omit"
|
|
12
|
+
})).then(async (response) => {
|
|
13
|
+
if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
|
|
14
|
+
const feed = require_hud_config.parseHudFeed(await response.json());
|
|
15
|
+
if (!feed) throw new Error("Feed failed validation");
|
|
16
|
+
return feed;
|
|
17
|
+
}).catch((error) => {
|
|
18
|
+
console.warn("[CopilotKit Inspector] Failed to load HUD content", error);
|
|
19
|
+
return null;
|
|
20
|
+
});
|
|
21
|
+
return request;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
exports.loadHudFeed = loadHudFeed;
|
|
26
|
+
//# sourceMappingURL=hud-loader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-loader.cjs","names":["parseHudFeed"],"sources":["../../src/lib/hud-loader.ts"],"sourcesContent":["import { parseHudFeed } from \"./hud-config.js\";\nimport type { HudFeed } from \"./hud-config.js\";\n\nexport const HUD_FEED_URL = \"https://cdn.copilotkit.ai/inspector-hud/v1.json\";\nlet request: Promise<HudFeed | null> | null = null;\n\n/** Load the HUD feed once per page. The request carries no client data. */\nexport function loadHudFeed(): Promise<HudFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n // Start inside a promise so even a synchronous fetch failure resolves null.\n request ??= Promise.resolve()\n .then(() => fetch(HUD_FEED_URL, { cache: \"no-cache\", credentials: \"omit\" }))\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseHudFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // The HUD keeps its built-in copy; the warning makes that debuggable.\n console.warn(\"[CopilotKit Inspector] Failed to load HUD content\", error);\n return null;\n });\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,eAAe;AAC5B,IAAI,UAA0C;;AAG9C,SAAgB,cAAuC;AACrD,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;AAE9B,aAAY,QAAQ,SAAS,CAC1B,WAAW,MAAM,cAAc;EAAE,OAAO;EAAY,aAAa;EAAQ,CAAC,CAAC,CAC3E,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAOA,gCAAa,MAAM,SAAS,MAAM,CAAC;AAChD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KAAK,qDAAqD,MAAM;AACxE,SAAO;GACP;AACJ,QAAO"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseHudFeed } from "./hud-config.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/hud-loader.ts
|
|
4
|
+
const HUD_FEED_URL = "https://cdn.copilotkit.ai/inspector-hud/v1.json";
|
|
5
|
+
let request = null;
|
|
6
|
+
/** Load the HUD feed once per page. The request carries no client data. */
|
|
7
|
+
function loadHudFeed() {
|
|
8
|
+
if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
9
|
+
request ??= Promise.resolve().then(() => fetch(HUD_FEED_URL, {
|
|
10
|
+
cache: "no-cache",
|
|
11
|
+
credentials: "omit"
|
|
12
|
+
})).then(async (response) => {
|
|
13
|
+
if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
|
|
14
|
+
const feed = parseHudFeed(await response.json());
|
|
15
|
+
if (!feed) throw new Error("Feed failed validation");
|
|
16
|
+
return feed;
|
|
17
|
+
}).catch((error) => {
|
|
18
|
+
console.warn("[CopilotKit Inspector] Failed to load HUD content", error);
|
|
19
|
+
return null;
|
|
20
|
+
});
|
|
21
|
+
return request;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { loadHudFeed };
|
|
26
|
+
//# sourceMappingURL=hud-loader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud-loader.mjs","names":[],"sources":["../../src/lib/hud-loader.ts"],"sourcesContent":["import { parseHudFeed } from \"./hud-config.js\";\nimport type { HudFeed } from \"./hud-config.js\";\n\nexport const HUD_FEED_URL = \"https://cdn.copilotkit.ai/inspector-hud/v1.json\";\nlet request: Promise<HudFeed | null> | null = null;\n\n/** Load the HUD feed once per page. The request carries no client data. */\nexport function loadHudFeed(): Promise<HudFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n // Start inside a promise so even a synchronous fetch failure resolves null.\n request ??= Promise.resolve()\n .then(() => fetch(HUD_FEED_URL, { cache: \"no-cache\", credentials: \"omit\" }))\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseHudFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // The HUD keeps its built-in copy; the warning makes that debuggable.\n console.warn(\"[CopilotKit Inspector] Failed to load HUD content\", error);\n return null;\n });\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,eAAe;AAC5B,IAAI,UAA0C;;AAG9C,SAAgB,cAAuC;AACrD,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;AAE9B,aAAY,QAAQ,SAAS,CAC1B,WAAW,MAAM,cAAc;EAAE,OAAO;EAAY,aAAa;EAAQ,CAAC,CAAC,CAC3E,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAO,aAAa,MAAM,SAAS,MAAM,CAAC;AAChD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KAAK,qDAAqD,MAAM;AACxE,SAAO;GACP;AACJ,QAAO"}
|
|
@@ -51,6 +51,7 @@ function shouldUseIconRail(args) {
|
|
|
51
51
|
|
|
52
52
|
//#endregion
|
|
53
53
|
exports.INSPECTOR_GROUPS = INSPECTOR_GROUPS;
|
|
54
|
+
exports.INSPECTOR_MENU_KEYS = INSPECTOR_MENU_KEYS;
|
|
54
55
|
exports.INSPECTOR_NAV_SECTIONS = INSPECTOR_NAV_SECTIONS;
|
|
55
56
|
exports.getGroupForMenu = getGroupForMenu;
|
|
56
57
|
exports.isInspectorMenuKey = isInspectorMenuKey;
|
|
@@ -49,5 +49,5 @@ function shouldUseIconRail(args) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
//#endregion
|
|
52
|
-
export { INSPECTOR_GROUPS, INSPECTOR_NAV_SECTIONS, getGroupForMenu, isInspectorMenuKey, shouldUseIconRail };
|
|
52
|
+
export { INSPECTOR_GROUPS, INSPECTOR_MENU_KEYS, INSPECTOR_NAV_SECTIONS, getGroupForMenu, isInspectorMenuKey, shouldUseIconRail };
|
|
53
53
|
//# sourceMappingURL=inspector-nav.mjs.map
|
package/dist/lib/persistence.cjs
CHANGED
|
@@ -54,11 +54,16 @@ function isValidDockMode(value) {
|
|
|
54
54
|
}
|
|
55
55
|
const INSPECTOR_DISMISSAL_MIRROR_KEY = "cpk:inspector:dismissed_until";
|
|
56
56
|
const INSPECTOR_DISMISSAL_COOKIE_NAME = "cpk_inspector_dismissed_until";
|
|
57
|
-
const INSPECTOR_DISMISSAL_MAX_DURATION_MS =
|
|
57
|
+
const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
58
58
|
/** Return the active host-scoped Inspector dismissal deadline, if any. */
|
|
59
59
|
function loadInspectorDismissedUntil(now = Date.now()) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
60
|
+
const payload = parseInspectorDismissalPayload(readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME)) ?? parseInspectorDismissalPayload(readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY));
|
|
61
|
+
if (payload === null) return null;
|
|
62
|
+
if (payload.forever) {
|
|
63
|
+
saveInspectorDismissedForever(now);
|
|
64
|
+
return now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;
|
|
65
|
+
}
|
|
66
|
+
const { until } = payload;
|
|
62
67
|
if (until <= now) {
|
|
63
68
|
clearInspectorDismissal();
|
|
64
69
|
return null;
|
|
@@ -70,14 +75,21 @@ function loadInspectorDismissedUntil(now = Date.now()) {
|
|
|
70
75
|
}
|
|
71
76
|
return until;
|
|
72
77
|
}
|
|
78
|
+
/** Persist a dismissal that renews itself on every load and never expires. */
|
|
79
|
+
function saveInspectorDismissedForever(now = Date.now()) {
|
|
80
|
+
saveInspectorDismissedUntil(now + INSPECTOR_DISMISSAL_MAX_DURATION_MS, now, true);
|
|
81
|
+
}
|
|
73
82
|
/** Persist a dismissal across browser sessions and localhost ports. */
|
|
74
|
-
function saveInspectorDismissedUntil(until, now = Date.now()) {
|
|
83
|
+
function saveInspectorDismissedUntil(until, now = Date.now(), forever = false) {
|
|
75
84
|
if (!Number.isFinite(until) || until <= now) {
|
|
76
85
|
clearInspectorDismissal();
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
79
88
|
const boundedUntil = Math.min(until, now + INSPECTOR_DISMISSAL_MAX_DURATION_MS);
|
|
80
|
-
const payload = JSON.stringify({
|
|
89
|
+
const payload = JSON.stringify({
|
|
90
|
+
until: boundedUntil,
|
|
91
|
+
...forever ? { forever: true } : {}
|
|
92
|
+
});
|
|
81
93
|
writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, payload, `Max-Age=${Math.max(1, Math.ceil((boundedUntil - now) / 1e3))}`);
|
|
82
94
|
writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);
|
|
83
95
|
}
|
|
@@ -86,12 +98,16 @@ function clearInspectorDismissal() {
|
|
|
86
98
|
writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, "", "Max-Age=0");
|
|
87
99
|
removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);
|
|
88
100
|
}
|
|
89
|
-
/** Parse a finite deadline from either persistence layer. */
|
|
101
|
+
/** Parse a finite deadline and forever flag from either persistence layer. */
|
|
90
102
|
function parseInspectorDismissalPayload(raw) {
|
|
91
103
|
if (!raw) return null;
|
|
92
104
|
try {
|
|
93
105
|
const parsed = JSON.parse(raw);
|
|
94
|
-
|
|
106
|
+
if (typeof parsed.until !== "number" || !Number.isFinite(parsed.until)) return null;
|
|
107
|
+
return parsed.forever === true ? {
|
|
108
|
+
until: parsed.until,
|
|
109
|
+
forever: true
|
|
110
|
+
} : { until: parsed.until };
|
|
95
111
|
} catch {
|
|
96
112
|
return null;
|
|
97
113
|
}
|
|
@@ -335,6 +351,7 @@ exports.loadInspectorState = loadInspectorState;
|
|
|
335
351
|
exports.loadNotificationState = loadNotificationState;
|
|
336
352
|
exports.markTelemetryDisclosureShown = markTelemetryDisclosureShown;
|
|
337
353
|
exports.migrateAnnouncementReadState = migrateAnnouncementReadState;
|
|
354
|
+
exports.saveInspectorDismissedForever = saveInspectorDismissedForever;
|
|
338
355
|
exports.saveInspectorDismissedUntil = saveInspectorDismissedUntil;
|
|
339
356
|
exports.saveInspectorState = saveInspectorState;
|
|
340
357
|
exports.saveNotificationPulsedId = saveNotificationPulsedId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.cjs","names":["parseNotificationState","emptyNotificationState"],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import {\n emptyNotificationState,\n parseNotificationState,\n} from \"./notifications.js\";\nimport type { NotificationState, NotificationFeed } from \"./notifications.js\";\nimport type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Inspector dismissal is intentionally host-scoped rather than origin-scoped.\n// A developer who closes the Inspector on localhost:3000 should not see it\n// again on localhost:5173 during the same dismissal window. Cookies are shared\n// across ports on one host; localStorage is not.\nexport const INSPECTOR_DISMISSAL_MIRROR_KEY = \"cpk:inspector:dismissed_until\";\nexport const INSPECTOR_DISMISSAL_COOKIE_NAME = \"cpk_inspector_dismissed_until\";\nexport const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype InspectorDismissalPayload = Readonly<{ until: number }>;\n\n/** Return the active host-scoped Inspector dismissal deadline, if any. */\nexport function loadInspectorDismissedUntil(\n now: number = Date.now(),\n): number | null {\n const until =\n parseInspectorDismissalPayload(\n readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME),\n ) ??\n parseInspectorDismissalPayload(\n readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY),\n );\n\n if (until === null) return null;\n if (until <= now) {\n clearInspectorDismissal();\n return null;\n }\n const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n if (until > maximumUntil) {\n saveInspectorDismissedUntil(maximumUntil, now);\n return maximumUntil;\n }\n return until;\n}\n\n/** Persist a dismissal across browser sessions and localhost ports. */\nexport function saveInspectorDismissedUntil(\n until: number,\n now: number = Date.now(),\n): void {\n if (!Number.isFinite(until) || until <= now) {\n clearInspectorDismissal();\n return;\n }\n\n const boundedUntil = Math.min(\n until,\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n );\n const payload = JSON.stringify({\n until: boundedUntil,\n } satisfies InspectorDismissalPayload);\n const maxAgeSeconds = Math.max(1, Math.ceil((boundedUntil - now) / 1000));\n writeCookie(\n INSPECTOR_DISMISSAL_COOKIE_NAME,\n payload,\n `Max-Age=${maxAgeSeconds}`,\n );\n writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);\n}\n\n/** Clear both persistence layers, including an expired host cookie. */\nexport function clearInspectorDismissal(): void {\n writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, \"\", \"Max-Age=0\");\n removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);\n}\n\n/** Parse a finite deadline from either persistence layer. */\nfunction parseInspectorDismissalPayload(raw: string | null): number | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<InspectorDismissalPayload>;\n return typeof parsed.until === \"number\" && Number.isFinite(parsed.until)\n ? parsed.until\n : null;\n } catch {\n return null;\n }\n}\n\n// Obsolete pre-cookie state; the cookie and its mirror are migrated below.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== name) continue;\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents. Callers fall back to their\n // origin-scoped mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string, lifetime: string): void {\n if (typeof document === \"undefined\") return;\n try {\n // No Domain attribute: the cookie belongs to exactly this host, while\n // remaining shared by every port on that host.\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; ${lifetime}; SameSite=Lax`;\n } catch {\n // The localStorage mirror gives cookie-blocking browsers per-origin\n // persistence, and neither storage layer may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nconst NOTIFICATION_COOKIE = \"cpk_inspector_notifications_v1\";\nconst NOTIFICATION_STORAGE = \"cpk:inspector:notifications:v1\";\nconst NOTIFICATION_COOKIE_MAX_LENGTH = 1024;\nconst LEGACY_ANNOUNCEMENT_ID = \"16f7d877-49e3-41c3-9ca6-f951d3d8ba80\";\n\n/** Read host-wide acknowledgements from either the new compact cookie or an old full-state cookie. */\nfunction readNotificationAcknowledgements(): Pick<\n NotificationState,\n \"readIds\" | \"suppressedIds\"\n> {\n try {\n const raw = readCookie(NOTIFICATION_COOKIE);\n if (raw) {\n const value: unknown = JSON.parse(raw);\n if (value && typeof value === \"object\") {\n const state = parseNotificationState({\n ...emptyNotificationState(),\n ...value,\n });\n if (state)\n return { readIds: state.readIds, suppressedIds: state.suppressedIds };\n }\n }\n } catch {\n // A blocked or malformed cookie must not disrupt the Inspector.\n }\n return { readIds: [], suppressedIds: [] };\n}\n\n/** Load per-origin selection and merge host-wide read and suppressed notices. */\nexport function loadNotificationState(): NotificationState {\n const raw = readLocalStorageItem(NOTIFICATION_STORAGE);\n let localState = emptyNotificationState();\n if (raw) {\n try {\n localState = parseNotificationState(JSON.parse(raw)) ?? localState;\n } catch {\n // Keep the host acknowledgements when local storage is malformed.\n }\n }\n const host = readNotificationAcknowledgements();\n return {\n ...localState,\n readIds: [...new Set([...localState.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...localState.suppressedIds, ...host.suppressedIds]),\n ],\n };\n}\n\n/** Preserve a legacy acknowledgement only for the known announcement entering the new feed. */\nexport function migrateAnnouncementReadState(\n state: NotificationState,\n feed: NotificationFeed,\n): NotificationState {\n for (const raw of [\n readCookie(\"cpk_inspector_announcements\"),\n readLocalStorageItem(\"cpk:inspector:announcement_read\"),\n ]) {\n if (!raw) continue;\n try {\n const value: unknown = JSON.parse(raw);\n if (!value || typeof value !== \"object\" || !(\"timestamp\" in value))\n continue;\n const legacyNotice = feed.notifications.find(\n (notice) =>\n notice.id === LEGACY_ANNOUNCEMENT_ID &&\n notice.publishedAt === value.timestamp,\n );\n if (!legacyNotice) continue;\n const migrated = {\n ...state,\n readIds: [...new Set([...state.readIds, legacyNotice.id])],\n suppressedIds: [...new Set([...state.suppressedIds, legacyNotice.id])],\n };\n saveNotificationState(migrated);\n writeCookie(\"cpk_inspector_announcements\", \"\", \"Max-Age=0\");\n removeLocalStorageItem(\"cpk:inspector:announcement_read\");\n return migrated;\n } catch {\n // Malformed legacy data must not disrupt the host app.\n }\n }\n return state;\n}\n\n/** Save full state per origin and a bounded host-wide acknowledgement cookie. */\nexport function saveNotificationState(state: NotificationState): void {\n const host = readNotificationAcknowledgements();\n const merged = {\n ...state,\n readIds: [...new Set([...state.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...state.suppressedIds, ...host.suppressedIds]),\n ],\n };\n writeLocalStorageItem(NOTIFICATION_STORAGE, JSON.stringify(merged));\n const cookieState = {\n schemaVersion: 1,\n readIds: [...merged.readIds],\n suppressedIds: [...merged.suppressedIds],\n };\n let raw = JSON.stringify(cookieState);\n // Limit bytes sent with every localhost request. Full history stays per\n // origin when the host cookie keeps only the newest acknowledgements.\n while (encodeURIComponent(raw).length >= NOTIFICATION_COOKIE_MAX_LENGTH) {\n if (cookieState.readIds.length >= cookieState.suppressedIds.length)\n cookieState.readIds.shift();\n else cookieState.suppressedIds.shift();\n raw = JSON.stringify(cookieState);\n }\n writeCookie(NOTIFICATION_COOKIE, raw, \"Max-Age=31536000\");\n}\n\n/** ID-based pulse state is separate from the legacy announcement timestamp. */\nconst NOTIFICATION_PULSED_SESSION_KEY = \"cpk:inspector:notification-pulsed-id\";\nconst MAX_PULSED_NOTIFICATION_IDS = 100;\n\nfunction pulsedNotificationIds(raw: string | null): string[] {\n if (!raw) return [];\n // Earlier previews stored one ID directly under this key.\n if (!raw.startsWith(\"[\")) return [raw];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids)\n ? ids.filter((id): id is string => typeof id === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nexport function saveNotificationPulsedId(id: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const ids = pulsedNotificationIds(\n window.sessionStorage.getItem(NOTIFICATION_PULSED_SESSION_KEY),\n );\n if (!ids.includes(id)) ids.push(id);\n window.sessionStorage.setItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n JSON.stringify(ids.slice(-MAX_PULSED_NOTIFICATION_IDS)),\n );\n } catch {\n /* A lost suppression must not disrupt the host. */\n }\n}\nexport function hasNotificationPulsed(\n id: string,\n publishedAt: string,\n): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const current = window.sessionStorage.getItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n );\n if (current !== null) return pulsedNotificationIds(current).includes(id);\n if (loadAnnouncementPulsedTimestamp() === publishedAt) {\n saveNotificationPulsedId(id);\n return true;\n }\n } catch {\n /* Treat unavailable storage as a fresh tab. */\n }\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persistence.cjs","names":["parseNotificationState","emptyNotificationState"],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import {\n emptyNotificationState,\n parseNotificationState,\n} from \"./notifications.js\";\nimport type { NotificationState, NotificationFeed } from \"./notifications.js\";\nimport type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Inspector dismissal is intentionally host-scoped rather than origin-scoped.\n// A developer who closes the Inspector on localhost:3000 should not see it\n// again on localhost:5173 during the same dismissal window. Cookies are shared\n// across ports on one host; localStorage is not.\nexport const INSPECTOR_DISMISSAL_MIRROR_KEY = \"cpk:inspector:dismissed_until\";\nexport const INSPECTOR_DISMISSAL_COOKIE_NAME = \"cpk_inspector_dismissed_until\";\nexport const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 365 * 24 * 60 * 60 * 1000;\n\n// `forever` marks an \"Always hide\" dismissal. Browsers cap cookie lifetimes\n// (Chrome at 400 days), so instead of a far-future deadline it is renewed to a\n// full MAX_DURATION window every time it is read and never expires.\ntype InspectorDismissalPayload = Readonly<{ until: number; forever?: true }>;\n\n/** Return the active host-scoped Inspector dismissal deadline, if any. */\nexport function loadInspectorDismissedUntil(\n now: number = Date.now(),\n): number | null {\n const payload =\n parseInspectorDismissalPayload(\n readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME),\n ) ??\n parseInspectorDismissalPayload(\n readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY),\n );\n\n if (payload === null) return null;\n if (payload.forever) {\n saveInspectorDismissedForever(now);\n return now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n }\n const { until } = payload;\n if (until <= now) {\n clearInspectorDismissal();\n return null;\n }\n const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n if (until > maximumUntil) {\n saveInspectorDismissedUntil(maximumUntil, now);\n return maximumUntil;\n }\n return until;\n}\n\n/** Persist a dismissal that renews itself on every load and never expires. */\nexport function saveInspectorDismissedForever(now: number = Date.now()): void {\n saveInspectorDismissedUntil(\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n now,\n true,\n );\n}\n\n/** Persist a dismissal across browser sessions and localhost ports. */\nexport function saveInspectorDismissedUntil(\n until: number,\n now: number = Date.now(),\n forever = false,\n): void {\n if (!Number.isFinite(until) || until <= now) {\n clearInspectorDismissal();\n return;\n }\n\n const boundedUntil = Math.min(\n until,\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n );\n const payload = JSON.stringify({\n until: boundedUntil,\n ...(forever ? { forever: true } : {}),\n } satisfies InspectorDismissalPayload);\n const maxAgeSeconds = Math.max(1, Math.ceil((boundedUntil - now) / 1000));\n writeCookie(\n INSPECTOR_DISMISSAL_COOKIE_NAME,\n payload,\n `Max-Age=${maxAgeSeconds}`,\n );\n writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);\n}\n\n/** Clear both persistence layers, including an expired host cookie. */\nexport function clearInspectorDismissal(): void {\n writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, \"\", \"Max-Age=0\");\n removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);\n}\n\n/** Parse a finite deadline and forever flag from either persistence layer. */\nfunction parseInspectorDismissalPayload(\n raw: string | null,\n): InspectorDismissalPayload | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<InspectorDismissalPayload>;\n if (typeof parsed.until !== \"number\" || !Number.isFinite(parsed.until)) {\n return null;\n }\n return parsed.forever === true\n ? { until: parsed.until, forever: true }\n : { until: parsed.until };\n } catch {\n return null;\n }\n}\n\n// Obsolete pre-cookie state; the cookie and its mirror are migrated below.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== name) continue;\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents. Callers fall back to their\n // origin-scoped mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string, lifetime: string): void {\n if (typeof document === \"undefined\") return;\n try {\n // No Domain attribute: the cookie belongs to exactly this host, while\n // remaining shared by every port on that host.\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; ${lifetime}; SameSite=Lax`;\n } catch {\n // The localStorage mirror gives cookie-blocking browsers per-origin\n // persistence, and neither storage layer may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nconst NOTIFICATION_COOKIE = \"cpk_inspector_notifications_v1\";\nconst NOTIFICATION_STORAGE = \"cpk:inspector:notifications:v1\";\nconst NOTIFICATION_COOKIE_MAX_LENGTH = 1024;\nconst LEGACY_ANNOUNCEMENT_ID = \"16f7d877-49e3-41c3-9ca6-f951d3d8ba80\";\n\n/** Read host-wide acknowledgements from either the new compact cookie or an old full-state cookie. */\nfunction readNotificationAcknowledgements(): Pick<\n NotificationState,\n \"readIds\" | \"suppressedIds\"\n> {\n try {\n const raw = readCookie(NOTIFICATION_COOKIE);\n if (raw) {\n const value: unknown = JSON.parse(raw);\n if (value && typeof value === \"object\") {\n const state = parseNotificationState({\n ...emptyNotificationState(),\n ...value,\n });\n if (state)\n return { readIds: state.readIds, suppressedIds: state.suppressedIds };\n }\n }\n } catch {\n // A blocked or malformed cookie must not disrupt the Inspector.\n }\n return { readIds: [], suppressedIds: [] };\n}\n\n/** Load per-origin selection and merge host-wide read and suppressed notices. */\nexport function loadNotificationState(): NotificationState {\n const raw = readLocalStorageItem(NOTIFICATION_STORAGE);\n let localState = emptyNotificationState();\n if (raw) {\n try {\n localState = parseNotificationState(JSON.parse(raw)) ?? localState;\n } catch {\n // Keep the host acknowledgements when local storage is malformed.\n }\n }\n const host = readNotificationAcknowledgements();\n return {\n ...localState,\n readIds: [...new Set([...localState.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...localState.suppressedIds, ...host.suppressedIds]),\n ],\n };\n}\n\n/** Preserve a legacy acknowledgement only for the known announcement entering the new feed. */\nexport function migrateAnnouncementReadState(\n state: NotificationState,\n feed: NotificationFeed,\n): NotificationState {\n for (const raw of [\n readCookie(\"cpk_inspector_announcements\"),\n readLocalStorageItem(\"cpk:inspector:announcement_read\"),\n ]) {\n if (!raw) continue;\n try {\n const value: unknown = JSON.parse(raw);\n if (!value || typeof value !== \"object\" || !(\"timestamp\" in value))\n continue;\n const legacyNotice = feed.notifications.find(\n (notice) =>\n notice.id === LEGACY_ANNOUNCEMENT_ID &&\n notice.publishedAt === value.timestamp,\n );\n if (!legacyNotice) continue;\n const migrated = {\n ...state,\n readIds: [...new Set([...state.readIds, legacyNotice.id])],\n suppressedIds: [...new Set([...state.suppressedIds, legacyNotice.id])],\n };\n saveNotificationState(migrated);\n writeCookie(\"cpk_inspector_announcements\", \"\", \"Max-Age=0\");\n removeLocalStorageItem(\"cpk:inspector:announcement_read\");\n return migrated;\n } catch {\n // Malformed legacy data must not disrupt the host app.\n }\n }\n return state;\n}\n\n/** Save full state per origin and a bounded host-wide acknowledgement cookie. */\nexport function saveNotificationState(state: NotificationState): void {\n const host = readNotificationAcknowledgements();\n const merged = {\n ...state,\n readIds: [...new Set([...state.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...state.suppressedIds, ...host.suppressedIds]),\n ],\n };\n writeLocalStorageItem(NOTIFICATION_STORAGE, JSON.stringify(merged));\n const cookieState = {\n schemaVersion: 1,\n readIds: [...merged.readIds],\n suppressedIds: [...merged.suppressedIds],\n };\n let raw = JSON.stringify(cookieState);\n // Limit bytes sent with every localhost request. Full history stays per\n // origin when the host cookie keeps only the newest acknowledgements.\n while (encodeURIComponent(raw).length >= NOTIFICATION_COOKIE_MAX_LENGTH) {\n if (cookieState.readIds.length >= cookieState.suppressedIds.length)\n cookieState.readIds.shift();\n else cookieState.suppressedIds.shift();\n raw = JSON.stringify(cookieState);\n }\n writeCookie(NOTIFICATION_COOKIE, raw, \"Max-Age=31536000\");\n}\n\n/** ID-based pulse state is separate from the legacy announcement timestamp. */\nconst NOTIFICATION_PULSED_SESSION_KEY = \"cpk:inspector:notification-pulsed-id\";\nconst MAX_PULSED_NOTIFICATION_IDS = 100;\n\nfunction pulsedNotificationIds(raw: string | null): string[] {\n if (!raw) return [];\n // Earlier previews stored one ID directly under this key.\n if (!raw.startsWith(\"[\")) return [raw];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids)\n ? ids.filter((id): id is string => typeof id === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nexport function saveNotificationPulsedId(id: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const ids = pulsedNotificationIds(\n window.sessionStorage.getItem(NOTIFICATION_PULSED_SESSION_KEY),\n );\n if (!ids.includes(id)) ids.push(id);\n window.sessionStorage.setItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n JSON.stringify(ids.slice(-MAX_PULSED_NOTIFICATION_IDS)),\n );\n } catch {\n /* A lost suppression must not disrupt the host. */\n }\n}\nexport function hasNotificationPulsed(\n id: string,\n publishedAt: string,\n): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const current = window.sessionStorage.getItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n );\n if (current !== null) return pulsedNotificationIds(current).includes(id);\n if (loadAnnouncementPulsedTimestamp() === publishedAt) {\n saveNotificationPulsedId(id);\n return true;\n }\n } catch {\n /* Treat unavailable storage as a fresh tab. */\n }\n return false;\n}\n"],"mappings":"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"}
|