@shadowob/sdk 1.1.7 → 1.1.9
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/bin/shadow-server-app.mjs +58 -0
- package/dist/bridge.cjs +364 -0
- package/dist/bridge.d.cts +105 -0
- package/dist/bridge.d.ts +105 -0
- package/dist/bridge.js +26 -0
- package/dist/chunk-5QQ7ZR6B.js +192 -0
- package/dist/chunk-OMTGSMTQ.js +525 -0
- package/dist/index.cjs +925 -4
- package/dist/index.d.cts +183 -1876
- package/dist/index.d.ts +183 -1876
- package/dist/index.js +252 -3
- package/dist/server-app-node.cjs +82 -0
- package/dist/server-app-node.d.cts +21 -0
- package/dist/server-app-node.d.ts +21 -0
- package/dist/server-app-node.js +56 -0
- package/dist/server-app-qqsBtGmE.d.cts +2445 -0
- package/dist/server-app-qqsBtGmE.d.ts +2445 -0
- package/dist/server-app.cjs +575 -0
- package/dist/server-app.d.cts +2 -0
- package/dist/server-app.d.ts +2 -0
- package/dist/server-app.js +58 -0
- package/package.json +29 -2
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
// src/server-app.ts
|
|
2
|
+
var SHADOW_SERVER_APP_PROTOCOL = "shadow.app/1";
|
|
3
|
+
var SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT = "server_app.command.completed";
|
|
4
|
+
var SHADOW_SERVER_APP_COMMAND_FAILED_EVENT = "server_app.command.failed";
|
|
5
|
+
var SHADOW_SERVER_APP_COMMAND_EVENTS = [
|
|
6
|
+
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
|
|
7
|
+
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT
|
|
8
|
+
];
|
|
9
|
+
function isProtocolRecord(value) {
|
|
10
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function optionalProtocolString(value) {
|
|
13
|
+
return typeof value === "string" && value ? value : void 0;
|
|
14
|
+
}
|
|
15
|
+
function protocolPathSegment(value) {
|
|
16
|
+
return encodeURIComponent(value);
|
|
17
|
+
}
|
|
18
|
+
function shadowServerAppInboxTaskEndpoint(serverIdOrSlug, target) {
|
|
19
|
+
if ("channelId" in target && target.channelId) {
|
|
20
|
+
return `/api/channels/${protocolPathSegment(target.channelId)}/inbox/tasks`;
|
|
21
|
+
}
|
|
22
|
+
if ("agentId" in target && target.agentId) {
|
|
23
|
+
return `/api/servers/${protocolPathSegment(serverIdOrSlug)}/inboxes/${protocolPathSegment(
|
|
24
|
+
target.agentId
|
|
25
|
+
)}/tasks`;
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Missing Inbox task target");
|
|
28
|
+
}
|
|
29
|
+
function buildShadowServerAppInboxTaskRequest(input) {
|
|
30
|
+
const appId = input.app.id ?? input.app.appId ?? input.app.appKey;
|
|
31
|
+
const serverAppData = isProtocolRecord(input.task.data?.serverApp) ? input.task.data.serverApp : {};
|
|
32
|
+
return {
|
|
33
|
+
endpoint: shadowServerAppInboxTaskEndpoint(input.serverIdOrSlug, input.target),
|
|
34
|
+
body: {
|
|
35
|
+
title: input.task.title,
|
|
36
|
+
body: input.task.body,
|
|
37
|
+
priority: input.task.priority,
|
|
38
|
+
idempotencyKey: input.task.idempotencyKey,
|
|
39
|
+
source: {
|
|
40
|
+
kind: "server_app",
|
|
41
|
+
id: appId,
|
|
42
|
+
appId,
|
|
43
|
+
appKey: input.app.appKey,
|
|
44
|
+
...input.app.serverId ? { serverId: input.app.serverId } : {},
|
|
45
|
+
...input.commandName ? { command: input.commandName } : {},
|
|
46
|
+
label: input.app.label ?? input.app.name ?? input.app.appKey,
|
|
47
|
+
...input.task.resource ? { resource: input.task.resource } : {}
|
|
48
|
+
},
|
|
49
|
+
data: {
|
|
50
|
+
...input.task.data ?? {},
|
|
51
|
+
serverApp: {
|
|
52
|
+
...serverAppData,
|
|
53
|
+
appKey: input.app.appKey,
|
|
54
|
+
...input.commandName ? { command: input.commandName } : {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function getShadowServerAppTaskCardId(message) {
|
|
61
|
+
const metadata = isProtocolRecord(message) ? message.metadata : null;
|
|
62
|
+
const cards = isProtocolRecord(metadata) && Array.isArray(metadata.cards) ? metadata.cards : [];
|
|
63
|
+
for (const item of cards) {
|
|
64
|
+
if (!isProtocolRecord(item)) continue;
|
|
65
|
+
if (item.kind === "task" && typeof item.id === "string" && item.id) return item.id;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function buildShadowServerAppInboxDelivery(input) {
|
|
70
|
+
const message = isProtocolRecord(input.message) ? input.message : {};
|
|
71
|
+
return {
|
|
72
|
+
..."agentId" in input.target && input.target.agentId ? { agentId: input.target.agentId } : {},
|
|
73
|
+
channelId: optionalProtocolString(message.channelId),
|
|
74
|
+
messageId: optionalProtocolString(message.id),
|
|
75
|
+
cardId: getShadowServerAppTaskCardId(message),
|
|
76
|
+
idempotencyKey: input.idempotencyKey
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function shadowFromPayload(payload) {
|
|
80
|
+
if (payload.protocol === SHADOW_SERVER_APP_PROTOCOL) {
|
|
81
|
+
return payload;
|
|
82
|
+
}
|
|
83
|
+
const shadow = isProtocolRecord(payload.shadow) ? payload.shadow : null;
|
|
84
|
+
if (shadow?.protocol === SHADOW_SERVER_APP_PROTOCOL) {
|
|
85
|
+
return shadow;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function mergeShadowResult(value, shadow) {
|
|
90
|
+
if (!shadow) return value;
|
|
91
|
+
const existing = shadowFromPayload(value);
|
|
92
|
+
if (!existing) return { ...value, shadow };
|
|
93
|
+
return {
|
|
94
|
+
...value,
|
|
95
|
+
shadow: {
|
|
96
|
+
protocol: SHADOW_SERVER_APP_PROTOCOL,
|
|
97
|
+
outbox: {
|
|
98
|
+
...existing.outbox ?? {},
|
|
99
|
+
...shadow.outbox ?? {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function getShadowServerAppInboxDeliveries(payload) {
|
|
105
|
+
if (!isProtocolRecord(payload)) return [];
|
|
106
|
+
const shadow = shadowFromPayload(payload);
|
|
107
|
+
return shadow?.outbox?.deliveries ?? [];
|
|
108
|
+
}
|
|
109
|
+
function getShadowServerAppInboxErrors(payload) {
|
|
110
|
+
if (!isProtocolRecord(payload)) return [];
|
|
111
|
+
const shadow = shadowFromPayload(payload);
|
|
112
|
+
return shadow?.outbox?.errors ?? [];
|
|
113
|
+
}
|
|
114
|
+
function getShadowServerAppChannelMessageDeliveries(payload) {
|
|
115
|
+
if (!isProtocolRecord(payload)) return [];
|
|
116
|
+
const shadow = shadowFromPayload(payload);
|
|
117
|
+
return shadow?.outbox?.channelMessageDeliveries ?? [];
|
|
118
|
+
}
|
|
119
|
+
function getShadowServerAppChannelMessageErrors(payload) {
|
|
120
|
+
if (!isProtocolRecord(payload)) return [];
|
|
121
|
+
const shadow = shadowFromPayload(payload);
|
|
122
|
+
return shadow?.outbox?.channelMessageErrors ?? [];
|
|
123
|
+
}
|
|
124
|
+
function unwrapShadowServerAppCommandPayload(payload) {
|
|
125
|
+
if (isProtocolRecord(payload) && payload.ok === false) {
|
|
126
|
+
throw new Error(typeof payload.error === "string" ? payload.error : "Command failed");
|
|
127
|
+
}
|
|
128
|
+
if (isProtocolRecord(payload) && "result" in payload && payload.result !== void 0) {
|
|
129
|
+
const nested = unwrapShadowServerAppCommandPayload(payload.result);
|
|
130
|
+
const shadow = shadowFromPayload(payload);
|
|
131
|
+
if (isProtocolRecord(nested)) return mergeShadowResult(nested, shadow);
|
|
132
|
+
return nested;
|
|
133
|
+
}
|
|
134
|
+
return payload;
|
|
135
|
+
}
|
|
136
|
+
var ShadowServerAppOutbox = class {
|
|
137
|
+
inboxTasks = [];
|
|
138
|
+
channelMessages = [];
|
|
139
|
+
enqueueInboxTask(task) {
|
|
140
|
+
this.inboxTasks.push(task);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
enqueueInboxTasks(tasks) {
|
|
144
|
+
for (const task of tasks) this.enqueueInboxTask(task);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
sendChannelMessage(message) {
|
|
148
|
+
this.channelMessages.push(message);
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
sendChannelMessages(messages) {
|
|
152
|
+
for (const message of messages) this.sendChannelMessage(message);
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
toShadow() {
|
|
156
|
+
return {
|
|
157
|
+
protocol: SHADOW_SERVER_APP_PROTOCOL,
|
|
158
|
+
outbox: {
|
|
159
|
+
...this.inboxTasks.length > 0 ? { inboxTasks: [...this.inboxTasks] } : {},
|
|
160
|
+
...this.channelMessages.length > 0 ? { channelMessages: [...this.channelMessages] } : {}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
attachTo(result) {
|
|
165
|
+
return { ...result, shadow: this.toShadow() };
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function trimTrailingSlash(value) {
|
|
169
|
+
return value.replace(/\/$/, "");
|
|
170
|
+
}
|
|
171
|
+
function joinBasePath(baseUrl, path) {
|
|
172
|
+
const cleanBase = trimTrailingSlash(baseUrl);
|
|
173
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
174
|
+
return `${cleanBase}${cleanPath}`;
|
|
175
|
+
}
|
|
176
|
+
function extractShadowServerAppBearerToken(value) {
|
|
177
|
+
if (!value) return null;
|
|
178
|
+
return value.toLowerCase().startsWith("bearer ") ? value.slice(7).trim() : null;
|
|
179
|
+
}
|
|
180
|
+
function normalizeShadowServerAppCommandInput(value) {
|
|
181
|
+
if (value && typeof value === "object" && !Array.isArray(value) && "input" in value && Object.keys(value).every((key) => key === "input" || key === "channelId")) {
|
|
182
|
+
return value.input ?? {};
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
function createShadowServerAppManifest(manifest, options = {}) {
|
|
187
|
+
const publicBaseUrl = trimTrailingSlash(
|
|
188
|
+
options.publicBaseUrl ?? `http://localhost:${options.port ?? 4201}`
|
|
189
|
+
);
|
|
190
|
+
const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl ?? publicBaseUrl);
|
|
191
|
+
const iframePath = options.iframePath ?? "/shadow/server";
|
|
192
|
+
const iconPath = options.iconPath ?? "/assets/icon.svg";
|
|
193
|
+
return {
|
|
194
|
+
...manifest,
|
|
195
|
+
iconUrl: joinBasePath(publicBaseUrl, iconPath),
|
|
196
|
+
iframe: manifest.iframe ? {
|
|
197
|
+
...manifest.iframe,
|
|
198
|
+
entry: joinBasePath(publicBaseUrl, iframePath),
|
|
199
|
+
allowedOrigins: options.allowedOrigins ?? [publicBaseUrl]
|
|
200
|
+
} : manifest.iframe,
|
|
201
|
+
api: {
|
|
202
|
+
...manifest.api,
|
|
203
|
+
baseUrl: apiBaseUrl
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function defineShadowServerApp(manifest, options = {}) {
|
|
208
|
+
return new ShadowServerAppRuntime(manifest, options);
|
|
209
|
+
}
|
|
210
|
+
var ShadowServerAppCommandError = class extends Error {
|
|
211
|
+
status;
|
|
212
|
+
issues;
|
|
213
|
+
constructor(status, error, issues) {
|
|
214
|
+
super(error);
|
|
215
|
+
this.name = "ShadowServerAppCommandError";
|
|
216
|
+
this.status = status;
|
|
217
|
+
this.issues = issues;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
function shadowServerAppError(status, error, issues) {
|
|
221
|
+
return new ShadowServerAppCommandError(status, error, issues);
|
|
222
|
+
}
|
|
223
|
+
var ShadowServerAppRuntime = class {
|
|
224
|
+
constructor(sourceManifest, options = {}) {
|
|
225
|
+
this.sourceManifest = sourceManifest;
|
|
226
|
+
this.options = options;
|
|
227
|
+
}
|
|
228
|
+
manifest(options = {}) {
|
|
229
|
+
return createShadowServerAppManifest(this.sourceManifest, options);
|
|
230
|
+
}
|
|
231
|
+
defineCommands(handlers) {
|
|
232
|
+
return handlers;
|
|
233
|
+
}
|
|
234
|
+
actor(envelopeOrContext) {
|
|
235
|
+
return shadowServerAppActorRef(envelopeOrContext);
|
|
236
|
+
}
|
|
237
|
+
error(status, error, issues) {
|
|
238
|
+
return shadowServerAppError(status, error, issues);
|
|
239
|
+
}
|
|
240
|
+
async parseCommand(commandName, request) {
|
|
241
|
+
return parseShadowServerAppCommandRequest(
|
|
242
|
+
{
|
|
243
|
+
...request,
|
|
244
|
+
expectedCommand: commandName,
|
|
245
|
+
shadowBaseUrl: this.options.shadowBaseUrl,
|
|
246
|
+
fetchImpl: this.options.fetchImpl
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async executeCommand(commandName, request, handlers) {
|
|
251
|
+
const parsed = await this.parseCommand(commandName, request);
|
|
252
|
+
if (!parsed.ok) return parseErrorResult(parsed);
|
|
253
|
+
return this.executeEnvelope(commandName, parsed.envelope, handlers);
|
|
254
|
+
}
|
|
255
|
+
async executeLocal(commandName, input, context, handlers) {
|
|
256
|
+
return this.executeEnvelope(
|
|
257
|
+
commandName,
|
|
258
|
+
{
|
|
259
|
+
input,
|
|
260
|
+
context: {
|
|
261
|
+
...context,
|
|
262
|
+
command: commandName
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
handlers
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
async executeEnvelope(commandName, envelope, handlers) {
|
|
269
|
+
const command = this.sourceManifest.commands.find((item) => item.name === commandName);
|
|
270
|
+
if (!command) return failureResult(404, "command_not_found");
|
|
271
|
+
const validation = validateShadowServerAppJsonSchema(command.inputSchema, envelope.input);
|
|
272
|
+
if (!validation.ok) return failureResult(422, "invalid_input", validation.issues);
|
|
273
|
+
const handler = handlers[commandName];
|
|
274
|
+
if (!handler) return failureResult(404, "command_not_found");
|
|
275
|
+
try {
|
|
276
|
+
const result = await handler(envelope.input, {
|
|
277
|
+
context: envelope.context,
|
|
278
|
+
actor: this.actor(envelope)
|
|
279
|
+
});
|
|
280
|
+
return { ok: true, status: 200, body: { ok: true, result } };
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error instanceof ShadowServerAppCommandError) {
|
|
283
|
+
return failureResult(error.status, error.message, error.issues);
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
async function introspectShadowServerAppToken(input) {
|
|
290
|
+
const baseUrl = trimTrailingSlash(input.shadowBaseUrl ?? "http://localhost:3002");
|
|
291
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
292
|
+
const response = await fetchImpl(
|
|
293
|
+
`${baseUrl}/api/servers/${encodeURIComponent(input.serverId)}/apps/${encodeURIComponent(
|
|
294
|
+
input.appKey
|
|
295
|
+
)}/oauth/introspect`,
|
|
296
|
+
{
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: {
|
|
299
|
+
Authorization: `Bearer ${input.token}`,
|
|
300
|
+
"Content-Type": "application/json"
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify({ token: input.token })
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
if (!response.ok) return null;
|
|
306
|
+
const payload = await response.json();
|
|
307
|
+
return payload.active ? payload : null;
|
|
308
|
+
}
|
|
309
|
+
async function parseShadowServerAppCommandRequest(input) {
|
|
310
|
+
const token = extractShadowServerAppBearerToken(input.authorizationHeader);
|
|
311
|
+
const serverId = input.serverIdHeader;
|
|
312
|
+
const appKey = input.appKeyHeader;
|
|
313
|
+
if (!token || !serverId || !appKey) {
|
|
314
|
+
return { ok: false, status: 401, error: "missing_oauth" };
|
|
315
|
+
}
|
|
316
|
+
const introspection = await introspectShadowServerAppToken({
|
|
317
|
+
token,
|
|
318
|
+
serverId,
|
|
319
|
+
appKey,
|
|
320
|
+
shadowBaseUrl: input.shadowBaseUrl,
|
|
321
|
+
fetchImpl: input.fetchImpl
|
|
322
|
+
}).catch(() => null);
|
|
323
|
+
const context = introspection?.shadow;
|
|
324
|
+
if (!context) return { ok: false, status: 401, error: "invalid_token" };
|
|
325
|
+
if (context.command !== input.expectedCommand) {
|
|
326
|
+
return { ok: false, status: 403, error: "wrong_command" };
|
|
327
|
+
}
|
|
328
|
+
let commandInput;
|
|
329
|
+
if (input.requestInput !== void 0) {
|
|
330
|
+
commandInput = input.requestInput;
|
|
331
|
+
} else {
|
|
332
|
+
let body;
|
|
333
|
+
try {
|
|
334
|
+
body = JSON.parse(input.requestBody ?? "{}");
|
|
335
|
+
} catch {
|
|
336
|
+
return { ok: false, status: 400, error: "invalid_json" };
|
|
337
|
+
}
|
|
338
|
+
commandInput = body.input ?? {};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
envelope: {
|
|
343
|
+
input: commandInput,
|
|
344
|
+
context
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function validateShadowServerAppJsonSchema(schema, value) {
|
|
349
|
+
if (!schema) return { ok: true };
|
|
350
|
+
const issues = [];
|
|
351
|
+
validateJsonSchemaValue(schema, value, "", issues);
|
|
352
|
+
return issues.length ? { ok: false, issues } : { ok: true };
|
|
353
|
+
}
|
|
354
|
+
function shadowServerAppActorDisplayName(envelopeOrContext) {
|
|
355
|
+
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
|
|
356
|
+
const actor = context.actor;
|
|
357
|
+
const profile = actor.profile;
|
|
358
|
+
return profile?.displayName?.trim() || profile?.username?.trim() || (actor.buddyAgentId ? `Buddy ${actor.buddyAgentId.slice(0, 8)}` : null) || (actor.userId ? `${actor.kind}:${actor.userId.slice(0, 8)}` : null) || `${actor.kind}:unknown`;
|
|
359
|
+
}
|
|
360
|
+
function shadowServerAppActorAvatarUrl(envelopeOrContext) {
|
|
361
|
+
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
|
|
362
|
+
return context.actor.profile?.avatarUrl ?? null;
|
|
363
|
+
}
|
|
364
|
+
function shadowServerAppActorRef(envelopeOrContext) {
|
|
365
|
+
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
|
|
366
|
+
const actor = context.actor;
|
|
367
|
+
return {
|
|
368
|
+
kind: actor.kind,
|
|
369
|
+
id: actor.buddyAgentId ?? actor.userId ?? actor.ownerId ?? "unknown",
|
|
370
|
+
userId: actor.userId ?? null,
|
|
371
|
+
buddyAgentId: actor.buddyAgentId ?? null,
|
|
372
|
+
ownerId: actor.ownerId ?? null,
|
|
373
|
+
displayName: shadowServerAppActorDisplayName(context),
|
|
374
|
+
avatarUrl: shadowServerAppActorAvatarUrl(context)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function parseErrorResult(error) {
|
|
378
|
+
return failureResult(error.status, error.error, error.issues);
|
|
379
|
+
}
|
|
380
|
+
function failureResult(status, error, issues) {
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
status,
|
|
384
|
+
body: issues === void 0 ? { ok: false, error } : { ok: false, error, issues }
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function validateJsonSchemaValue(schema, value, path, issues) {
|
|
388
|
+
if (Array.isArray(schema.oneOf)) {
|
|
389
|
+
const matches = schema.oneOf.some((option) => {
|
|
390
|
+
const nestedIssues = [];
|
|
391
|
+
if (option && typeof option === "object" && !Array.isArray(option)) {
|
|
392
|
+
validateJsonSchemaValue(
|
|
393
|
+
option,
|
|
394
|
+
value,
|
|
395
|
+
path,
|
|
396
|
+
nestedIssues
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return nestedIssues.length === 0;
|
|
400
|
+
});
|
|
401
|
+
if (!matches) issues.push({ path, message: "Expected value matching one schema option" });
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const enumValues = schema.enum;
|
|
405
|
+
if (Array.isArray(enumValues) && !enumValues.includes(value)) {
|
|
406
|
+
issues.push({ path, message: `Expected one of ${enumValues.map(String).join(", ")}` });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const type = schema.type;
|
|
410
|
+
if (type === "object") {
|
|
411
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
412
|
+
issues.push({ path, message: "Expected object" });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const record = value;
|
|
416
|
+
const properties = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties) ? schema.properties : {};
|
|
417
|
+
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
|
|
418
|
+
for (const key of required) {
|
|
419
|
+
if (!(key in record)) issues.push({ path: joinJsonPath(path, key), message: "Required" });
|
|
420
|
+
}
|
|
421
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
422
|
+
if (record[key] !== void 0) {
|
|
423
|
+
validateJsonSchemaValue(propertySchema, record[key], joinJsonPath(path, key), issues);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const additionalProperties = schema.additionalProperties && typeof schema.additionalProperties === "object" && !Array.isArray(schema.additionalProperties) ? schema.additionalProperties : null;
|
|
427
|
+
if (additionalProperties) {
|
|
428
|
+
for (const [key, nestedValue] of Object.entries(record)) {
|
|
429
|
+
if (!(key in properties)) {
|
|
430
|
+
validateJsonSchemaValue(
|
|
431
|
+
additionalProperties,
|
|
432
|
+
nestedValue,
|
|
433
|
+
joinJsonPath(path, key),
|
|
434
|
+
issues
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} else if (schema.additionalProperties === false) {
|
|
439
|
+
for (const key of Object.keys(record)) {
|
|
440
|
+
if (!(key in properties)) {
|
|
441
|
+
issues.push({ path: joinJsonPath(path, key), message: "Unknown property" });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (type === "array") {
|
|
448
|
+
if (!Array.isArray(value)) {
|
|
449
|
+
issues.push({ path, message: "Expected array" });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const maxItems = typeof schema.maxItems === "number" ? schema.maxItems : null;
|
|
453
|
+
if (maxItems !== null && value.length > maxItems) {
|
|
454
|
+
issues.push({ path, message: `Expected at most ${maxItems} items` });
|
|
455
|
+
}
|
|
456
|
+
const itemSchema = schema.items && typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : null;
|
|
457
|
+
if (itemSchema) {
|
|
458
|
+
value.forEach(
|
|
459
|
+
(item, index) => validateJsonSchemaValue(itemSchema, item, `${path}[${index}]`, issues)
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (type === "string") {
|
|
465
|
+
if (typeof value !== "string") {
|
|
466
|
+
issues.push({ path, message: "Expected string" });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const maxLength = typeof schema.maxLength === "number" ? schema.maxLength : null;
|
|
470
|
+
const minLength = typeof schema.minLength === "number" ? schema.minLength : null;
|
|
471
|
+
if (minLength !== null && value.length < minLength) {
|
|
472
|
+
issues.push({ path, message: `Expected at least ${minLength} characters` });
|
|
473
|
+
}
|
|
474
|
+
if (maxLength !== null && value.length > maxLength) {
|
|
475
|
+
issues.push({ path, message: `Expected at most ${maxLength} characters` });
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (type === "number" || type === "integer") {
|
|
480
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
481
|
+
issues.push({ path, message: "Expected number" });
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (type === "integer" && !Number.isInteger(value)) {
|
|
485
|
+
issues.push({ path, message: "Expected integer" });
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (type === "boolean" && typeof value !== "boolean") {
|
|
490
|
+
issues.push({ path, message: "Expected boolean" });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function joinJsonPath(parent, key) {
|
|
494
|
+
return parent ? `${parent}.${key}` : key;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export {
|
|
498
|
+
SHADOW_SERVER_APP_PROTOCOL,
|
|
499
|
+
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
|
|
500
|
+
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT,
|
|
501
|
+
SHADOW_SERVER_APP_COMMAND_EVENTS,
|
|
502
|
+
shadowServerAppInboxTaskEndpoint,
|
|
503
|
+
buildShadowServerAppInboxTaskRequest,
|
|
504
|
+
getShadowServerAppTaskCardId,
|
|
505
|
+
buildShadowServerAppInboxDelivery,
|
|
506
|
+
getShadowServerAppInboxDeliveries,
|
|
507
|
+
getShadowServerAppInboxErrors,
|
|
508
|
+
getShadowServerAppChannelMessageDeliveries,
|
|
509
|
+
getShadowServerAppChannelMessageErrors,
|
|
510
|
+
unwrapShadowServerAppCommandPayload,
|
|
511
|
+
ShadowServerAppOutbox,
|
|
512
|
+
extractShadowServerAppBearerToken,
|
|
513
|
+
normalizeShadowServerAppCommandInput,
|
|
514
|
+
createShadowServerAppManifest,
|
|
515
|
+
defineShadowServerApp,
|
|
516
|
+
ShadowServerAppCommandError,
|
|
517
|
+
shadowServerAppError,
|
|
518
|
+
ShadowServerAppRuntime,
|
|
519
|
+
introspectShadowServerAppToken,
|
|
520
|
+
parseShadowServerAppCommandRequest,
|
|
521
|
+
validateShadowServerAppJsonSchema,
|
|
522
|
+
shadowServerAppActorDisplayName,
|
|
523
|
+
shadowServerAppActorAvatarUrl,
|
|
524
|
+
shadowServerAppActorRef
|
|
525
|
+
};
|