@copilotkit/web-inspector 1.73.3 → 1.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +250 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +24 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +251 -234
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1165 -591
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/notification-loader.cjs +38 -0
- package/dist/lib/notification-loader.cjs.map +1 -0
- package/dist/lib/notification-loader.mjs +38 -0
- package/dist/lib/notification-loader.mjs.map +1 -0
- package/dist/lib/notifications.cjs +173 -0
- package/dist/lib/notifications.cjs.map +1 -0
- package/dist/lib/notifications.d.cts +17 -0
- package/dist/lib/notifications.d.cts.map +1 -0
- package/dist/lib/notifications.d.mts +17 -0
- package/dist/lib/notifications.d.mts.map +1 -0
- package/dist/lib/notifications.mjs +168 -0
- package/dist/lib/notifications.mjs.map +1 -0
- package/dist/lib/persistence.cjs +124 -48
- package/dist/lib/persistence.cjs.map +1 -1
- package/dist/lib/persistence.mjs +121 -45
- package/dist/lib/persistence.mjs.map +1 -1
- package/dist/lib/telemetry.cjs +42 -0
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.d.cts.map +1 -1
- package/dist/lib/telemetry.d.mts.map +1 -1
- package/dist/lib/telemetry.mjs +35 -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/dist/styles/notification-article.cjs +244 -0
- package/dist/styles/notification-article.cjs.map +1 -0
- package/dist/styles/notification-article.mjs +244 -0
- package/dist/styles/notification-article.mjs.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const require_notifications = require('./notifications.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/lib/notification-loader.ts
|
|
4
|
+
const NOTIFICATION_FEED_URL = "https://cdn.copilotkit.ai/notifications/v1.json";
|
|
5
|
+
const requests = /* @__PURE__ */ new Map();
|
|
6
|
+
/** New SDKs read only cohorts; announcements.json remains the older SDK channel.
|
|
7
|
+
* Share one request per framework/version per page. Missing context gets the full
|
|
8
|
+
* feed so future servers cannot exclude notices the client may still match.
|
|
9
|
+
*/
|
|
10
|
+
function loadNotificationFeed(context) {
|
|
11
|
+
if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
12
|
+
const url = new URL(NOTIFICATION_FEED_URL);
|
|
13
|
+
if (context.framework && context.sdkVersion) {
|
|
14
|
+
url.searchParams.set("framework", context.framework);
|
|
15
|
+
url.searchParams.set("sdkVersion", context.sdkVersion);
|
|
16
|
+
}
|
|
17
|
+
const key = url.toString();
|
|
18
|
+
const existing = requests.get(key);
|
|
19
|
+
if (existing) return existing;
|
|
20
|
+
const request = fetch(key, {
|
|
21
|
+
cache: "no-cache",
|
|
22
|
+
credentials: "omit"
|
|
23
|
+
}).then(async (response) => {
|
|
24
|
+
if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
|
|
25
|
+
const feed = require_notifications.parseNotificationFeed(await response.json());
|
|
26
|
+
if (!feed) throw new Error("Feed failed validation");
|
|
27
|
+
return feed;
|
|
28
|
+
}).catch((error) => {
|
|
29
|
+
console.warn("[CopilotKit Inspector] Failed to load notifications", error);
|
|
30
|
+
return null;
|
|
31
|
+
});
|
|
32
|
+
requests.set(key, request);
|
|
33
|
+
return request;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
exports.loadNotificationFeed = loadNotificationFeed;
|
|
38
|
+
//# sourceMappingURL=notification-loader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-loader.cjs","names":["parseNotificationFeed"],"sources":["../../src/lib/notification-loader.ts"],"sourcesContent":["import { parseNotificationFeed } from \"./notifications.js\";\nimport type { NotificationContext, NotificationFeed } from \"./notifications.js\";\n\nexport const NOTIFICATION_FEED_URL =\n \"https://cdn.copilotkit.ai/notifications/v1.json\";\nconst requests = new Map<string, Promise<NotificationFeed | null>>();\n/** New SDKs read only cohorts; announcements.json remains the older SDK channel.\n * Share one request per framework/version per page. Missing context gets the full\n * feed so future servers cannot exclude notices the client may still match.\n */\nexport function loadNotificationFeed(\n context: Pick<NotificationContext, \"framework\" | \"sdkVersion\">,\n): Promise<NotificationFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n const url = new URL(NOTIFICATION_FEED_URL);\n if (context.framework && context.sdkVersion) {\n url.searchParams.set(\"framework\", context.framework);\n url.searchParams.set(\"sdkVersion\", context.sdkVersion);\n }\n const key = url.toString();\n const existing = requests.get(key);\n if (existing) return existing;\n const request = fetch(key, {\n cache: \"no-cache\",\n credentials: \"omit\",\n })\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseNotificationFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // Keep the host running, but make an empty What's New debuggable.\n console.warn(\n \"[CopilotKit Inspector] Failed to load notifications\",\n error,\n );\n return null;\n });\n requests.set(key, request);\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,wBACX;AACF,MAAM,2BAAW,IAAI,KAA+C;;;;;AAKpE,SAAgB,qBACd,SACkC;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;CAC9B,MAAM,MAAM,IAAI,IAAI,sBAAsB;AAC1C,KAAI,QAAQ,aAAa,QAAQ,YAAY;AAC3C,MAAI,aAAa,IAAI,aAAa,QAAQ,UAAU;AACpD,MAAI,aAAa,IAAI,cAAc,QAAQ,WAAW;;CAExD,MAAM,MAAM,IAAI,UAAU;CAC1B,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,MAAM,KAAK;EACzB,OAAO;EACP,aAAa;EACd,CAAC,CACC,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAOA,4CAAsB,MAAM,SAAS,MAAM,CAAC;AACzD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KACN,uDACA,MACD;AACD,SAAO;GACP;AACJ,UAAS,IAAI,KAAK,QAAQ;AAC1B,QAAO"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseNotificationFeed } from "./notifications.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/notification-loader.ts
|
|
4
|
+
const NOTIFICATION_FEED_URL = "https://cdn.copilotkit.ai/notifications/v1.json";
|
|
5
|
+
const requests = /* @__PURE__ */ new Map();
|
|
6
|
+
/** New SDKs read only cohorts; announcements.json remains the older SDK channel.
|
|
7
|
+
* Share one request per framework/version per page. Missing context gets the full
|
|
8
|
+
* feed so future servers cannot exclude notices the client may still match.
|
|
9
|
+
*/
|
|
10
|
+
function loadNotificationFeed(context) {
|
|
11
|
+
if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
12
|
+
const url = new URL(NOTIFICATION_FEED_URL);
|
|
13
|
+
if (context.framework && context.sdkVersion) {
|
|
14
|
+
url.searchParams.set("framework", context.framework);
|
|
15
|
+
url.searchParams.set("sdkVersion", context.sdkVersion);
|
|
16
|
+
}
|
|
17
|
+
const key = url.toString();
|
|
18
|
+
const existing = requests.get(key);
|
|
19
|
+
if (existing) return existing;
|
|
20
|
+
const request = fetch(key, {
|
|
21
|
+
cache: "no-cache",
|
|
22
|
+
credentials: "omit"
|
|
23
|
+
}).then(async (response) => {
|
|
24
|
+
if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
|
|
25
|
+
const feed = parseNotificationFeed(await response.json());
|
|
26
|
+
if (!feed) throw new Error("Feed failed validation");
|
|
27
|
+
return feed;
|
|
28
|
+
}).catch((error) => {
|
|
29
|
+
console.warn("[CopilotKit Inspector] Failed to load notifications", error);
|
|
30
|
+
return null;
|
|
31
|
+
});
|
|
32
|
+
requests.set(key, request);
|
|
33
|
+
return request;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { loadNotificationFeed };
|
|
38
|
+
//# sourceMappingURL=notification-loader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-loader.mjs","names":[],"sources":["../../src/lib/notification-loader.ts"],"sourcesContent":["import { parseNotificationFeed } from \"./notifications.js\";\nimport type { NotificationContext, NotificationFeed } from \"./notifications.js\";\n\nexport const NOTIFICATION_FEED_URL =\n \"https://cdn.copilotkit.ai/notifications/v1.json\";\nconst requests = new Map<string, Promise<NotificationFeed | null>>();\n/** New SDKs read only cohorts; announcements.json remains the older SDK channel.\n * Share one request per framework/version per page. Missing context gets the full\n * feed so future servers cannot exclude notices the client may still match.\n */\nexport function loadNotificationFeed(\n context: Pick<NotificationContext, \"framework\" | \"sdkVersion\">,\n): Promise<NotificationFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n const url = new URL(NOTIFICATION_FEED_URL);\n if (context.framework && context.sdkVersion) {\n url.searchParams.set(\"framework\", context.framework);\n url.searchParams.set(\"sdkVersion\", context.sdkVersion);\n }\n const key = url.toString();\n const existing = requests.get(key);\n if (existing) return existing;\n const request = fetch(key, {\n cache: \"no-cache\",\n credentials: \"omit\",\n })\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseNotificationFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // Keep the host running, but make an empty What's New debuggable.\n console.warn(\n \"[CopilotKit Inspector] Failed to load notifications\",\n error,\n );\n return null;\n });\n requests.set(key, request);\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,wBACX;AACF,MAAM,2BAAW,IAAI,KAA+C;;;;;AAKpE,SAAgB,qBACd,SACkC;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;CAC9B,MAAM,MAAM,IAAI,IAAI,sBAAsB;AAC1C,KAAI,QAAQ,aAAa,QAAQ,YAAY;AAC3C,MAAI,aAAa,IAAI,aAAa,QAAQ,UAAU;AACpD,MAAI,aAAa,IAAI,cAAc,QAAQ,WAAW;;CAExD,MAAM,MAAM,IAAI,UAAU;CAC1B,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,MAAM,KAAK;EACzB,OAAO;EACP,aAAa;EACd,CAAC,CACC,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAO,sBAAsB,MAAM,SAAS,MAAM,CAAC;AACzD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KACN,uDACA,MACD;AACD,SAAO;GACP;AACJ,UAAS,IAAI,KAAK,QAAQ;AAC1B,QAAO"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
let semver = require("semver");
|
|
2
|
+
|
|
3
|
+
//#region src/lib/notifications.ts
|
|
4
|
+
function emptyNotificationState() {
|
|
5
|
+
return {
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
eligibleIds: [],
|
|
8
|
+
readIds: [],
|
|
9
|
+
suppressedIds: [],
|
|
10
|
+
activeId: null
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const priorities = {
|
|
14
|
+
Low: 10,
|
|
15
|
+
Normal: 20,
|
|
16
|
+
High: 30,
|
|
17
|
+
Urgent: 40
|
|
18
|
+
};
|
|
19
|
+
const conditionValues = {
|
|
20
|
+
framework: [
|
|
21
|
+
"react",
|
|
22
|
+
"vue",
|
|
23
|
+
"angular"
|
|
24
|
+
],
|
|
25
|
+
intelligence: ["enabled", "disabled"],
|
|
26
|
+
deployment: ["managed", "self-hosted"]
|
|
27
|
+
};
|
|
28
|
+
function record(value) {
|
|
29
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function onlyKeys(value, keys) {
|
|
32
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
33
|
+
}
|
|
34
|
+
function text(value, max = 256) {
|
|
35
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
36
|
+
}
|
|
37
|
+
function isNotificationId(value) {
|
|
38
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
39
|
+
}
|
|
40
|
+
function ids(value) {
|
|
41
|
+
return Array.isArray(value) && value.every(isNotificationId) && new Set(value).size === value.length;
|
|
42
|
+
}
|
|
43
|
+
/** Validate authored conditions; unsupported predicates cannot broaden the audience. */
|
|
44
|
+
function validNotificationConditions(value) {
|
|
45
|
+
if (!record(value)) return false;
|
|
46
|
+
if (value.sdkVersion !== void 0 && value.framework === void 0) return false;
|
|
47
|
+
return Object.entries(value).every(([key, expected]) => {
|
|
48
|
+
if (!text(expected)) return false;
|
|
49
|
+
if (key === "sdkVersion") return (0, semver.validRange)(expected) !== null;
|
|
50
|
+
if (key === "plan") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);
|
|
51
|
+
return Object.prototype.hasOwnProperty.call(conditionValues, key) && (conditionValues[key]?.includes(expected) ?? false);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Parse the public envelope. Any invalid entry rejects the whole feed. */
|
|
55
|
+
function parseNotificationFeed(value) {
|
|
56
|
+
if (!record(value) || !onlyKeys(value, ["schemaVersion", "notifications"]) || value.schemaVersion !== 1) return null;
|
|
57
|
+
if (!Array.isArray(value.notifications) || value.notifications.length > 100) return null;
|
|
58
|
+
const notifications = [];
|
|
59
|
+
for (const notice of value.notifications) {
|
|
60
|
+
if (!record(notice) || !onlyKeys(notice, [
|
|
61
|
+
"id",
|
|
62
|
+
"title",
|
|
63
|
+
"body",
|
|
64
|
+
"publishedAt",
|
|
65
|
+
"audiences",
|
|
66
|
+
"priority",
|
|
67
|
+
"priorityOverride"
|
|
68
|
+
])) return null;
|
|
69
|
+
if (!isNotificationId(notice.id) || !text(notice.title) || !text(notice.body, 1e5) || !text(notice.publishedAt)) return null;
|
|
70
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(notice.publishedAt) || !Number.isFinite(Date.parse(notice.publishedAt)) || new Date(notice.publishedAt).toISOString() !== notice.publishedAt) return null;
|
|
71
|
+
if (!Array.isArray(notice.audiences) || !notice.audiences.length || notice.audiences.length > 100 || !notice.audiences.every(validNotificationConditions)) return null;
|
|
72
|
+
if (!text(notice.priority) || !Object.prototype.hasOwnProperty.call(priorities, notice.priority)) return null;
|
|
73
|
+
if (notice.priorityOverride !== void 0 && (typeof notice.priorityOverride !== "number" || !Number.isSafeInteger(notice.priorityOverride) || notice.priorityOverride < 0)) return null;
|
|
74
|
+
if (notifications.some((n) => n.id === notice.id)) return null;
|
|
75
|
+
notifications.push({
|
|
76
|
+
id: notice.id,
|
|
77
|
+
title: notice.title,
|
|
78
|
+
body: notice.body,
|
|
79
|
+
publishedAt: notice.publishedAt,
|
|
80
|
+
audiences: notice.audiences,
|
|
81
|
+
priority: notice.priority,
|
|
82
|
+
...notice.priorityOverride === void 0 ? {} : { priorityOverride: notice.priorityOverride }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
notifications
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Ignore malformed or incompatible browser state. */
|
|
91
|
+
function parseNotificationState(value) {
|
|
92
|
+
if (!record(value) || value.schemaVersion !== 1 || !ids(value.eligibleIds) || !ids(value.readIds) || !ids(value.suppressedIds) || value.activeId !== null && !isNotificationId(value.activeId)) return null;
|
|
93
|
+
return {
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
eligibleIds: value.eligibleIds,
|
|
96
|
+
readIds: value.readIds,
|
|
97
|
+
suppressedIds: value.suppressedIds,
|
|
98
|
+
activeId: value.activeId
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Internal priority never becomes a developer-facing badge. */
|
|
102
|
+
function notificationPriority(notice) {
|
|
103
|
+
return notice.priorityOverride ?? priorities[notice.priority];
|
|
104
|
+
}
|
|
105
|
+
/** Stable ordering used by authoring reports and first-time selection. */
|
|
106
|
+
function compareNotifications(a, b) {
|
|
107
|
+
return notificationPriority(b) - notificationPriority(a) || Date.parse(b.publishedAt) - Date.parse(a.publishedAt) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
108
|
+
}
|
|
109
|
+
/** Refresh eligibility without promoting the previously delivered backlog. */
|
|
110
|
+
function reconcileNotifications(state, feed, context) {
|
|
111
|
+
const eligible = feed.notifications.filter((n) => matchNotification(n, context).matches);
|
|
112
|
+
const quiet = (n) => state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);
|
|
113
|
+
const active = eligible.find((n) => n.id === state.activeId && !quiet(n)) ?? feed.notifications.find((n) => n.id === state.activeId && !quiet(n) && n.audiences.some((conditions) => {
|
|
114
|
+
const entries = Object.entries(conditions);
|
|
115
|
+
if (!entries.some(([key]) => context[key] === void 0)) return false;
|
|
116
|
+
const known = Object.fromEntries(entries.filter(([key]) => context[key] !== void 0));
|
|
117
|
+
return matchNotification({
|
|
118
|
+
...n,
|
|
119
|
+
audiences: [known]
|
|
120
|
+
}, context).matches;
|
|
121
|
+
}));
|
|
122
|
+
const newcomer = eligible.filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id)).sort(compareNotifications)[0];
|
|
123
|
+
const winner = active && (!newcomer || notificationPriority(active) >= notificationPriority(newcomer)) ? active : newcomer;
|
|
124
|
+
return {
|
|
125
|
+
...state,
|
|
126
|
+
eligibleIds: eligible.map((n) => n.id),
|
|
127
|
+
activeId: winner?.id ?? null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */
|
|
131
|
+
function acknowledgeNotification(state, noticeId) {
|
|
132
|
+
if (!state.eligibleIds.includes(noticeId)) return state;
|
|
133
|
+
const highlighted = state.activeId === noticeId;
|
|
134
|
+
return {
|
|
135
|
+
...state,
|
|
136
|
+
readIds: [...new Set([...state.readIds, noticeId])],
|
|
137
|
+
suppressedIds: [...new Set([...state.suppressedIds, ...highlighted ? state.eligibleIds : [noticeId]])],
|
|
138
|
+
activeId: highlighted ? null : state.activeId
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Explain targeting using the same rules in CI and the Inspector. */
|
|
142
|
+
function matchNotification(notice, context) {
|
|
143
|
+
if (!context.development) return {
|
|
144
|
+
matches: false,
|
|
145
|
+
reasons: ["Development only"]
|
|
146
|
+
};
|
|
147
|
+
if (!context.sdkVersion || !(0, semver.valid)(context.sdkVersion) || (0, semver.prerelease)(context.sdkVersion) !== null) return {
|
|
148
|
+
matches: false,
|
|
149
|
+
reasons: ["Known stable SDK required"]
|
|
150
|
+
};
|
|
151
|
+
const reasons = [];
|
|
152
|
+
const matches = notice.audiences.some((conditions) => {
|
|
153
|
+
return Object.entries(conditions).every(([field, expected]) => {
|
|
154
|
+
const actual = context[field];
|
|
155
|
+
const matches = field === "sdkVersion" ? actual !== void 0 && (0, semver.valid)(actual) !== null && (0, semver.prerelease)(actual) === null && (0, semver.satisfies)(actual, expected) : actual === expected;
|
|
156
|
+
if (!matches) reasons.push(`${field} ${actual === void 0 ? "is unknown" : `requires ${expected} (got ${actual})`}`);
|
|
157
|
+
return matches;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
matches,
|
|
162
|
+
reasons: matches ? [] : reasons
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
exports.acknowledgeNotification = acknowledgeNotification;
|
|
168
|
+
exports.compareNotifications = compareNotifications;
|
|
169
|
+
exports.emptyNotificationState = emptyNotificationState;
|
|
170
|
+
exports.parseNotificationFeed = parseNotificationFeed;
|
|
171
|
+
exports.parseNotificationState = parseNotificationState;
|
|
172
|
+
exports.reconcileNotifications = reconcileNotifications;
|
|
173
|
+
//# sourceMappingURL=notifications.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.cjs","names":[],"sources":["../../src/lib/notifications.ts"],"sourcesContent":["import { satisfies, valid, validRange, prerelease } from \"semver\";\n\n/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */\nexport type NotificationFramework = \"react\" | \"vue\" | \"angular\";\nexport type NotificationPriority = \"Low\" | \"Normal\" | \"High\" | \"Urgent\";\nexport interface NotificationConditions {\n framework?: NotificationFramework;\n sdkVersion?: string;\n intelligence?: \"enabled\" | \"disabled\";\n deployment?: \"managed\" | \"self-hosted\";\n plan?: string;\n}\nexport interface CohortNotification {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n audiences: NotificationConditions[];\n priority: NotificationPriority;\n priorityOverride?: number;\n}\nexport interface NotificationFeed {\n schemaVersion: 1;\n notifications: CohortNotification[];\n}\nexport interface NotificationContext extends Omit<\n NotificationConditions,\n \"sdkVersion\"\n> {\n development: boolean;\n sdkVersion?: string;\n}\nexport interface NotificationState {\n schemaVersion: 1;\n eligibleIds: string[];\n readIds: string[];\n suppressedIds: string[];\n activeId: string | null;\n}\nexport function emptyNotificationState(): NotificationState {\n return {\n schemaVersion: 1,\n eligibleIds: [],\n readIds: [],\n suppressedIds: [],\n activeId: null,\n };\n}\nconst priorities: Record<NotificationPriority, number> = {\n Low: 10,\n Normal: 20,\n High: 30,\n Urgent: 40,\n};\nconst conditionValues: Record<string, readonly string[]> = {\n framework: [\"react\", \"vue\", \"angular\"],\n intelligence: [\"enabled\", \"disabled\"],\n deployment: [\"managed\", \"self-hosted\"],\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 = 256): value is string {\n return (\n typeof value === \"string\" && value.trim().length > 0 && value.length <= max\n );\n}\nexport function isNotificationId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n value,\n )\n );\n}\nfunction ids(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.every(isNotificationId) &&\n new Set(value).size === value.length\n );\n}\n/** Validate authored conditions; unsupported predicates cannot broaden the audience. */\nexport function validNotificationConditions(\n value: unknown,\n): value is NotificationConditions {\n if (!record(value)) return false;\n if (value.sdkVersion !== undefined && value.framework === undefined)\n return false;\n return Object.entries(value).every(([key, expected]) => {\n if (!text(expected)) return false;\n if (key === \"sdkVersion\") return validRange(expected) !== null;\n if (key === \"plan\") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);\n return (\n Object.prototype.hasOwnProperty.call(conditionValues, key) &&\n (conditionValues[key]?.includes(expected) ?? false)\n );\n });\n}\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseNotificationFeed(value: unknown): NotificationFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"notifications\"]) ||\n value.schemaVersion !== 1\n )\n return null;\n if (!Array.isArray(value.notifications) || value.notifications.length > 100)\n return null;\n const notifications: CohortNotification[] = [];\n for (const notice of value.notifications) {\n if (\n !record(notice) ||\n !onlyKeys(notice, [\n \"id\",\n \"title\",\n \"body\",\n \"publishedAt\",\n \"audiences\",\n \"priority\",\n \"priorityOverride\",\n ])\n )\n return null;\n if (\n !isNotificationId(notice.id) ||\n !text(notice.title) ||\n !text(notice.body, 100_000) ||\n !text(notice.publishedAt)\n )\n return null;\n if (\n !/^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d{3}Z$/.test(notice.publishedAt) ||\n !Number.isFinite(Date.parse(notice.publishedAt)) ||\n new Date(notice.publishedAt).toISOString() !== notice.publishedAt\n )\n return null;\n if (\n !Array.isArray(notice.audiences) ||\n !notice.audiences.length ||\n notice.audiences.length > 100 ||\n !notice.audiences.every(validNotificationConditions)\n )\n return null;\n if (\n !text(notice.priority) ||\n !Object.prototype.hasOwnProperty.call(priorities, notice.priority)\n )\n return null;\n if (\n notice.priorityOverride !== undefined &&\n (typeof notice.priorityOverride !== \"number\" ||\n !Number.isSafeInteger(notice.priorityOverride) ||\n notice.priorityOverride < 0)\n )\n return null;\n if (notifications.some((n) => n.id === notice.id)) return null;\n notifications.push({\n id: notice.id,\n title: notice.title,\n body: notice.body,\n publishedAt: notice.publishedAt,\n audiences: notice.audiences,\n priority: notice.priority as NotificationPriority,\n ...(notice.priorityOverride === undefined\n ? {}\n : { priorityOverride: notice.priorityOverride }),\n });\n }\n return { schemaVersion: 1, notifications };\n}\n/** Ignore malformed or incompatible browser state. */\nexport function parseNotificationState(\n value: unknown,\n): NotificationState | null {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !ids(value.eligibleIds) ||\n !ids(value.readIds) ||\n !ids(value.suppressedIds) ||\n (value.activeId !== null && !isNotificationId(value.activeId))\n )\n return null;\n return {\n schemaVersion: 1,\n eligibleIds: value.eligibleIds,\n readIds: value.readIds,\n suppressedIds: value.suppressedIds,\n activeId: value.activeId,\n };\n}\n/** Internal priority never becomes a developer-facing badge. */\nexport function notificationPriority(notice: CohortNotification): number {\n return notice.priorityOverride ?? priorities[notice.priority];\n}\n/** Stable ordering used by authoring reports and first-time selection. */\nexport function compareNotifications(\n a: CohortNotification,\n b: CohortNotification,\n): number {\n return (\n notificationPriority(b) - notificationPriority(a) ||\n Date.parse(b.publishedAt) - Date.parse(a.publishedAt) ||\n (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)\n );\n}\n/** Refresh eligibility without promoting the previously delivered backlog. */\nexport function reconcileNotifications(\n state: NotificationState,\n feed: NotificationFeed,\n context: NotificationContext,\n): NotificationState {\n const eligible = feed.notifications.filter(\n (n) => matchNotification(n, context).matches,\n );\n const quiet = (n: CohortNotification) =>\n state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);\n const active =\n eligible.find((n) => n.id === state.activeId && !quiet(n)) ??\n feed.notifications.find(\n (n) =>\n n.id === state.activeId &&\n !quiet(n) &&\n n.audiences.some((conditions) => {\n const entries = Object.entries(conditions);\n if (\n !entries.some(\n ([key]) =>\n context[key as keyof NotificationConditions] === undefined,\n )\n )\n return false;\n const known = Object.fromEntries(\n entries.filter(\n ([key]) =>\n context[key as keyof NotificationConditions] !== undefined,\n ),\n );\n return matchNotification({ ...n, audiences: [known] }, context)\n .matches;\n }),\n );\n const newcomer = eligible\n .filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id))\n .sort(compareNotifications)[0];\n const winner =\n active &&\n (!newcomer ||\n notificationPriority(active) >= notificationPriority(newcomer))\n ? active\n : newcomer;\n return {\n ...state,\n eligibleIds: eligible.map((n) => n.id),\n activeId: winner?.id ?? null,\n };\n}\n/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */\nexport function acknowledgeNotification(\n state: NotificationState,\n noticeId: string,\n): NotificationState {\n if (!state.eligibleIds.includes(noticeId)) return state;\n const highlighted = state.activeId === noticeId;\n return {\n ...state,\n readIds: [...new Set([...state.readIds, noticeId])],\n suppressedIds: [\n ...new Set([\n ...state.suppressedIds,\n ...(highlighted ? state.eligibleIds : [noticeId]),\n ]),\n ],\n activeId: highlighted ? null : state.activeId,\n };\n}\n/** Explain targeting using the same rules in CI and the Inspector. */\nexport function matchNotification(\n notice: CohortNotification,\n context: NotificationContext,\n): { matches: boolean; reasons: string[] } {\n if (!context.development)\n return { matches: false, reasons: [\"Development only\"] };\n if (\n !context.sdkVersion ||\n !valid(context.sdkVersion) ||\n prerelease(context.sdkVersion) !== null\n )\n return {\n matches: false,\n reasons: [\"Known stable SDK required\"],\n };\n const reasons: string[] = [];\n const matches = notice.audiences.some((conditions) => {\n return Object.entries(conditions).every(([field, expected]) => {\n const actual = context[field as keyof NotificationConditions];\n const matches =\n field === \"sdkVersion\"\n ? actual !== undefined &&\n valid(actual) !== null &&\n prerelease(actual) === null &&\n satisfies(actual, expected)\n : actual === expected;\n if (!matches)\n reasons.push(\n `${field} ${actual === undefined ? \"is unknown\" : `requires ${expected} (got ${actual})`}`,\n );\n return matches;\n });\n });\n return {\n matches,\n reasons: matches ? [] : reasons,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/lib/notifications.d.ts
|
|
2
|
+
/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */
|
|
3
|
+
type NotificationFramework = "react" | "vue" | "angular";
|
|
4
|
+
interface NotificationConditions {
|
|
5
|
+
framework?: NotificationFramework;
|
|
6
|
+
sdkVersion?: string;
|
|
7
|
+
intelligence?: "enabled" | "disabled";
|
|
8
|
+
deployment?: "managed" | "self-hosted";
|
|
9
|
+
plan?: string;
|
|
10
|
+
}
|
|
11
|
+
interface NotificationContext extends Omit<NotificationConditions, "sdkVersion"> {
|
|
12
|
+
development: boolean;
|
|
13
|
+
sdkVersion?: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { NotificationContext };
|
|
17
|
+
//# sourceMappingURL=notifications.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.d.cts","names":[],"sources":["../../src/lib/notifications.ts"],"mappings":";;KAGY,qBAAA;AAAA,UAEK,sBAAA;EACf,SAAA,GAAY,qBAAA;EACZ,UAAA;EACA,YAAA;EACA,UAAA;EACA,IAAA;AAAA;AAAA,UAee,mBAAA,SAA4B,IAAA,CAC3C,sBAAA;EAGA,WAAA;EACA,UAAA;AAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/lib/notifications.d.ts
|
|
2
|
+
/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */
|
|
3
|
+
type NotificationFramework = "react" | "vue" | "angular";
|
|
4
|
+
interface NotificationConditions {
|
|
5
|
+
framework?: NotificationFramework;
|
|
6
|
+
sdkVersion?: string;
|
|
7
|
+
intelligence?: "enabled" | "disabled";
|
|
8
|
+
deployment?: "managed" | "self-hosted";
|
|
9
|
+
plan?: string;
|
|
10
|
+
}
|
|
11
|
+
interface NotificationContext extends Omit<NotificationConditions, "sdkVersion"> {
|
|
12
|
+
development: boolean;
|
|
13
|
+
sdkVersion?: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { NotificationContext };
|
|
17
|
+
//# sourceMappingURL=notifications.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.d.mts","names":[],"sources":["../../src/lib/notifications.ts"],"mappings":";;KAGY,qBAAA;AAAA,UAEK,sBAAA;EACf,SAAA,GAAY,qBAAA;EACZ,UAAA;EACA,YAAA;EACA,UAAA;EACA,IAAA;AAAA;AAAA,UAee,mBAAA,SAA4B,IAAA,CAC3C,sBAAA;EAGA,WAAA;EACA,UAAA;AAAA"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { prerelease, satisfies, valid, validRange } from "semver";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/notifications.ts
|
|
4
|
+
function emptyNotificationState() {
|
|
5
|
+
return {
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
eligibleIds: [],
|
|
8
|
+
readIds: [],
|
|
9
|
+
suppressedIds: [],
|
|
10
|
+
activeId: null
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const priorities = {
|
|
14
|
+
Low: 10,
|
|
15
|
+
Normal: 20,
|
|
16
|
+
High: 30,
|
|
17
|
+
Urgent: 40
|
|
18
|
+
};
|
|
19
|
+
const conditionValues = {
|
|
20
|
+
framework: [
|
|
21
|
+
"react",
|
|
22
|
+
"vue",
|
|
23
|
+
"angular"
|
|
24
|
+
],
|
|
25
|
+
intelligence: ["enabled", "disabled"],
|
|
26
|
+
deployment: ["managed", "self-hosted"]
|
|
27
|
+
};
|
|
28
|
+
function record(value) {
|
|
29
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function onlyKeys(value, keys) {
|
|
32
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
33
|
+
}
|
|
34
|
+
function text(value, max = 256) {
|
|
35
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
36
|
+
}
|
|
37
|
+
function isNotificationId(value) {
|
|
38
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
39
|
+
}
|
|
40
|
+
function ids(value) {
|
|
41
|
+
return Array.isArray(value) && value.every(isNotificationId) && new Set(value).size === value.length;
|
|
42
|
+
}
|
|
43
|
+
/** Validate authored conditions; unsupported predicates cannot broaden the audience. */
|
|
44
|
+
function validNotificationConditions(value) {
|
|
45
|
+
if (!record(value)) return false;
|
|
46
|
+
if (value.sdkVersion !== void 0 && value.framework === void 0) return false;
|
|
47
|
+
return Object.entries(value).every(([key, expected]) => {
|
|
48
|
+
if (!text(expected)) return false;
|
|
49
|
+
if (key === "sdkVersion") return validRange(expected) !== null;
|
|
50
|
+
if (key === "plan") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);
|
|
51
|
+
return Object.prototype.hasOwnProperty.call(conditionValues, key) && (conditionValues[key]?.includes(expected) ?? false);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Parse the public envelope. Any invalid entry rejects the whole feed. */
|
|
55
|
+
function parseNotificationFeed(value) {
|
|
56
|
+
if (!record(value) || !onlyKeys(value, ["schemaVersion", "notifications"]) || value.schemaVersion !== 1) return null;
|
|
57
|
+
if (!Array.isArray(value.notifications) || value.notifications.length > 100) return null;
|
|
58
|
+
const notifications = [];
|
|
59
|
+
for (const notice of value.notifications) {
|
|
60
|
+
if (!record(notice) || !onlyKeys(notice, [
|
|
61
|
+
"id",
|
|
62
|
+
"title",
|
|
63
|
+
"body",
|
|
64
|
+
"publishedAt",
|
|
65
|
+
"audiences",
|
|
66
|
+
"priority",
|
|
67
|
+
"priorityOverride"
|
|
68
|
+
])) return null;
|
|
69
|
+
if (!isNotificationId(notice.id) || !text(notice.title) || !text(notice.body, 1e5) || !text(notice.publishedAt)) return null;
|
|
70
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(notice.publishedAt) || !Number.isFinite(Date.parse(notice.publishedAt)) || new Date(notice.publishedAt).toISOString() !== notice.publishedAt) return null;
|
|
71
|
+
if (!Array.isArray(notice.audiences) || !notice.audiences.length || notice.audiences.length > 100 || !notice.audiences.every(validNotificationConditions)) return null;
|
|
72
|
+
if (!text(notice.priority) || !Object.prototype.hasOwnProperty.call(priorities, notice.priority)) return null;
|
|
73
|
+
if (notice.priorityOverride !== void 0 && (typeof notice.priorityOverride !== "number" || !Number.isSafeInteger(notice.priorityOverride) || notice.priorityOverride < 0)) return null;
|
|
74
|
+
if (notifications.some((n) => n.id === notice.id)) return null;
|
|
75
|
+
notifications.push({
|
|
76
|
+
id: notice.id,
|
|
77
|
+
title: notice.title,
|
|
78
|
+
body: notice.body,
|
|
79
|
+
publishedAt: notice.publishedAt,
|
|
80
|
+
audiences: notice.audiences,
|
|
81
|
+
priority: notice.priority,
|
|
82
|
+
...notice.priorityOverride === void 0 ? {} : { priorityOverride: notice.priorityOverride }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
notifications
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Ignore malformed or incompatible browser state. */
|
|
91
|
+
function parseNotificationState(value) {
|
|
92
|
+
if (!record(value) || value.schemaVersion !== 1 || !ids(value.eligibleIds) || !ids(value.readIds) || !ids(value.suppressedIds) || value.activeId !== null && !isNotificationId(value.activeId)) return null;
|
|
93
|
+
return {
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
eligibleIds: value.eligibleIds,
|
|
96
|
+
readIds: value.readIds,
|
|
97
|
+
suppressedIds: value.suppressedIds,
|
|
98
|
+
activeId: value.activeId
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Internal priority never becomes a developer-facing badge. */
|
|
102
|
+
function notificationPriority(notice) {
|
|
103
|
+
return notice.priorityOverride ?? priorities[notice.priority];
|
|
104
|
+
}
|
|
105
|
+
/** Stable ordering used by authoring reports and first-time selection. */
|
|
106
|
+
function compareNotifications(a, b) {
|
|
107
|
+
return notificationPriority(b) - notificationPriority(a) || Date.parse(b.publishedAt) - Date.parse(a.publishedAt) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
108
|
+
}
|
|
109
|
+
/** Refresh eligibility without promoting the previously delivered backlog. */
|
|
110
|
+
function reconcileNotifications(state, feed, context) {
|
|
111
|
+
const eligible = feed.notifications.filter((n) => matchNotification(n, context).matches);
|
|
112
|
+
const quiet = (n) => state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);
|
|
113
|
+
const active = eligible.find((n) => n.id === state.activeId && !quiet(n)) ?? feed.notifications.find((n) => n.id === state.activeId && !quiet(n) && n.audiences.some((conditions) => {
|
|
114
|
+
const entries = Object.entries(conditions);
|
|
115
|
+
if (!entries.some(([key]) => context[key] === void 0)) return false;
|
|
116
|
+
const known = Object.fromEntries(entries.filter(([key]) => context[key] !== void 0));
|
|
117
|
+
return matchNotification({
|
|
118
|
+
...n,
|
|
119
|
+
audiences: [known]
|
|
120
|
+
}, context).matches;
|
|
121
|
+
}));
|
|
122
|
+
const newcomer = eligible.filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id)).sort(compareNotifications)[0];
|
|
123
|
+
const winner = active && (!newcomer || notificationPriority(active) >= notificationPriority(newcomer)) ? active : newcomer;
|
|
124
|
+
return {
|
|
125
|
+
...state,
|
|
126
|
+
eligibleIds: eligible.map((n) => n.id),
|
|
127
|
+
activeId: winner?.id ?? null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */
|
|
131
|
+
function acknowledgeNotification(state, noticeId) {
|
|
132
|
+
if (!state.eligibleIds.includes(noticeId)) return state;
|
|
133
|
+
const highlighted = state.activeId === noticeId;
|
|
134
|
+
return {
|
|
135
|
+
...state,
|
|
136
|
+
readIds: [...new Set([...state.readIds, noticeId])],
|
|
137
|
+
suppressedIds: [...new Set([...state.suppressedIds, ...highlighted ? state.eligibleIds : [noticeId]])],
|
|
138
|
+
activeId: highlighted ? null : state.activeId
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Explain targeting using the same rules in CI and the Inspector. */
|
|
142
|
+
function matchNotification(notice, context) {
|
|
143
|
+
if (!context.development) return {
|
|
144
|
+
matches: false,
|
|
145
|
+
reasons: ["Development only"]
|
|
146
|
+
};
|
|
147
|
+
if (!context.sdkVersion || !valid(context.sdkVersion) || prerelease(context.sdkVersion) !== null) return {
|
|
148
|
+
matches: false,
|
|
149
|
+
reasons: ["Known stable SDK required"]
|
|
150
|
+
};
|
|
151
|
+
const reasons = [];
|
|
152
|
+
const matches = notice.audiences.some((conditions) => {
|
|
153
|
+
return Object.entries(conditions).every(([field, expected]) => {
|
|
154
|
+
const actual = context[field];
|
|
155
|
+
const matches = field === "sdkVersion" ? actual !== void 0 && valid(actual) !== null && prerelease(actual) === null && satisfies(actual, expected) : actual === expected;
|
|
156
|
+
if (!matches) reasons.push(`${field} ${actual === void 0 ? "is unknown" : `requires ${expected} (got ${actual})`}`);
|
|
157
|
+
return matches;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
matches,
|
|
162
|
+
reasons: matches ? [] : reasons
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
export { acknowledgeNotification, compareNotifications, emptyNotificationState, parseNotificationFeed, parseNotificationState, reconcileNotifications };
|
|
168
|
+
//# sourceMappingURL=notifications.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.mjs","names":[],"sources":["../../src/lib/notifications.ts"],"sourcesContent":["import { satisfies, valid, validRange, prerelease } from \"semver\";\n\n/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */\nexport type NotificationFramework = \"react\" | \"vue\" | \"angular\";\nexport type NotificationPriority = \"Low\" | \"Normal\" | \"High\" | \"Urgent\";\nexport interface NotificationConditions {\n framework?: NotificationFramework;\n sdkVersion?: string;\n intelligence?: \"enabled\" | \"disabled\";\n deployment?: \"managed\" | \"self-hosted\";\n plan?: string;\n}\nexport interface CohortNotification {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n audiences: NotificationConditions[];\n priority: NotificationPriority;\n priorityOverride?: number;\n}\nexport interface NotificationFeed {\n schemaVersion: 1;\n notifications: CohortNotification[];\n}\nexport interface NotificationContext extends Omit<\n NotificationConditions,\n \"sdkVersion\"\n> {\n development: boolean;\n sdkVersion?: string;\n}\nexport interface NotificationState {\n schemaVersion: 1;\n eligibleIds: string[];\n readIds: string[];\n suppressedIds: string[];\n activeId: string | null;\n}\nexport function emptyNotificationState(): NotificationState {\n return {\n schemaVersion: 1,\n eligibleIds: [],\n readIds: [],\n suppressedIds: [],\n activeId: null,\n };\n}\nconst priorities: Record<NotificationPriority, number> = {\n Low: 10,\n Normal: 20,\n High: 30,\n Urgent: 40,\n};\nconst conditionValues: Record<string, readonly string[]> = {\n framework: [\"react\", \"vue\", \"angular\"],\n intelligence: [\"enabled\", \"disabled\"],\n deployment: [\"managed\", \"self-hosted\"],\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 = 256): value is string {\n return (\n typeof value === \"string\" && value.trim().length > 0 && value.length <= max\n );\n}\nexport function isNotificationId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n value,\n )\n );\n}\nfunction ids(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.every(isNotificationId) &&\n new Set(value).size === value.length\n );\n}\n/** Validate authored conditions; unsupported predicates cannot broaden the audience. */\nexport function validNotificationConditions(\n value: unknown,\n): value is NotificationConditions {\n if (!record(value)) return false;\n if (value.sdkVersion !== undefined && value.framework === undefined)\n return false;\n return Object.entries(value).every(([key, expected]) => {\n if (!text(expected)) return false;\n if (key === \"sdkVersion\") return validRange(expected) !== null;\n if (key === \"plan\") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);\n return (\n Object.prototype.hasOwnProperty.call(conditionValues, key) &&\n (conditionValues[key]?.includes(expected) ?? false)\n );\n });\n}\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseNotificationFeed(value: unknown): NotificationFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"notifications\"]) ||\n value.schemaVersion !== 1\n )\n return null;\n if (!Array.isArray(value.notifications) || value.notifications.length > 100)\n return null;\n const notifications: CohortNotification[] = [];\n for (const notice of value.notifications) {\n if (\n !record(notice) ||\n !onlyKeys(notice, [\n \"id\",\n \"title\",\n \"body\",\n \"publishedAt\",\n \"audiences\",\n \"priority\",\n \"priorityOverride\",\n ])\n )\n return null;\n if (\n !isNotificationId(notice.id) ||\n !text(notice.title) ||\n !text(notice.body, 100_000) ||\n !text(notice.publishedAt)\n )\n return null;\n if (\n !/^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d{3}Z$/.test(notice.publishedAt) ||\n !Number.isFinite(Date.parse(notice.publishedAt)) ||\n new Date(notice.publishedAt).toISOString() !== notice.publishedAt\n )\n return null;\n if (\n !Array.isArray(notice.audiences) ||\n !notice.audiences.length ||\n notice.audiences.length > 100 ||\n !notice.audiences.every(validNotificationConditions)\n )\n return null;\n if (\n !text(notice.priority) ||\n !Object.prototype.hasOwnProperty.call(priorities, notice.priority)\n )\n return null;\n if (\n notice.priorityOverride !== undefined &&\n (typeof notice.priorityOverride !== \"number\" ||\n !Number.isSafeInteger(notice.priorityOverride) ||\n notice.priorityOverride < 0)\n )\n return null;\n if (notifications.some((n) => n.id === notice.id)) return null;\n notifications.push({\n id: notice.id,\n title: notice.title,\n body: notice.body,\n publishedAt: notice.publishedAt,\n audiences: notice.audiences,\n priority: notice.priority as NotificationPriority,\n ...(notice.priorityOverride === undefined\n ? {}\n : { priorityOverride: notice.priorityOverride }),\n });\n }\n return { schemaVersion: 1, notifications };\n}\n/** Ignore malformed or incompatible browser state. */\nexport function parseNotificationState(\n value: unknown,\n): NotificationState | null {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !ids(value.eligibleIds) ||\n !ids(value.readIds) ||\n !ids(value.suppressedIds) ||\n (value.activeId !== null && !isNotificationId(value.activeId))\n )\n return null;\n return {\n schemaVersion: 1,\n eligibleIds: value.eligibleIds,\n readIds: value.readIds,\n suppressedIds: value.suppressedIds,\n activeId: value.activeId,\n };\n}\n/** Internal priority never becomes a developer-facing badge. */\nexport function notificationPriority(notice: CohortNotification): number {\n return notice.priorityOverride ?? priorities[notice.priority];\n}\n/** Stable ordering used by authoring reports and first-time selection. */\nexport function compareNotifications(\n a: CohortNotification,\n b: CohortNotification,\n): number {\n return (\n notificationPriority(b) - notificationPriority(a) ||\n Date.parse(b.publishedAt) - Date.parse(a.publishedAt) ||\n (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)\n );\n}\n/** Refresh eligibility without promoting the previously delivered backlog. */\nexport function reconcileNotifications(\n state: NotificationState,\n feed: NotificationFeed,\n context: NotificationContext,\n): NotificationState {\n const eligible = feed.notifications.filter(\n (n) => matchNotification(n, context).matches,\n );\n const quiet = (n: CohortNotification) =>\n state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);\n const active =\n eligible.find((n) => n.id === state.activeId && !quiet(n)) ??\n feed.notifications.find(\n (n) =>\n n.id === state.activeId &&\n !quiet(n) &&\n n.audiences.some((conditions) => {\n const entries = Object.entries(conditions);\n if (\n !entries.some(\n ([key]) =>\n context[key as keyof NotificationConditions] === undefined,\n )\n )\n return false;\n const known = Object.fromEntries(\n entries.filter(\n ([key]) =>\n context[key as keyof NotificationConditions] !== undefined,\n ),\n );\n return matchNotification({ ...n, audiences: [known] }, context)\n .matches;\n }),\n );\n const newcomer = eligible\n .filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id))\n .sort(compareNotifications)[0];\n const winner =\n active &&\n (!newcomer ||\n notificationPriority(active) >= notificationPriority(newcomer))\n ? active\n : newcomer;\n return {\n ...state,\n eligibleIds: eligible.map((n) => n.id),\n activeId: winner?.id ?? null,\n };\n}\n/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */\nexport function acknowledgeNotification(\n state: NotificationState,\n noticeId: string,\n): NotificationState {\n if (!state.eligibleIds.includes(noticeId)) return state;\n const highlighted = state.activeId === noticeId;\n return {\n ...state,\n readIds: [...new Set([...state.readIds, noticeId])],\n suppressedIds: [\n ...new Set([\n ...state.suppressedIds,\n ...(highlighted ? state.eligibleIds : [noticeId]),\n ]),\n ],\n activeId: highlighted ? null : state.activeId,\n };\n}\n/** Explain targeting using the same rules in CI and the Inspector. */\nexport function matchNotification(\n notice: CohortNotification,\n context: NotificationContext,\n): { matches: boolean; reasons: string[] } {\n if (!context.development)\n return { matches: false, reasons: [\"Development only\"] };\n if (\n !context.sdkVersion ||\n !valid(context.sdkVersion) ||\n prerelease(context.sdkVersion) !== null\n )\n return {\n matches: false,\n reasons: [\"Known stable SDK required\"],\n };\n const reasons: string[] = [];\n const matches = notice.audiences.some((conditions) => {\n return Object.entries(conditions).every(([field, expected]) => {\n const actual = context[field as keyof NotificationConditions];\n const matches =\n field === \"sdkVersion\"\n ? actual !== undefined &&\n valid(actual) !== null &&\n prerelease(actual) === null &&\n satisfies(actual, expected)\n : actual === expected;\n if (!matches)\n reasons.push(\n `${field} ${actual === undefined ? \"is unknown\" : `requires ${expected} (got ${actual})`}`,\n );\n return matches;\n });\n });\n return {\n matches,\n reasons: matches ? [] : reasons,\n };\n}\n"],"mappings":"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"}
|