@openchambery/relay-server 1.19.7-beta.5 → 1.19.7-beta.7
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/package.json +1 -1
- package/src/push/handler.js +9 -1
- package/src/push/schema.js +46 -1
package/package.json
CHANGED
package/src/push/handler.js
CHANGED
|
@@ -8,6 +8,14 @@ import { createTokenStore } from './store.js';
|
|
|
8
8
|
const WINDOW_MS = 60_000;
|
|
9
9
|
const STOP_DEADLINE_MS = 5_500;
|
|
10
10
|
|
|
11
|
+
const liveActivitySignMessage = (ts, tokens, event, contentState, dismissalDate, staleDate) => {
|
|
12
|
+
let message = `${ts}.${tokens.join(',')}.${event}.${contentState.status}.${contentState.eventVersion}.${contentState.updatedAt}.${contentState.endedAt ?? ''}.${dismissalDate ?? ''}.${staleDate ?? ''}`;
|
|
13
|
+
if (contentState.title !== undefined || contentState.workingCount !== undefined || contentState.items !== undefined) {
|
|
14
|
+
message += `.${contentState.title ?? ''}.${contentState.workingCount ?? ''}.${JSON.stringify(contentState.items ?? [])}`;
|
|
15
|
+
}
|
|
16
|
+
return message;
|
|
17
|
+
};
|
|
18
|
+
|
|
11
19
|
const sendJson = (response, status, payload, method = 'GET') => {
|
|
12
20
|
if (response.writableEnded) return;
|
|
13
21
|
response.setHeader('cache-control', 'no-store');
|
|
@@ -161,7 +169,7 @@ export const createPushRelayHandler = (options = {}) => {
|
|
|
161
169
|
const handleLiveActivity = async (parsed) => {
|
|
162
170
|
const sorted = [...parsed.tokens].sort();
|
|
163
171
|
const contentState = parsed.contentState;
|
|
164
|
-
const message =
|
|
172
|
+
const message = liveActivitySignMessage(parsed.ts, sorted, parsed.event, contentState, parsed.dismissalDate, parsed.staleDate);
|
|
165
173
|
const authError = authenticate(parsed.publicKeyJwk, message, parsed.sig, parsed.ts);
|
|
166
174
|
if (authError) return { status: 401, body: { error: authError } };
|
|
167
175
|
const serverId = deriveServerId(parsed.publicKeyJwk);
|
package/src/push/schema.js
CHANGED
|
@@ -14,7 +14,18 @@ export const MAX_DATA_TOTAL_BYTES = 2048;
|
|
|
14
14
|
export const LIVE_ACTIVITY_KIND = 'liveactivity';
|
|
15
15
|
export const LIVE_ACTIVITY_EVENTS = new Set(['update', 'end']);
|
|
16
16
|
export const LIVE_ACTIVITY_STATUSES = new Set(['working', 'tool', 'retry', 'input', 'permission', 'stale', 'complete', 'error']);
|
|
17
|
-
export const LIVE_ACTIVITY_CONTENT_KEYS = new Set([
|
|
17
|
+
export const LIVE_ACTIVITY_CONTENT_KEYS = new Set([
|
|
18
|
+
'status',
|
|
19
|
+
'eventVersion',
|
|
20
|
+
'updatedAt',
|
|
21
|
+
'endedAt',
|
|
22
|
+
'title',
|
|
23
|
+
'workingCount',
|
|
24
|
+
'items',
|
|
25
|
+
]);
|
|
26
|
+
const MAX_LIVE_ACTIVITY_ITEMS = 4;
|
|
27
|
+
const MAX_LIVE_ACTIVITY_ITEM_TITLE = 80;
|
|
28
|
+
const MAX_LIVE_ACTIVITY_ITEM_SESSION = 128;
|
|
18
29
|
|
|
19
30
|
const bytes = (value) => Buffer.byteLength(value, 'utf8');
|
|
20
31
|
const isSafeInt = (value) => typeof value === 'number' && Number.isSafeInteger(value);
|
|
@@ -71,6 +82,29 @@ const uniqueTokens = (tokens) => {
|
|
|
71
82
|
return unique;
|
|
72
83
|
};
|
|
73
84
|
|
|
85
|
+
const parseLiveActivityItems = (value) => {
|
|
86
|
+
if (value === undefined) return undefined;
|
|
87
|
+
if (!Array.isArray(value) || value.length > MAX_LIVE_ACTIVITY_ITEMS) return null;
|
|
88
|
+
const items = [];
|
|
89
|
+
for (const raw of value) {
|
|
90
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
91
|
+
const sessionID = typeof raw.sessionID === 'string'
|
|
92
|
+
? raw.sessionID
|
|
93
|
+
: (typeof raw.sessionId === 'string' ? raw.sessionId : '');
|
|
94
|
+
if (!sessionID || sessionID.length > MAX_LIVE_ACTIVITY_ITEM_SESSION) return null;
|
|
95
|
+
if (typeof raw.status !== 'string' || !LIVE_ACTIVITY_STATUSES.has(raw.status)) return null;
|
|
96
|
+
if (!isFiniteNumber(raw.startedAt)) return null;
|
|
97
|
+
const title = typeof raw.title === 'string' ? raw.title.slice(0, MAX_LIVE_ACTIVITY_ITEM_TITLE) : '';
|
|
98
|
+
const item = { sessionID, title, status: raw.status, startedAt: raw.startedAt };
|
|
99
|
+
if (raw.endedAt !== undefined) {
|
|
100
|
+
if (!isFiniteNumber(raw.endedAt)) return null;
|
|
101
|
+
item.endedAt = raw.endedAt;
|
|
102
|
+
}
|
|
103
|
+
items.push(item);
|
|
104
|
+
}
|
|
105
|
+
return items;
|
|
106
|
+
};
|
|
107
|
+
|
|
74
108
|
const parseContentState = (value, event) => {
|
|
75
109
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
76
110
|
const keys = Object.keys(value);
|
|
@@ -79,8 +113,19 @@ const parseContentState = (value, event) => {
|
|
|
79
113
|
if (!isSafeInt(value.eventVersion) || !isFiniteNumber(value.updatedAt)) return null;
|
|
80
114
|
if (event === 'end' && !isFiniteNumber(value.endedAt)) return null;
|
|
81
115
|
if (value.endedAt !== undefined && !isFiniteNumber(value.endedAt)) return null;
|
|
116
|
+
if (value.title !== undefined && (typeof value.title !== 'string' || value.title.length > MAX_LIVE_ACTIVITY_ITEM_TITLE)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (value.workingCount !== undefined && (!isSafeInt(value.workingCount) || value.workingCount < 0)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const items = parseLiveActivityItems(value.items);
|
|
123
|
+
if (items === null) return null;
|
|
82
124
|
const contentState = { status: value.status, eventVersion: value.eventVersion, updatedAt: value.updatedAt };
|
|
83
125
|
if (value.endedAt !== undefined) contentState.endedAt = value.endedAt;
|
|
126
|
+
if (value.title !== undefined) contentState.title = value.title;
|
|
127
|
+
if (value.workingCount !== undefined) contentState.workingCount = value.workingCount;
|
|
128
|
+
if (items !== undefined) contentState.items = items;
|
|
84
129
|
return contentState;
|
|
85
130
|
};
|
|
86
131
|
|