🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@shadowob/sdk

Package Overview
Dependencies
Maintainers
1
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shadowob/sdk - npm Package Compare versions

Comparing version
1.1.50
to
1.1.51
+726
dist/chunk-N55V4ZKC.js
// src/server-app.ts
import {
BUDDY_INBOX_DELIVERY_PERMISSION
} from "@shadowob/shared";
var SHADOW_SERVER_APP_PROTOCOL = "shadow.app/1";
var SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT = "server_app.command.completed";
var SHADOW_SERVER_APP_COMMAND_FAILED_EVENT = "server_app.command.failed";
var SHADOW_SERVER_APP_COMMAND_EVENTS = [
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT
];
var ShadowServerAppHttpError = class extends Error {
status;
payload;
constructor(status, message, payload) {
super(message);
this.name = "ShadowServerAppHttpError";
this.status = status;
this.payload = payload;
}
};
function isProtocolRecord(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function optionalProtocolString(value) {
return typeof value === "string" && value ? value : void 0;
}
function protocolPathSegment(value) {
return encodeURIComponent(value);
}
function shadowServerAppInboxTaskEndpoint(serverIdOrSlug, target) {
if ("channelId" in target && target.channelId) {
return `/api/channels/${protocolPathSegment(target.channelId)}/inbox/tasks`;
}
if ("agentId" in target && target.agentId) {
return `/api/servers/${protocolPathSegment(serverIdOrSlug)}/inboxes/${protocolPathSegment(
target.agentId
)}/tasks`;
}
throw new Error("Missing Inbox task target");
}
function buildShadowServerAppInboxTaskRequest(input) {
const appId = input.app.id ?? input.app.appId ?? input.app.appKey;
const serverAppData = isProtocolRecord(input.task.data?.serverApp) ? input.task.data.serverApp : {};
return {
endpoint: shadowServerAppInboxTaskEndpoint(input.serverIdOrSlug, input.target),
body: {
title: input.task.title,
body: input.task.body,
priority: input.task.priority,
tags: input.task.tags,
idempotencyKey: input.task.idempotencyKey,
requirements: input.task.requirements,
outputContract: input.task.outputContract,
privacy: input.task.privacy,
app: {
id: appId,
appId,
appKey: input.app.appKey,
name: input.app.name ?? input.app.label ?? input.app.appKey,
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {}
},
source: {
kind: "server_app",
id: appId,
appId,
appKey: input.app.appKey,
...input.app.name ? { appName: input.app.name } : {},
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {},
...input.app.serverId ? { serverId: input.app.serverId } : {},
...input.commandName ? { command: input.commandName } : {},
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.task.resource ? { resource: input.task.resource } : {}
},
data: {
...input.task.data ?? {},
serverApp: {
...serverAppData,
appKey: input.app.appKey,
name: input.app.name ?? input.app.label ?? input.app.appKey,
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {},
...input.commandName ? { command: input.commandName } : {}
}
}
}
};
}
function getShadowServerAppTaskCardId(message) {
const metadata = isProtocolRecord(message) ? message.metadata : null;
const cards = isProtocolRecord(metadata) && Array.isArray(metadata.cards) ? metadata.cards : [];
for (const item of cards) {
if (!isProtocolRecord(item)) continue;
if (item.kind === "task" && typeof item.id === "string" && item.id) return item.id;
}
return null;
}
function buildShadowServerAppInboxDelivery(input) {
const message = isProtocolRecord(input.message) ? input.message : {};
return {
..."agentId" in input.target && input.target.agentId ? { agentId: input.target.agentId } : {},
channelId: optionalProtocolString(message.channelId),
messageId: optionalProtocolString(message.id),
cardId: getShadowServerAppTaskCardId(message),
idempotencyKey: input.idempotencyKey
};
}
function shadowFromPayload(payload) {
if (payload.protocol === SHADOW_SERVER_APP_PROTOCOL) {
return payload;
}
const shadow = isProtocolRecord(payload.shadow) ? payload.shadow : null;
if (shadow?.protocol === SHADOW_SERVER_APP_PROTOCOL) {
return shadow;
}
return null;
}
function mergeShadowResult(value, shadow) {
if (!shadow) return value;
const existing = shadowFromPayload(value);
if (!existing) return { ...value, shadow };
return {
...value,
shadow: {
protocol: SHADOW_SERVER_APP_PROTOCOL,
outbox: {
...existing.outbox ?? {},
...shadow.outbox ?? {}
}
}
};
}
function getShadowServerAppInboxDeliveries(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.deliveries ?? [];
}
function getShadowServerAppInboxErrors(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.errors ?? [];
}
function getShadowServerAppPendingInboxTasks(payload, depth = 0) {
if (!isProtocolRecord(payload) || depth > 4) return [];
const shadow = shadowFromPayload(payload);
const tasks = shadow?.outbox?.inboxTasks ?? [];
return "result" in payload && payload.result !== void 0 ? [...tasks, ...getShadowServerAppPendingInboxTasks(payload.result, depth + 1)] : tasks;
}
function getShadowServerAppChannelMessageDeliveries(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.channelMessageDeliveries ?? [];
}
function getShadowServerAppChannelMessageErrors(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.channelMessageErrors ?? [];
}
function getShadowServerAppPendingChannelMessages(payload, depth = 0) {
if (!isProtocolRecord(payload) || depth > 4) return [];
const shadow = shadowFromPayload(payload);
const messages = shadow?.outbox?.channelMessages ?? [];
return "result" in payload && payload.result !== void 0 ? [...messages, ...getShadowServerAppPendingChannelMessages(payload.result, depth + 1)] : messages;
}
function hasShadowServerAppPendingOutbox(payload) {
return getShadowServerAppPendingInboxTasks(payload).length > 0 || getShadowServerAppPendingChannelMessages(payload).length > 0;
}
function isDomainResultWithEvents(payload) {
return Array.isArray(payload.events) && ("cursor" in payload || "result" in payload);
}
function isCommandPayloadEnvelope(payload) {
if (isDomainResultWithEvents(payload)) return false;
return payload.ok === true || payload.ok === false || shadowFromPayload(payload) !== null;
}
function unwrapShadowServerAppCommandPayload(payload) {
if (isProtocolRecord(payload) && payload.ok === false) {
throw new Error(typeof payload.error === "string" ? payload.error : "Command failed");
}
if (isProtocolRecord(payload) && "result" in payload && payload.result !== void 0 && isCommandPayloadEnvelope(payload)) {
const nested = unwrapShadowServerAppCommandPayload(payload.result);
const shadow = shadowFromPayload(payload);
if (isProtocolRecord(nested)) return mergeShadowResult(nested, shadow);
return nested;
}
return payload;
}
async function readShadowServerAppResponsePayload(response) {
const text = await response.text().catch(() => "");
if (!text.trim()) return null;
try {
return JSON.parse(text);
} catch {
if (!response.ok) return { ok: false, error: text };
throw new ShadowServerAppHttpError(response.status, "Command returned invalid JSON", text);
}
}
function shadowServerAppResponseErrorMessage(status, payload, fallback = "Command failed") {
if (isProtocolRecord(payload) && typeof payload.error === "string" && payload.error) {
return payload.error;
}
if (typeof payload === "string" && payload.trim()) return payload;
return status ? `${fallback} (${status})` : fallback;
}
async function readShadowServerAppCommandResponse(response) {
const payload = await readShadowServerAppResponsePayload(response);
if (!response.ok || isProtocolRecord(payload) && payload.ok === false) {
throw new ShadowServerAppHttpError(
response.status,
shadowServerAppResponseErrorMessage(response.status, payload),
payload
);
}
return unwrapShadowServerAppCommandPayload(payload);
}
var ShadowServerAppOutbox = class {
inboxTasks = [];
channelMessages = [];
enqueueInboxTask(task) {
this.inboxTasks.push(task);
return this;
}
enqueueInboxTasks(tasks) {
for (const task of tasks) this.enqueueInboxTask(task);
return this;
}
sendChannelMessage(message) {
this.channelMessages.push(message);
return this;
}
sendChannelMessages(messages) {
for (const message of messages) this.sendChannelMessage(message);
return this;
}
toShadow() {
return {
protocol: SHADOW_SERVER_APP_PROTOCOL,
outbox: {
...this.inboxTasks.length > 0 ? { inboxTasks: [...this.inboxTasks] } : {},
...this.channelMessages.length > 0 ? { channelMessages: [...this.channelMessages] } : {}
}
};
}
attachTo(result) {
return { ...result, shadow: this.toShadow() };
}
};
function trimTrailingSlash(value) {
return value.replace(/\/$/, "");
}
function joinBasePath(baseUrl, path) {
const cleanBase = trimTrailingSlash(baseUrl);
const cleanPath = path.startsWith("/") ? path : `/${path}`;
return `${cleanBase}${cleanPath}`;
}
function urlOrigin(value) {
try {
return new URL(value).origin;
} catch {
return null;
}
}
function rebasePublicAssetUrl(value, sourceOrigin, publicBaseUrl) {
if (!sourceOrigin) return value;
try {
const url = new URL(value);
if (url.origin !== sourceOrigin) return value;
return joinBasePath(publicBaseUrl, `${url.pathname}${url.search}${url.hash}`);
} catch {
return value;
}
}
function extractShadowServerAppBearerToken(value) {
if (!value) return null;
return value.toLowerCase().startsWith("bearer ") ? value.slice(7).trim() : null;
}
function decodeBase64UrlJson(value) {
try {
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
const binary = globalThis.atob(padded);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
} catch {
return null;
}
}
function decodeShadowServerAppLaunchTokenHint(token) {
if (!token) return null;
const parts = token.split(".");
if (parts.length !== 3 || parts[0] !== "sat_v1") return null;
const payload = decodeBase64UrlJson(parts[1]);
if (typeof payload?.serverId !== "string" || typeof payload.appKey !== "string") return null;
return { serverId: payload.serverId, appKey: payload.appKey };
}
async function fetchShadowServerAppLaunchInboxes(options) {
const hint = decodeShadowServerAppLaunchTokenHint(options.launchToken);
if (!hint || !options.launchToken) return { inboxes: [] };
const fetchFn = options.fetch ?? fetch;
const baseUrl = trimTrailingSlash(options.shadowApiBaseUrl ?? "http://localhost:3002");
const response = await fetchFn(
`${baseUrl}/api/servers/${encodeURIComponent(hint.serverId)}/apps/${encodeURIComponent(
hint.appKey
)}/launch/inboxes`,
{ headers: { Authorization: `Bearer ${options.launchToken}` } }
);
if (!response.ok) {
const message = await response.text().catch(() => "");
throw new Error(`Shadow launch inbox lookup failed (${response.status}): ${message}`);
}
return await response.json();
}
async function deliverShadowServerAppLaunchOutbox(options) {
const hint = decodeShadowServerAppLaunchTokenHint(options.launchToken);
if (!hint || !options.launchToken) return options.result;
const fetchFn = options.fetch ?? fetch;
const baseUrl = trimTrailingSlash(options.shadowApiBaseUrl ?? "http://localhost:3002");
const response = await fetchFn(
`${baseUrl}/api/servers/${encodeURIComponent(hint.serverId)}/apps/${encodeURIComponent(
hint.appKey
)}/launch/outbox`,
{
method: "POST",
headers: {
Authorization: `Bearer ${options.launchToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
commandName: options.commandName,
result: options.result
})
}
);
if (!response.ok) {
const payload = await readShadowServerAppResponsePayload(response);
throw new ShadowServerAppHttpError(
response.status,
shadowServerAppResponseErrorMessage(
response.status,
payload,
"Shadow launch outbox delivery failed"
),
payload
);
}
return readShadowServerAppResponsePayload(response);
}
function normalizeShadowServerAppCommandInput(value) {
if (value && typeof value === "object" && !Array.isArray(value) && "input" in value && Object.keys(value).every((key) => key === "input" || key === "channelId")) {
return value.input ?? {};
}
return value;
}
function createShadowServerAppManifest(manifest, options = {}) {
const publicBaseUrl = trimTrailingSlash(
options.publicBaseUrl ?? `http://localhost:${options.port ?? 4201}`
);
const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl ?? publicBaseUrl);
const iframeAllowedOrigins = (options.allowedOrigins ?? [publicBaseUrl]).map(
(origin) => urlOrigin(origin)
);
const iframePath = options.iframePath ?? "/shadow/server";
const iconPath = options.iconPath ?? "/assets/icon.svg";
const sourceAssetOrigin = urlOrigin(manifest.iconUrl);
return {
...manifest,
iconUrl: joinBasePath(publicBaseUrl, iconPath),
marketplace: manifest.marketplace ? {
...manifest.marketplace,
coverImageUrl: manifest.marketplace.coverImageUrl ? rebasePublicAssetUrl(
manifest.marketplace.coverImageUrl,
sourceAssetOrigin,
publicBaseUrl
) : manifest.marketplace.coverImageUrl,
gallery: manifest.marketplace.gallery?.map((item) => ({
...item,
url: rebasePublicAssetUrl(item.url, sourceAssetOrigin, publicBaseUrl)
})),
links: manifest.marketplace.links?.map((item) => ({
...item,
url: rebasePublicAssetUrl(item.url, sourceAssetOrigin, publicBaseUrl)
}))
} : manifest.marketplace,
iframe: manifest.iframe ? {
...manifest.iframe,
entry: joinBasePath(publicBaseUrl, iframePath),
allowedOrigins: iframeAllowedOrigins
} : manifest.iframe,
api: {
...manifest.api,
baseUrl: apiBaseUrl
}
};
}
function defineShadowServerApp(manifest, options = {}) {
return new ShadowServerAppRuntime(manifest, options);
}
var createShadowServerAppRuntime = defineShadowServerApp;
var ShadowServerAppCommandError = class extends Error {
status;
issues;
constructor(status, error, issues) {
super(error);
this.name = "ShadowServerAppCommandError";
this.status = status;
this.issues = issues;
}
};
function shadowServerAppError(status, error, issues) {
return new ShadowServerAppCommandError(status, error, issues);
}
var ShadowServerAppRuntime = class {
constructor(sourceManifest, options = {}) {
this.sourceManifest = sourceManifest;
this.options = options;
}
sourceManifest;
options;
manifest(options = {}) {
return createShadowServerAppManifest(this.sourceManifest, options);
}
defineCommands(handlers) {
return handlers;
}
actor(envelopeOrContext) {
return shadowServerAppActorRef(envelopeOrContext);
}
error(status, error, issues) {
return shadowServerAppError(status, error, issues);
}
async parseCommand(commandName, request) {
return parseShadowServerAppCommandRequest(
{
...request,
expectedCommand: commandName,
shadowBaseUrl: this.options.shadowBaseUrl,
fetchImpl: this.options.fetchImpl
}
);
}
async executeCommand(commandName, request, handlers) {
const parsed = await this.parseCommand(commandName, request);
if (!parsed.ok) return parseErrorResult(parsed);
return this.executeEnvelope(commandName, parsed.envelope, handlers);
}
async executeLocal(commandName, input, context, handlers) {
return this.executeEnvelope(
commandName,
{
input,
context: {
...context,
command: commandName
}
},
handlers
);
}
async executeEnvelope(commandName, envelope, handlers) {
const command = this.sourceManifest.commands.find((item) => item.name === commandName);
if (!command) return failureResult(404, "command_not_found");
const validation = validateShadowServerAppJsonSchema(command.inputSchema, envelope.input);
if (!validation.ok) return failureResult(422, "invalid_input", validation.issues);
const handler = handlers[commandName];
if (!handler) return failureResult(404, "command_not_found");
try {
const result = await handler(envelope.input, {
context: envelope.context,
actor: this.actor(envelope)
});
return { ok: true, status: 200, body: { ok: true, result } };
} catch (error) {
if (error instanceof ShadowServerAppCommandError) {
return failureResult(error.status, error.message, error.issues);
}
throw error;
}
}
};
async function introspectShadowServerAppToken(input) {
const baseUrl = trimTrailingSlash(input.shadowBaseUrl ?? "http://localhost:3002");
const fetchImpl = input.fetchImpl ?? fetch;
const response = await fetchImpl(
`${baseUrl}/api/servers/${encodeURIComponent(input.serverId)}/apps/${encodeURIComponent(
input.appKey
)}/oauth/introspect`,
{
method: "POST",
headers: {
Authorization: `Bearer ${input.token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ token: input.token })
}
);
if (!response.ok) return null;
const payload = await response.json();
return payload.active ? payload : null;
}
async function parseShadowServerAppCommandRequest(input) {
const token = extractShadowServerAppBearerToken(input.authorizationHeader);
const serverId = input.serverIdHeader;
const appKey = input.appKeyHeader;
if (!token || !serverId || !appKey) {
return { ok: false, status: 401, error: "missing_oauth" };
}
const introspection = await introspectShadowServerAppToken({
token,
serverId,
appKey,
shadowBaseUrl: input.shadowBaseUrl,
fetchImpl: input.fetchImpl
}).catch(() => null);
const context = introspection?.shadow;
if (!context) return { ok: false, status: 401, error: "invalid_token" };
if (context.command !== input.expectedCommand) {
return { ok: false, status: 403, error: "wrong_command" };
}
let commandInput;
if (input.requestInput !== void 0) {
commandInput = input.requestInput;
} else {
let body;
try {
body = JSON.parse(input.requestBody ?? "{}");
} catch {
return { ok: false, status: 400, error: "invalid_json" };
}
commandInput = body.input ?? {};
}
return {
ok: true,
envelope: {
input: commandInput,
context
}
};
}
function validateShadowServerAppJsonSchema(schema, value) {
if (!schema) return { ok: true };
const issues = [];
validateJsonSchemaValue(schema, value, "", issues);
return issues.length ? { ok: false, issues } : { ok: true };
}
function shadowServerAppActorDisplayName(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
const actor = context.actor;
const profile = actor.profile;
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`;
}
function shadowServerAppActorAvatarUrl(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
return context.actor.profile?.avatarUrl ?? null;
}
function shadowServerAppActorRef(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
const actor = context.actor;
return {
kind: actor.kind,
id: actor.buddyAgentId ?? actor.userId ?? actor.ownerId ?? "unknown",
userId: actor.userId ?? null,
buddyAgentId: actor.buddyAgentId ?? null,
ownerId: actor.ownerId ?? null,
displayName: shadowServerAppActorDisplayName(context),
avatarUrl: shadowServerAppActorAvatarUrl(context)
};
}
function parseErrorResult(error) {
return failureResult(error.status, error.error, error.issues);
}
function failureResult(status, error, issues) {
return {
ok: false,
status,
body: issues === void 0 ? { ok: false, error } : { ok: false, error, issues }
};
}
function validateJsonSchemaValue(schema, value, path, issues) {
if (Array.isArray(schema.oneOf)) {
const matches = schema.oneOf.some((option) => {
const nestedIssues = [];
if (option && typeof option === "object" && !Array.isArray(option)) {
validateJsonSchemaValue(
option,
value,
path,
nestedIssues
);
}
return nestedIssues.length === 0;
});
if (!matches) issues.push({ path, message: "Expected value matching one schema option" });
return;
}
const enumValues = schema.enum;
if (Array.isArray(enumValues) && !enumValues.includes(value)) {
issues.push({ path, message: `Expected one of ${enumValues.map(String).join(", ")}` });
return;
}
const type = schema.type;
if (type === "object") {
if (!value || typeof value !== "object" || Array.isArray(value)) {
issues.push({ path, message: "Expected object" });
return;
}
const record = value;
const properties = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties) ? schema.properties : {};
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
for (const key of required) {
if (!(key in record)) issues.push({ path: joinJsonPath(path, key), message: "Required" });
}
for (const [key, propertySchema] of Object.entries(properties)) {
if (record[key] !== void 0) {
validateJsonSchemaValue(propertySchema, record[key], joinJsonPath(path, key), issues);
}
}
const additionalProperties = schema.additionalProperties && typeof schema.additionalProperties === "object" && !Array.isArray(schema.additionalProperties) ? schema.additionalProperties : null;
if (additionalProperties) {
for (const [key, nestedValue] of Object.entries(record)) {
if (!(key in properties)) {
validateJsonSchemaValue(
additionalProperties,
nestedValue,
joinJsonPath(path, key),
issues
);
}
}
} else if (schema.additionalProperties === false) {
for (const key of Object.keys(record)) {
if (!(key in properties)) {
issues.push({ path: joinJsonPath(path, key), message: "Unknown property" });
}
}
}
return;
}
if (type === "array") {
if (!Array.isArray(value)) {
issues.push({ path, message: "Expected array" });
return;
}
const maxItems = typeof schema.maxItems === "number" ? schema.maxItems : null;
if (maxItems !== null && value.length > maxItems) {
issues.push({ path, message: `Expected at most ${maxItems} items` });
}
const itemSchema = schema.items && typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : null;
if (itemSchema) {
value.forEach(
(item, index) => validateJsonSchemaValue(itemSchema, item, `${path}[${index}]`, issues)
);
}
return;
}
if (type === "string") {
if (typeof value !== "string") {
issues.push({ path, message: "Expected string" });
return;
}
const maxLength = typeof schema.maxLength === "number" ? schema.maxLength : null;
const minLength = typeof schema.minLength === "number" ? schema.minLength : null;
if (minLength !== null && value.length < minLength) {
issues.push({ path, message: `Expected at least ${minLength} characters` });
}
if (maxLength !== null && value.length > maxLength) {
issues.push({ path, message: `Expected at most ${maxLength} characters` });
}
return;
}
if (type === "number" || type === "integer") {
if (typeof value !== "number" || !Number.isFinite(value)) {
issues.push({ path, message: "Expected number" });
return;
}
if (type === "integer" && !Number.isInteger(value)) {
issues.push({ path, message: "Expected integer" });
}
return;
}
if (type === "boolean" && typeof value !== "boolean") {
issues.push({ path, message: "Expected boolean" });
}
}
function joinJsonPath(parent, key) {
return parent ? `${parent}.${key}` : key;
}
export {
SHADOW_SERVER_APP_PROTOCOL,
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT,
SHADOW_SERVER_APP_COMMAND_EVENTS,
ShadowServerAppHttpError,
shadowServerAppInboxTaskEndpoint,
buildShadowServerAppInboxTaskRequest,
getShadowServerAppTaskCardId,
buildShadowServerAppInboxDelivery,
getShadowServerAppInboxDeliveries,
getShadowServerAppInboxErrors,
getShadowServerAppPendingInboxTasks,
getShadowServerAppChannelMessageDeliveries,
getShadowServerAppChannelMessageErrors,
getShadowServerAppPendingChannelMessages,
hasShadowServerAppPendingOutbox,
unwrapShadowServerAppCommandPayload,
readShadowServerAppCommandResponse,
ShadowServerAppOutbox,
extractShadowServerAppBearerToken,
decodeShadowServerAppLaunchTokenHint,
fetchShadowServerAppLaunchInboxes,
deliverShadowServerAppLaunchOutbox,
normalizeShadowServerAppCommandInput,
createShadowServerAppManifest,
defineShadowServerApp,
createShadowServerAppRuntime,
ShadowServerAppCommandError,
shadowServerAppError,
ShadowServerAppRuntime,
introspectShadowServerAppToken,
parseShadowServerAppCommandRequest,
validateShadowServerAppJsonSchema,
shadowServerAppActorDisplayName,
shadowServerAppActorAvatarUrl,
shadowServerAppActorRef,
BUDDY_INBOX_DELIVERY_PERMISSION
};
import {
BUDDY_INBOX_DELIVERY_PERMISSION,
decodeShadowServerAppLaunchTokenHint,
deliverShadowServerAppLaunchOutbox,
getShadowServerAppChannelMessageDeliveries,
getShadowServerAppChannelMessageErrors,
getShadowServerAppInboxDeliveries,
getShadowServerAppInboxErrors,
hasShadowServerAppPendingOutbox,
readShadowServerAppCommandResponse,
unwrapShadowServerAppCommandPayload
} from "./chunk-N55V4ZKC.js";
// src/bridge.ts
var SHADOW_BRIDGE_CAPABILITIES = [
"copilot.open",
"workspace.open",
"buddy.create.open",
"buddy.inboxes.list",
"buddy.grant.ensure",
"oauth.authorize",
"route.navigate"
];
function commandPath(basePath, commandName) {
return `${basePath.replace(/\/+$/u, "")}/${encodeURIComponent(commandName)}`;
}
function shadowServerAppMountedPathPrefix(windowRef) {
const win = windowRef ?? (typeof window === "undefined" ? null : window);
const pathname = win?.location?.pathname ?? "";
const segments = pathname.split("/").filter(Boolean);
const shadowIndex = segments.indexOf("shadow");
if (shadowIndex <= 0 || segments[shadowIndex + 1] !== "server") return "";
return `/${segments.slice(0, shadowIndex).join("/")}`;
}
function shadowServerAppMountedPath(path, windowRef) {
const normalized = path.startsWith("/") ? path : `/${path}`;
return `${shadowServerAppMountedPathPrefix(windowRef)}${normalized}`;
}
function isRecord(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function withoutUndefined(value) {
if (value === void 0) return {};
if (Array.isArray(value)) return value.map(withoutUndefined);
if (!isRecord(value)) return value;
return Object.fromEntries(
Object.entries(value).filter(([, entry]) => entry !== void 0).map(([key, entry]) => [key, withoutUndefined(entry)])
);
}
var ShadowServerAppBrowserClient = class {
bridge;
commandBasePath;
inboxesPath;
shadowApiBaseUrl;
fetchFn;
deliverLaunchOutboxFromBrowser;
win;
constructor(options = {}) {
this.bridge = new ShadowBridge(options);
const windowRef = options.windowRef ?? (typeof window === "undefined" ? null : window);
this.commandBasePath = options.commandBasePath ?? shadowServerAppMountedPath("/api/local/commands", windowRef);
this.inboxesPath = options.inboxesPath ?? shadowServerAppMountedPath("/api/local/inboxes", windowRef);
this.shadowApiBaseUrl = options.shadowApiBaseUrl;
this.fetchFn = options.fetch;
this.deliverLaunchOutboxFromBrowser = options.deliverLaunchOutboxFromBrowser ?? false;
this.win = windowRef;
}
bridgeAvailable() {
return this.bridge.isAvailable();
}
launchToken(param = "shadow_launch") {
if (!this.win) return null;
return new URLSearchParams(this.win.location.search).get(param);
}
launchHeaders(headers = {}, options = {}) {
const token = this.launchToken(options.launchTokenParam);
return token ? { ...headers, "X-Shadow-Launch-Token": token } : headers;
}
async command(commandName, input = {}) {
const response = await this.fetch(commandPath(this.commandBasePath, commandName), {
method: "POST",
headers: this.launchHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify({ input: withoutUndefined(input) })
});
const result = await readShadowServerAppCommandResponse(response);
return this.deliverLaunchOutbox(commandName, result);
}
async listBuddyInboxes(options = {}) {
if (this.bridge.isAvailable()) {
try {
return await this.bridge.listBuddyInboxes({ refresh: options.refresh });
} catch {
}
}
const response = await this.fetch(this.inboxesPath, { headers: this.launchHeaders() });
if (!response.ok) {
if (options.emptyOnError) return { inboxes: [] };
const message = await response.text().catch(() => "");
throw new Error(message || `Buddy inbox lookup failed (${response.status})`);
}
return await response.json();
}
async ensureBuddyTaskGrant(input) {
const buddyAgentId = input.agentId?.trim();
if (!buddyAgentId || !this.bridge.isAvailable()) return { granted: false, skipped: true };
return this.bridge.ensureBuddyGrant(
{
buddyAgentId,
permissions: input.permissions ?? [BUDDY_INBOX_DELIVERY_PERMISSION],
reason: input.reason
},
{ timeoutMs: input.timeoutMs ?? 6e4 }
);
}
openBuddyCreator(input = {}, options = {}) {
if (!this.bridge.isAvailable()) return Promise.resolve({ opened: false, agent: null });
return this.bridge.openBuddyCreator(input, options);
}
openCopilot(delivery, options = {}) {
return this.bridge.openCopilot(delivery, options);
}
openWorkspaceResource(input, options = {}) {
return this.bridge.openWorkspaceResource(input, options);
}
authorizeOAuth(input, options = {}) {
if (!this.bridge.isAvailable()) return Promise.resolve({ opened: false });
return this.bridge.authorizeOAuth(input, options);
}
inboxDeliveries(payload) {
return this.bridge.inboxDeliveries(payload);
}
inboxErrors(payload) {
return this.bridge.inboxErrors(payload);
}
channelMessageDeliveries(payload) {
return this.bridge.channelMessageDeliveries(payload);
}
channelMessageErrors(payload) {
return this.bridge.channelMessageErrors(payload);
}
async deliverLaunchOutbox(commandName, result) {
if (!this.deliverLaunchOutboxFromBrowser || !hasShadowServerAppPendingOutbox(result)) {
return result;
}
const launchToken = this.launchToken();
if (!decodeShadowServerAppLaunchTokenHint(launchToken)) return result;
return await deliverShadowServerAppLaunchOutbox({
commandName,
result,
launchToken,
shadowApiBaseUrl: this.shadowApiBaseUrl,
fetch: this.fetch.bind(this)
});
}
fetch(input, init) {
if (this.fetchFn) return this.fetchFn(input, init);
return globalThis.fetch(input, init);
}
};
function createShadowServerAppClient(options = {}) {
return new ShadowServerAppBrowserClient(options);
}
var createShadowServerAppBrowserClient = createShadowServerAppClient;
function createShadowServerAppRuntimeClient(options = {}) {
const windowRef = options.windowRef ?? (typeof window === "undefined" ? void 0 : window);
return new ShadowServerAppBrowserClient({
...options,
windowRef,
commandBasePath: options.commandBasePath ?? shadowServerAppMountedPath("/api/runtime/commands", windowRef),
inboxesPath: options.inboxesPath ?? shadowServerAppMountedPath("/api/runtime/inboxes", windowRef),
deliverLaunchOutboxFromBrowser: false
});
}
var ShadowBridge = class _ShadowBridge {
static capabilitiesRequestType = "shadow.app.capabilities.request";
static capabilitiesResponseType = "shadow.app.capabilities.response";
static openCopilotRequestType = "shadow.app.copilot.open.request";
static openCopilotResponseType = "shadow.app.copilot.open.response";
static openWorkspaceResourceRequestType = "shadow.app.workspace.open.request";
static openWorkspaceResourceResponseType = "shadow.app.workspace.open.response";
static openBuddyCreatorRequestType = "shadow.app.buddy.create.request";
static openBuddyCreatorResponseType = "shadow.app.buddy.create.response";
static listBuddyInboxesRequestType = "shadow.app.buddy.inboxes.request";
static listBuddyInboxesResponseType = "shadow.app.buddy.inboxes.response";
static ensureBuddyGrantRequestType = "shadow.app.buddy.grant.request";
static ensureBuddyGrantResponseType = "shadow.app.buddy.grant.response";
static authorizeOAuthRequestType = "shadow.app.oauth.authorize.request";
static authorizeOAuthResponseType = "shadow.app.oauth.authorize.response";
static inboxDeliveries(payload) {
return getShadowServerAppInboxDeliveries(payload);
}
static inboxErrors(payload) {
return getShadowServerAppInboxErrors(payload);
}
static channelMessageDeliveries(payload) {
return getShadowServerAppChannelMessageDeliveries(payload);
}
static channelMessageErrors(payload) {
return getShadowServerAppChannelMessageErrors(payload);
}
static unwrapCommandPayload(payload) {
return unwrapShadowServerAppCommandPayload(payload);
}
appKey;
targetOrigin;
timeoutMs;
win;
hasLaunchContext;
pending = /* @__PURE__ */ new Map();
onMessage = (event) => {
let data = event.data;
if (typeof data === "string") {
try {
data = JSON.parse(data || "{}");
} catch {
return;
}
}
if (!data || typeof data !== "object") return;
const record = data;
if (typeof record.requestId !== "string" || typeof record.type !== "string") return;
const entry = this.pending.get(record.requestId);
if (!entry || record.type !== entry.responseType) return;
this.pending.delete(record.requestId);
if (record.ok) entry.resolve(record.result);
else
entry.reject(
new Error(typeof record.error === "string" ? record.error : "Bridge request failed")
);
};
constructor(options = {}) {
this.win = options.windowRef ?? (typeof window === "undefined" ? null : window);
this.appKey = options.appKey ?? this.resolveLaunchAppKey();
this.targetOrigin = options.targetOrigin ?? "*";
this.timeoutMs = options.timeoutMs ?? 6e4;
this.hasLaunchContext = this.resolveLaunchContext();
this.win?.addEventListener("message", this.onMessage);
}
dispose() {
this.win?.removeEventListener("message", this.onMessage);
for (const entry of this.pending.values()) {
entry.reject(new Error("ShadowBridge disposed"));
}
this.pending.clear();
}
isAvailable() {
if (!this.win) return false;
return this.hasLaunchContext && (this.win.parent !== this.win || !!this.win.ReactNativeWebView);
}
capabilities(options = {}) {
return this.request(
_ShadowBridge.capabilitiesRequestType,
_ShadowBridge.capabilitiesResponseType,
{},
options.timeoutMs ?? 15e3
);
}
openCopilot(deliveryOrInput, options = {}) {
const input = "delivery" in deliveryOrInput ? deliveryOrInput : { delivery: deliveryOrInput };
return this.request(
_ShadowBridge.openCopilotRequestType,
_ShadowBridge.openCopilotResponseType,
input,
options.timeoutMs ?? 15e3
);
}
openWorkspaceResource(input, options = {}) {
return this.request(
_ShadowBridge.openWorkspaceResourceRequestType,
_ShadowBridge.openWorkspaceResourceResponseType,
input,
options.timeoutMs ?? 15e3
);
}
openBuddyCreator(input = {}, options = {}) {
return this.request(
_ShadowBridge.openBuddyCreatorRequestType,
_ShadowBridge.openBuddyCreatorResponseType,
input,
options.timeoutMs ?? 10 * 60 * 1e3
);
}
listBuddyInboxes(input = {}, options = {}) {
return this.request(
_ShadowBridge.listBuddyInboxesRequestType,
_ShadowBridge.listBuddyInboxesResponseType,
input,
options.timeoutMs ?? 15e3
);
}
ensureBuddyGrant(input, options = {}) {
return this.request(
_ShadowBridge.ensureBuddyGrantRequestType,
_ShadowBridge.ensureBuddyGrantResponseType,
input,
options.timeoutMs ?? 3e4
);
}
authorizeOAuth(input, options = {}) {
const payload = typeof input === "string" ? { authorizeUrl: input } : input;
return this.request(
_ShadowBridge.authorizeOAuthRequestType,
_ShadowBridge.authorizeOAuthResponseType,
payload,
options.timeoutMs ?? 10 * 60 * 1e3
);
}
unwrapCommandPayload(payload) {
return unwrapShadowServerAppCommandPayload(payload);
}
inboxDeliveries(payload) {
return getShadowServerAppInboxDeliveries(payload);
}
inboxErrors(payload) {
return getShadowServerAppInboxErrors(payload);
}
channelMessageDeliveries(payload) {
return getShadowServerAppChannelMessageDeliveries(payload);
}
channelMessageErrors(payload) {
return getShadowServerAppChannelMessageErrors(payload);
}
request(requestType, responseType, payload, timeoutMs = this.timeoutMs) {
if (!this.isAvailable()) {
return Promise.reject(
new Error("ShadowBridge is not available outside a Shadow launch frame")
);
}
const requestId = `req_${Math.random().toString(36).slice(2)}`;
return new Promise((resolve, reject) => {
this.pending.set(requestId, {
responseType,
resolve,
reject
});
this.postMessage({
type: requestType,
requestId,
...this.appKey ? { appKey: this.appKey } : {},
...payload
});
this.win?.setTimeout(() => {
if (!this.pending.has(requestId)) return;
this.pending.delete(requestId);
reject(new Error("Bridge request timed out"));
}, timeoutMs);
});
}
postMessage(message) {
if (!this.win) return;
if (this.win.ReactNativeWebView) {
this.win.ReactNativeWebView.postMessage(JSON.stringify(message));
return;
}
this.win.parent.postMessage(message, this.targetOrigin);
}
resolveLaunchContext() {
if (!this.win) return false;
const storageKey = this.appKey ? `shadow.bridge.launch:${this.appKey}` : null;
const memoryContexts = this.win.__shadowBridgeLaunchContexts ??= {};
const hasLaunchToken = new URLSearchParams(this.win.location.search).has("shadow_launch");
if (hasLaunchToken) {
if (this.appKey) memoryContexts[this.appKey] = true;
try {
if (storageKey) this.win.sessionStorage?.setItem(storageKey, "1");
} catch {
}
return true;
}
if (this.appKey && memoryContexts[this.appKey]) return true;
try {
return storageKey ? this.win.sessionStorage?.getItem(storageKey) === "1" : false;
} catch {
return false;
}
}
resolveLaunchAppKey() {
if (!this.win) return void 0;
const token = new URLSearchParams(this.win.location.search).get("shadow_launch");
return decodeShadowServerAppLaunchTokenHint(token)?.appKey;
}
};
export {
SHADOW_BRIDGE_CAPABILITIES,
shadowServerAppMountedPathPrefix,
shadowServerAppMountedPath,
ShadowServerAppBrowserClient,
createShadowServerAppClient,
createShadowServerAppBrowserClient,
createShadowServerAppRuntimeClient,
ShadowBridge
};
+2
-2

@@ -10,3 +10,3 @@ import {

shadowServerAppMountedPathPrefix
} from "./chunk-L5PHQEDC.js";
} from "./chunk-QX6PUTJZ.js";
import {

@@ -23,3 +23,3 @@ SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,

shadowServerAppInboxTaskEndpoint
} from "./chunk-5ODYWMBC.js";
} from "./chunk-N55V4ZKC.js";
export {

@@ -26,0 +26,0 @@ SHADOW_BRIDGE_CAPABILITIES,

@@ -33,2 +33,3 @@ "use strict";

}
options;
read() {

@@ -35,0 +36,0 @@ if (!(0, import_node_fs.existsSync)(this.options.filePath)) {

@@ -8,2 +8,3 @@ // src/server-app-node.ts

}
options;
read() {

@@ -10,0 +11,0 @@ if (!existsSync(this.options.filePath)) {

@@ -475,2 +475,4 @@ "use strict";

}
sourceManifest;
options;
manifest(options = {}) {

@@ -477,0 +479,0 @@ return createShadowServerAppManifest(this.sourceManifest, options);

@@ -39,3 +39,3 @@ import {

validateShadowServerAppJsonSchema
} from "./chunk-5ODYWMBC.js";
} from "./chunk-N55V4ZKC.js";
export {

@@ -42,0 +42,0 @@ BUDDY_INBOX_DELIVERY_PERMISSION,

{
"name": "@shadowob/sdk",
"version": "1.1.50",
"version": "1.1.51",
"description": "Shadow SDK — typed REST client and real-time Socket.IO event listener for Shadow servers",

@@ -51,3 +51,3 @@ "license": "MIT",

"socket.io-client": "^4.8.1",
"@shadowob/shared": "1.1.50"
"@shadowob/shared": "1.1.51"
},

@@ -54,0 +54,0 @@ "peerDependencies": {

// src/server-app.ts
import {
BUDDY_INBOX_DELIVERY_PERMISSION
} from "@shadowob/shared";
var SHADOW_SERVER_APP_PROTOCOL = "shadow.app/1";
var SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT = "server_app.command.completed";
var SHADOW_SERVER_APP_COMMAND_FAILED_EVENT = "server_app.command.failed";
var SHADOW_SERVER_APP_COMMAND_EVENTS = [
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT
];
var ShadowServerAppHttpError = class extends Error {
status;
payload;
constructor(status, message, payload) {
super(message);
this.name = "ShadowServerAppHttpError";
this.status = status;
this.payload = payload;
}
};
function isProtocolRecord(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function optionalProtocolString(value) {
return typeof value === "string" && value ? value : void 0;
}
function protocolPathSegment(value) {
return encodeURIComponent(value);
}
function shadowServerAppInboxTaskEndpoint(serverIdOrSlug, target) {
if ("channelId" in target && target.channelId) {
return `/api/channels/${protocolPathSegment(target.channelId)}/inbox/tasks`;
}
if ("agentId" in target && target.agentId) {
return `/api/servers/${protocolPathSegment(serverIdOrSlug)}/inboxes/${protocolPathSegment(
target.agentId
)}/tasks`;
}
throw new Error("Missing Inbox task target");
}
function buildShadowServerAppInboxTaskRequest(input) {
const appId = input.app.id ?? input.app.appId ?? input.app.appKey;
const serverAppData = isProtocolRecord(input.task.data?.serverApp) ? input.task.data.serverApp : {};
return {
endpoint: shadowServerAppInboxTaskEndpoint(input.serverIdOrSlug, input.target),
body: {
title: input.task.title,
body: input.task.body,
priority: input.task.priority,
tags: input.task.tags,
idempotencyKey: input.task.idempotencyKey,
requirements: input.task.requirements,
outputContract: input.task.outputContract,
privacy: input.task.privacy,
app: {
id: appId,
appId,
appKey: input.app.appKey,
name: input.app.name ?? input.app.label ?? input.app.appKey,
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {}
},
source: {
kind: "server_app",
id: appId,
appId,
appKey: input.app.appKey,
...input.app.name ? { appName: input.app.name } : {},
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {},
...input.app.serverId ? { serverId: input.app.serverId } : {},
...input.commandName ? { command: input.commandName } : {},
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.task.resource ? { resource: input.task.resource } : {}
},
data: {
...input.task.data ?? {},
serverApp: {
...serverAppData,
appKey: input.app.appKey,
name: input.app.name ?? input.app.label ?? input.app.appKey,
label: input.app.label ?? input.app.name ?? input.app.appKey,
...input.app.iconUrl ? { iconUrl: input.app.iconUrl } : {},
...input.commandName ? { command: input.commandName } : {}
}
}
}
};
}
function getShadowServerAppTaskCardId(message) {
const metadata = isProtocolRecord(message) ? message.metadata : null;
const cards = isProtocolRecord(metadata) && Array.isArray(metadata.cards) ? metadata.cards : [];
for (const item of cards) {
if (!isProtocolRecord(item)) continue;
if (item.kind === "task" && typeof item.id === "string" && item.id) return item.id;
}
return null;
}
function buildShadowServerAppInboxDelivery(input) {
const message = isProtocolRecord(input.message) ? input.message : {};
return {
..."agentId" in input.target && input.target.agentId ? { agentId: input.target.agentId } : {},
channelId: optionalProtocolString(message.channelId),
messageId: optionalProtocolString(message.id),
cardId: getShadowServerAppTaskCardId(message),
idempotencyKey: input.idempotencyKey
};
}
function shadowFromPayload(payload) {
if (payload.protocol === SHADOW_SERVER_APP_PROTOCOL) {
return payload;
}
const shadow = isProtocolRecord(payload.shadow) ? payload.shadow : null;
if (shadow?.protocol === SHADOW_SERVER_APP_PROTOCOL) {
return shadow;
}
return null;
}
function mergeShadowResult(value, shadow) {
if (!shadow) return value;
const existing = shadowFromPayload(value);
if (!existing) return { ...value, shadow };
return {
...value,
shadow: {
protocol: SHADOW_SERVER_APP_PROTOCOL,
outbox: {
...existing.outbox ?? {},
...shadow.outbox ?? {}
}
}
};
}
function getShadowServerAppInboxDeliveries(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.deliveries ?? [];
}
function getShadowServerAppInboxErrors(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.errors ?? [];
}
function getShadowServerAppPendingInboxTasks(payload, depth = 0) {
if (!isProtocolRecord(payload) || depth > 4) return [];
const shadow = shadowFromPayload(payload);
const tasks = shadow?.outbox?.inboxTasks ?? [];
return "result" in payload && payload.result !== void 0 ? [...tasks, ...getShadowServerAppPendingInboxTasks(payload.result, depth + 1)] : tasks;
}
function getShadowServerAppChannelMessageDeliveries(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.channelMessageDeliveries ?? [];
}
function getShadowServerAppChannelMessageErrors(payload) {
if (!isProtocolRecord(payload)) return [];
const shadow = shadowFromPayload(payload);
return shadow?.outbox?.channelMessageErrors ?? [];
}
function getShadowServerAppPendingChannelMessages(payload, depth = 0) {
if (!isProtocolRecord(payload) || depth > 4) return [];
const shadow = shadowFromPayload(payload);
const messages = shadow?.outbox?.channelMessages ?? [];
return "result" in payload && payload.result !== void 0 ? [...messages, ...getShadowServerAppPendingChannelMessages(payload.result, depth + 1)] : messages;
}
function hasShadowServerAppPendingOutbox(payload) {
return getShadowServerAppPendingInboxTasks(payload).length > 0 || getShadowServerAppPendingChannelMessages(payload).length > 0;
}
function isDomainResultWithEvents(payload) {
return Array.isArray(payload.events) && ("cursor" in payload || "result" in payload);
}
function isCommandPayloadEnvelope(payload) {
if (isDomainResultWithEvents(payload)) return false;
return payload.ok === true || payload.ok === false || shadowFromPayload(payload) !== null;
}
function unwrapShadowServerAppCommandPayload(payload) {
if (isProtocolRecord(payload) && payload.ok === false) {
throw new Error(typeof payload.error === "string" ? payload.error : "Command failed");
}
if (isProtocolRecord(payload) && "result" in payload && payload.result !== void 0 && isCommandPayloadEnvelope(payload)) {
const nested = unwrapShadowServerAppCommandPayload(payload.result);
const shadow = shadowFromPayload(payload);
if (isProtocolRecord(nested)) return mergeShadowResult(nested, shadow);
return nested;
}
return payload;
}
async function readShadowServerAppResponsePayload(response) {
const text = await response.text().catch(() => "");
if (!text.trim()) return null;
try {
return JSON.parse(text);
} catch {
if (!response.ok) return { ok: false, error: text };
throw new ShadowServerAppHttpError(response.status, "Command returned invalid JSON", text);
}
}
function shadowServerAppResponseErrorMessage(status, payload, fallback = "Command failed") {
if (isProtocolRecord(payload) && typeof payload.error === "string" && payload.error) {
return payload.error;
}
if (typeof payload === "string" && payload.trim()) return payload;
return status ? `${fallback} (${status})` : fallback;
}
async function readShadowServerAppCommandResponse(response) {
const payload = await readShadowServerAppResponsePayload(response);
if (!response.ok || isProtocolRecord(payload) && payload.ok === false) {
throw new ShadowServerAppHttpError(
response.status,
shadowServerAppResponseErrorMessage(response.status, payload),
payload
);
}
return unwrapShadowServerAppCommandPayload(payload);
}
var ShadowServerAppOutbox = class {
inboxTasks = [];
channelMessages = [];
enqueueInboxTask(task) {
this.inboxTasks.push(task);
return this;
}
enqueueInboxTasks(tasks) {
for (const task of tasks) this.enqueueInboxTask(task);
return this;
}
sendChannelMessage(message) {
this.channelMessages.push(message);
return this;
}
sendChannelMessages(messages) {
for (const message of messages) this.sendChannelMessage(message);
return this;
}
toShadow() {
return {
protocol: SHADOW_SERVER_APP_PROTOCOL,
outbox: {
...this.inboxTasks.length > 0 ? { inboxTasks: [...this.inboxTasks] } : {},
...this.channelMessages.length > 0 ? { channelMessages: [...this.channelMessages] } : {}
}
};
}
attachTo(result) {
return { ...result, shadow: this.toShadow() };
}
};
function trimTrailingSlash(value) {
return value.replace(/\/$/, "");
}
function joinBasePath(baseUrl, path) {
const cleanBase = trimTrailingSlash(baseUrl);
const cleanPath = path.startsWith("/") ? path : `/${path}`;
return `${cleanBase}${cleanPath}`;
}
function urlOrigin(value) {
try {
return new URL(value).origin;
} catch {
return null;
}
}
function rebasePublicAssetUrl(value, sourceOrigin, publicBaseUrl) {
if (!sourceOrigin) return value;
try {
const url = new URL(value);
if (url.origin !== sourceOrigin) return value;
return joinBasePath(publicBaseUrl, `${url.pathname}${url.search}${url.hash}`);
} catch {
return value;
}
}
function extractShadowServerAppBearerToken(value) {
if (!value) return null;
return value.toLowerCase().startsWith("bearer ") ? value.slice(7).trim() : null;
}
function decodeBase64UrlJson(value) {
try {
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
const binary = globalThis.atob(padded);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
} catch {
return null;
}
}
function decodeShadowServerAppLaunchTokenHint(token) {
if (!token) return null;
const parts = token.split(".");
if (parts.length !== 3 || parts[0] !== "sat_v1") return null;
const payload = decodeBase64UrlJson(parts[1]);
if (typeof payload?.serverId !== "string" || typeof payload.appKey !== "string") return null;
return { serverId: payload.serverId, appKey: payload.appKey };
}
async function fetchShadowServerAppLaunchInboxes(options) {
const hint = decodeShadowServerAppLaunchTokenHint(options.launchToken);
if (!hint || !options.launchToken) return { inboxes: [] };
const fetchFn = options.fetch ?? fetch;
const baseUrl = trimTrailingSlash(options.shadowApiBaseUrl ?? "http://localhost:3002");
const response = await fetchFn(
`${baseUrl}/api/servers/${encodeURIComponent(hint.serverId)}/apps/${encodeURIComponent(
hint.appKey
)}/launch/inboxes`,
{ headers: { Authorization: `Bearer ${options.launchToken}` } }
);
if (!response.ok) {
const message = await response.text().catch(() => "");
throw new Error(`Shadow launch inbox lookup failed (${response.status}): ${message}`);
}
return await response.json();
}
async function deliverShadowServerAppLaunchOutbox(options) {
const hint = decodeShadowServerAppLaunchTokenHint(options.launchToken);
if (!hint || !options.launchToken) return options.result;
const fetchFn = options.fetch ?? fetch;
const baseUrl = trimTrailingSlash(options.shadowApiBaseUrl ?? "http://localhost:3002");
const response = await fetchFn(
`${baseUrl}/api/servers/${encodeURIComponent(hint.serverId)}/apps/${encodeURIComponent(
hint.appKey
)}/launch/outbox`,
{
method: "POST",
headers: {
Authorization: `Bearer ${options.launchToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
commandName: options.commandName,
result: options.result
})
}
);
if (!response.ok) {
const payload = await readShadowServerAppResponsePayload(response);
throw new ShadowServerAppHttpError(
response.status,
shadowServerAppResponseErrorMessage(
response.status,
payload,
"Shadow launch outbox delivery failed"
),
payload
);
}
return readShadowServerAppResponsePayload(response);
}
function normalizeShadowServerAppCommandInput(value) {
if (value && typeof value === "object" && !Array.isArray(value) && "input" in value && Object.keys(value).every((key) => key === "input" || key === "channelId")) {
return value.input ?? {};
}
return value;
}
function createShadowServerAppManifest(manifest, options = {}) {
const publicBaseUrl = trimTrailingSlash(
options.publicBaseUrl ?? `http://localhost:${options.port ?? 4201}`
);
const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl ?? publicBaseUrl);
const iframeAllowedOrigins = (options.allowedOrigins ?? [publicBaseUrl]).map(
(origin) => urlOrigin(origin)
);
const iframePath = options.iframePath ?? "/shadow/server";
const iconPath = options.iconPath ?? "/assets/icon.svg";
const sourceAssetOrigin = urlOrigin(manifest.iconUrl);
return {
...manifest,
iconUrl: joinBasePath(publicBaseUrl, iconPath),
marketplace: manifest.marketplace ? {
...manifest.marketplace,
coverImageUrl: manifest.marketplace.coverImageUrl ? rebasePublicAssetUrl(
manifest.marketplace.coverImageUrl,
sourceAssetOrigin,
publicBaseUrl
) : manifest.marketplace.coverImageUrl,
gallery: manifest.marketplace.gallery?.map((item) => ({
...item,
url: rebasePublicAssetUrl(item.url, sourceAssetOrigin, publicBaseUrl)
})),
links: manifest.marketplace.links?.map((item) => ({
...item,
url: rebasePublicAssetUrl(item.url, sourceAssetOrigin, publicBaseUrl)
}))
} : manifest.marketplace,
iframe: manifest.iframe ? {
...manifest.iframe,
entry: joinBasePath(publicBaseUrl, iframePath),
allowedOrigins: iframeAllowedOrigins
} : manifest.iframe,
api: {
...manifest.api,
baseUrl: apiBaseUrl
}
};
}
function defineShadowServerApp(manifest, options = {}) {
return new ShadowServerAppRuntime(manifest, options);
}
var createShadowServerAppRuntime = defineShadowServerApp;
var ShadowServerAppCommandError = class extends Error {
status;
issues;
constructor(status, error, issues) {
super(error);
this.name = "ShadowServerAppCommandError";
this.status = status;
this.issues = issues;
}
};
function shadowServerAppError(status, error, issues) {
return new ShadowServerAppCommandError(status, error, issues);
}
var ShadowServerAppRuntime = class {
constructor(sourceManifest, options = {}) {
this.sourceManifest = sourceManifest;
this.options = options;
}
manifest(options = {}) {
return createShadowServerAppManifest(this.sourceManifest, options);
}
defineCommands(handlers) {
return handlers;
}
actor(envelopeOrContext) {
return shadowServerAppActorRef(envelopeOrContext);
}
error(status, error, issues) {
return shadowServerAppError(status, error, issues);
}
async parseCommand(commandName, request) {
return parseShadowServerAppCommandRequest(
{
...request,
expectedCommand: commandName,
shadowBaseUrl: this.options.shadowBaseUrl,
fetchImpl: this.options.fetchImpl
}
);
}
async executeCommand(commandName, request, handlers) {
const parsed = await this.parseCommand(commandName, request);
if (!parsed.ok) return parseErrorResult(parsed);
return this.executeEnvelope(commandName, parsed.envelope, handlers);
}
async executeLocal(commandName, input, context, handlers) {
return this.executeEnvelope(
commandName,
{
input,
context: {
...context,
command: commandName
}
},
handlers
);
}
async executeEnvelope(commandName, envelope, handlers) {
const command = this.sourceManifest.commands.find((item) => item.name === commandName);
if (!command) return failureResult(404, "command_not_found");
const validation = validateShadowServerAppJsonSchema(command.inputSchema, envelope.input);
if (!validation.ok) return failureResult(422, "invalid_input", validation.issues);
const handler = handlers[commandName];
if (!handler) return failureResult(404, "command_not_found");
try {
const result = await handler(envelope.input, {
context: envelope.context,
actor: this.actor(envelope)
});
return { ok: true, status: 200, body: { ok: true, result } };
} catch (error) {
if (error instanceof ShadowServerAppCommandError) {
return failureResult(error.status, error.message, error.issues);
}
throw error;
}
}
};
async function introspectShadowServerAppToken(input) {
const baseUrl = trimTrailingSlash(input.shadowBaseUrl ?? "http://localhost:3002");
const fetchImpl = input.fetchImpl ?? fetch;
const response = await fetchImpl(
`${baseUrl}/api/servers/${encodeURIComponent(input.serverId)}/apps/${encodeURIComponent(
input.appKey
)}/oauth/introspect`,
{
method: "POST",
headers: {
Authorization: `Bearer ${input.token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ token: input.token })
}
);
if (!response.ok) return null;
const payload = await response.json();
return payload.active ? payload : null;
}
async function parseShadowServerAppCommandRequest(input) {
const token = extractShadowServerAppBearerToken(input.authorizationHeader);
const serverId = input.serverIdHeader;
const appKey = input.appKeyHeader;
if (!token || !serverId || !appKey) {
return { ok: false, status: 401, error: "missing_oauth" };
}
const introspection = await introspectShadowServerAppToken({
token,
serverId,
appKey,
shadowBaseUrl: input.shadowBaseUrl,
fetchImpl: input.fetchImpl
}).catch(() => null);
const context = introspection?.shadow;
if (!context) return { ok: false, status: 401, error: "invalid_token" };
if (context.command !== input.expectedCommand) {
return { ok: false, status: 403, error: "wrong_command" };
}
let commandInput;
if (input.requestInput !== void 0) {
commandInput = input.requestInput;
} else {
let body;
try {
body = JSON.parse(input.requestBody ?? "{}");
} catch {
return { ok: false, status: 400, error: "invalid_json" };
}
commandInput = body.input ?? {};
}
return {
ok: true,
envelope: {
input: commandInput,
context
}
};
}
function validateShadowServerAppJsonSchema(schema, value) {
if (!schema) return { ok: true };
const issues = [];
validateJsonSchemaValue(schema, value, "", issues);
return issues.length ? { ok: false, issues } : { ok: true };
}
function shadowServerAppActorDisplayName(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
const actor = context.actor;
const profile = actor.profile;
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`;
}
function shadowServerAppActorAvatarUrl(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
return context.actor.profile?.avatarUrl ?? null;
}
function shadowServerAppActorRef(envelopeOrContext) {
const context = "context" in envelopeOrContext ? envelopeOrContext.context : envelopeOrContext;
const actor = context.actor;
return {
kind: actor.kind,
id: actor.buddyAgentId ?? actor.userId ?? actor.ownerId ?? "unknown",
userId: actor.userId ?? null,
buddyAgentId: actor.buddyAgentId ?? null,
ownerId: actor.ownerId ?? null,
displayName: shadowServerAppActorDisplayName(context),
avatarUrl: shadowServerAppActorAvatarUrl(context)
};
}
function parseErrorResult(error) {
return failureResult(error.status, error.error, error.issues);
}
function failureResult(status, error, issues) {
return {
ok: false,
status,
body: issues === void 0 ? { ok: false, error } : { ok: false, error, issues }
};
}
function validateJsonSchemaValue(schema, value, path, issues) {
if (Array.isArray(schema.oneOf)) {
const matches = schema.oneOf.some((option) => {
const nestedIssues = [];
if (option && typeof option === "object" && !Array.isArray(option)) {
validateJsonSchemaValue(
option,
value,
path,
nestedIssues
);
}
return nestedIssues.length === 0;
});
if (!matches) issues.push({ path, message: "Expected value matching one schema option" });
return;
}
const enumValues = schema.enum;
if (Array.isArray(enumValues) && !enumValues.includes(value)) {
issues.push({ path, message: `Expected one of ${enumValues.map(String).join(", ")}` });
return;
}
const type = schema.type;
if (type === "object") {
if (!value || typeof value !== "object" || Array.isArray(value)) {
issues.push({ path, message: "Expected object" });
return;
}
const record = value;
const properties = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties) ? schema.properties : {};
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
for (const key of required) {
if (!(key in record)) issues.push({ path: joinJsonPath(path, key), message: "Required" });
}
for (const [key, propertySchema] of Object.entries(properties)) {
if (record[key] !== void 0) {
validateJsonSchemaValue(propertySchema, record[key], joinJsonPath(path, key), issues);
}
}
const additionalProperties = schema.additionalProperties && typeof schema.additionalProperties === "object" && !Array.isArray(schema.additionalProperties) ? schema.additionalProperties : null;
if (additionalProperties) {
for (const [key, nestedValue] of Object.entries(record)) {
if (!(key in properties)) {
validateJsonSchemaValue(
additionalProperties,
nestedValue,
joinJsonPath(path, key),
issues
);
}
}
} else if (schema.additionalProperties === false) {
for (const key of Object.keys(record)) {
if (!(key in properties)) {
issues.push({ path: joinJsonPath(path, key), message: "Unknown property" });
}
}
}
return;
}
if (type === "array") {
if (!Array.isArray(value)) {
issues.push({ path, message: "Expected array" });
return;
}
const maxItems = typeof schema.maxItems === "number" ? schema.maxItems : null;
if (maxItems !== null && value.length > maxItems) {
issues.push({ path, message: `Expected at most ${maxItems} items` });
}
const itemSchema = schema.items && typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : null;
if (itemSchema) {
value.forEach(
(item, index) => validateJsonSchemaValue(itemSchema, item, `${path}[${index}]`, issues)
);
}
return;
}
if (type === "string") {
if (typeof value !== "string") {
issues.push({ path, message: "Expected string" });
return;
}
const maxLength = typeof schema.maxLength === "number" ? schema.maxLength : null;
const minLength = typeof schema.minLength === "number" ? schema.minLength : null;
if (minLength !== null && value.length < minLength) {
issues.push({ path, message: `Expected at least ${minLength} characters` });
}
if (maxLength !== null && value.length > maxLength) {
issues.push({ path, message: `Expected at most ${maxLength} characters` });
}
return;
}
if (type === "number" || type === "integer") {
if (typeof value !== "number" || !Number.isFinite(value)) {
issues.push({ path, message: "Expected number" });
return;
}
if (type === "integer" && !Number.isInteger(value)) {
issues.push({ path, message: "Expected integer" });
}
return;
}
if (type === "boolean" && typeof value !== "boolean") {
issues.push({ path, message: "Expected boolean" });
}
}
function joinJsonPath(parent, key) {
return parent ? `${parent}.${key}` : key;
}
export {
SHADOW_SERVER_APP_PROTOCOL,
SHADOW_SERVER_APP_COMMAND_COMPLETED_EVENT,
SHADOW_SERVER_APP_COMMAND_FAILED_EVENT,
SHADOW_SERVER_APP_COMMAND_EVENTS,
ShadowServerAppHttpError,
shadowServerAppInboxTaskEndpoint,
buildShadowServerAppInboxTaskRequest,
getShadowServerAppTaskCardId,
buildShadowServerAppInboxDelivery,
getShadowServerAppInboxDeliveries,
getShadowServerAppInboxErrors,
getShadowServerAppPendingInboxTasks,
getShadowServerAppChannelMessageDeliveries,
getShadowServerAppChannelMessageErrors,
getShadowServerAppPendingChannelMessages,
hasShadowServerAppPendingOutbox,
unwrapShadowServerAppCommandPayload,
readShadowServerAppCommandResponse,
ShadowServerAppOutbox,
extractShadowServerAppBearerToken,
decodeShadowServerAppLaunchTokenHint,
fetchShadowServerAppLaunchInboxes,
deliverShadowServerAppLaunchOutbox,
normalizeShadowServerAppCommandInput,
createShadowServerAppManifest,
defineShadowServerApp,
createShadowServerAppRuntime,
ShadowServerAppCommandError,
shadowServerAppError,
ShadowServerAppRuntime,
introspectShadowServerAppToken,
parseShadowServerAppCommandRequest,
validateShadowServerAppJsonSchema,
shadowServerAppActorDisplayName,
shadowServerAppActorAvatarUrl,
shadowServerAppActorRef,
BUDDY_INBOX_DELIVERY_PERMISSION
};
import {
BUDDY_INBOX_DELIVERY_PERMISSION,
decodeShadowServerAppLaunchTokenHint,
deliverShadowServerAppLaunchOutbox,
getShadowServerAppChannelMessageDeliveries,
getShadowServerAppChannelMessageErrors,
getShadowServerAppInboxDeliveries,
getShadowServerAppInboxErrors,
hasShadowServerAppPendingOutbox,
readShadowServerAppCommandResponse,
unwrapShadowServerAppCommandPayload
} from "./chunk-5ODYWMBC.js";
// src/bridge.ts
var SHADOW_BRIDGE_CAPABILITIES = [
"copilot.open",
"workspace.open",
"buddy.create.open",
"buddy.inboxes.list",
"buddy.grant.ensure",
"oauth.authorize",
"route.navigate"
];
function commandPath(basePath, commandName) {
return `${basePath.replace(/\/+$/u, "")}/${encodeURIComponent(commandName)}`;
}
function shadowServerAppMountedPathPrefix(windowRef) {
const win = windowRef ?? (typeof window === "undefined" ? null : window);
const pathname = win?.location?.pathname ?? "";
const segments = pathname.split("/").filter(Boolean);
const shadowIndex = segments.indexOf("shadow");
if (shadowIndex <= 0 || segments[shadowIndex + 1] !== "server") return "";
return `/${segments.slice(0, shadowIndex).join("/")}`;
}
function shadowServerAppMountedPath(path, windowRef) {
const normalized = path.startsWith("/") ? path : `/${path}`;
return `${shadowServerAppMountedPathPrefix(windowRef)}${normalized}`;
}
function isRecord(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function withoutUndefined(value) {
if (value === void 0) return {};
if (Array.isArray(value)) return value.map(withoutUndefined);
if (!isRecord(value)) return value;
return Object.fromEntries(
Object.entries(value).filter(([, entry]) => entry !== void 0).map(([key, entry]) => [key, withoutUndefined(entry)])
);
}
var ShadowServerAppBrowserClient = class {
bridge;
commandBasePath;
inboxesPath;
shadowApiBaseUrl;
fetchFn;
deliverLaunchOutboxFromBrowser;
win;
constructor(options = {}) {
this.bridge = new ShadowBridge(options);
const windowRef = options.windowRef ?? (typeof window === "undefined" ? null : window);
this.commandBasePath = options.commandBasePath ?? shadowServerAppMountedPath("/api/local/commands", windowRef);
this.inboxesPath = options.inboxesPath ?? shadowServerAppMountedPath("/api/local/inboxes", windowRef);
this.shadowApiBaseUrl = options.shadowApiBaseUrl;
this.fetchFn = options.fetch;
this.deliverLaunchOutboxFromBrowser = options.deliverLaunchOutboxFromBrowser ?? false;
this.win = windowRef;
}
bridgeAvailable() {
return this.bridge.isAvailable();
}
launchToken(param = "shadow_launch") {
if (!this.win) return null;
return new URLSearchParams(this.win.location.search).get(param);
}
launchHeaders(headers = {}, options = {}) {
const token = this.launchToken(options.launchTokenParam);
return token ? { ...headers, "X-Shadow-Launch-Token": token } : headers;
}
async command(commandName, input = {}) {
const response = await this.fetch(commandPath(this.commandBasePath, commandName), {
method: "POST",
headers: this.launchHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify({ input: withoutUndefined(input) })
});
const result = await readShadowServerAppCommandResponse(response);
return this.deliverLaunchOutbox(commandName, result);
}
async listBuddyInboxes(options = {}) {
if (this.bridge.isAvailable()) {
try {
return await this.bridge.listBuddyInboxes({ refresh: options.refresh });
} catch {
}
}
const response = await this.fetch(this.inboxesPath, { headers: this.launchHeaders() });
if (!response.ok) {
if (options.emptyOnError) return { inboxes: [] };
const message = await response.text().catch(() => "");
throw new Error(message || `Buddy inbox lookup failed (${response.status})`);
}
return await response.json();
}
async ensureBuddyTaskGrant(input) {
const buddyAgentId = input.agentId?.trim();
if (!buddyAgentId || !this.bridge.isAvailable()) return { granted: false, skipped: true };
return this.bridge.ensureBuddyGrant(
{
buddyAgentId,
permissions: input.permissions ?? [BUDDY_INBOX_DELIVERY_PERMISSION],
reason: input.reason
},
{ timeoutMs: input.timeoutMs ?? 6e4 }
);
}
openBuddyCreator(input = {}, options = {}) {
if (!this.bridge.isAvailable()) return Promise.resolve({ opened: false, agent: null });
return this.bridge.openBuddyCreator(input, options);
}
openCopilot(delivery, options = {}) {
return this.bridge.openCopilot(delivery, options);
}
openWorkspaceResource(input, options = {}) {
return this.bridge.openWorkspaceResource(input, options);
}
authorizeOAuth(input, options = {}) {
if (!this.bridge.isAvailable()) return Promise.resolve({ opened: false });
return this.bridge.authorizeOAuth(input, options);
}
inboxDeliveries(payload) {
return this.bridge.inboxDeliveries(payload);
}
inboxErrors(payload) {
return this.bridge.inboxErrors(payload);
}
channelMessageDeliveries(payload) {
return this.bridge.channelMessageDeliveries(payload);
}
channelMessageErrors(payload) {
return this.bridge.channelMessageErrors(payload);
}
async deliverLaunchOutbox(commandName, result) {
if (!this.deliverLaunchOutboxFromBrowser || !hasShadowServerAppPendingOutbox(result)) {
return result;
}
const launchToken = this.launchToken();
if (!decodeShadowServerAppLaunchTokenHint(launchToken)) return result;
return await deliverShadowServerAppLaunchOutbox({
commandName,
result,
launchToken,
shadowApiBaseUrl: this.shadowApiBaseUrl,
fetch: this.fetch.bind(this)
});
}
fetch(input, init) {
if (this.fetchFn) return this.fetchFn(input, init);
return globalThis.fetch(input, init);
}
};
function createShadowServerAppClient(options = {}) {
return new ShadowServerAppBrowserClient(options);
}
var createShadowServerAppBrowserClient = createShadowServerAppClient;
function createShadowServerAppRuntimeClient(options = {}) {
const windowRef = options.windowRef ?? (typeof window === "undefined" ? void 0 : window);
return new ShadowServerAppBrowserClient({
...options,
windowRef,
commandBasePath: options.commandBasePath ?? shadowServerAppMountedPath("/api/runtime/commands", windowRef),
inboxesPath: options.inboxesPath ?? shadowServerAppMountedPath("/api/runtime/inboxes", windowRef),
deliverLaunchOutboxFromBrowser: false
});
}
var ShadowBridge = class _ShadowBridge {
static capabilitiesRequestType = "shadow.app.capabilities.request";
static capabilitiesResponseType = "shadow.app.capabilities.response";
static openCopilotRequestType = "shadow.app.copilot.open.request";
static openCopilotResponseType = "shadow.app.copilot.open.response";
static openWorkspaceResourceRequestType = "shadow.app.workspace.open.request";
static openWorkspaceResourceResponseType = "shadow.app.workspace.open.response";
static openBuddyCreatorRequestType = "shadow.app.buddy.create.request";
static openBuddyCreatorResponseType = "shadow.app.buddy.create.response";
static listBuddyInboxesRequestType = "shadow.app.buddy.inboxes.request";
static listBuddyInboxesResponseType = "shadow.app.buddy.inboxes.response";
static ensureBuddyGrantRequestType = "shadow.app.buddy.grant.request";
static ensureBuddyGrantResponseType = "shadow.app.buddy.grant.response";
static authorizeOAuthRequestType = "shadow.app.oauth.authorize.request";
static authorizeOAuthResponseType = "shadow.app.oauth.authorize.response";
static inboxDeliveries(payload) {
return getShadowServerAppInboxDeliveries(payload);
}
static inboxErrors(payload) {
return getShadowServerAppInboxErrors(payload);
}
static channelMessageDeliveries(payload) {
return getShadowServerAppChannelMessageDeliveries(payload);
}
static channelMessageErrors(payload) {
return getShadowServerAppChannelMessageErrors(payload);
}
static unwrapCommandPayload(payload) {
return unwrapShadowServerAppCommandPayload(payload);
}
appKey;
targetOrigin;
timeoutMs;
win;
hasLaunchContext;
pending = /* @__PURE__ */ new Map();
onMessage = (event) => {
let data = event.data;
if (typeof data === "string") {
try {
data = JSON.parse(data || "{}");
} catch {
return;
}
}
if (!data || typeof data !== "object") return;
const record = data;
if (typeof record.requestId !== "string" || typeof record.type !== "string") return;
const entry = this.pending.get(record.requestId);
if (!entry || record.type !== entry.responseType) return;
this.pending.delete(record.requestId);
if (record.ok) entry.resolve(record.result);
else
entry.reject(
new Error(typeof record.error === "string" ? record.error : "Bridge request failed")
);
};
constructor(options = {}) {
this.win = options.windowRef ?? (typeof window === "undefined" ? null : window);
this.appKey = options.appKey ?? this.resolveLaunchAppKey();
this.targetOrigin = options.targetOrigin ?? "*";
this.timeoutMs = options.timeoutMs ?? 6e4;
this.hasLaunchContext = this.resolveLaunchContext();
this.win?.addEventListener("message", this.onMessage);
}
dispose() {
this.win?.removeEventListener("message", this.onMessage);
for (const entry of this.pending.values()) {
entry.reject(new Error("ShadowBridge disposed"));
}
this.pending.clear();
}
isAvailable() {
if (!this.win) return false;
return this.hasLaunchContext && (this.win.parent !== this.win || !!this.win.ReactNativeWebView);
}
capabilities(options = {}) {
return this.request(
_ShadowBridge.capabilitiesRequestType,
_ShadowBridge.capabilitiesResponseType,
{},
options.timeoutMs ?? 15e3
);
}
openCopilot(deliveryOrInput, options = {}) {
const input = "delivery" in deliveryOrInput ? deliveryOrInput : { delivery: deliveryOrInput };
return this.request(
_ShadowBridge.openCopilotRequestType,
_ShadowBridge.openCopilotResponseType,
input,
options.timeoutMs ?? 15e3
);
}
openWorkspaceResource(input, options = {}) {
return this.request(
_ShadowBridge.openWorkspaceResourceRequestType,
_ShadowBridge.openWorkspaceResourceResponseType,
input,
options.timeoutMs ?? 15e3
);
}
openBuddyCreator(input = {}, options = {}) {
return this.request(
_ShadowBridge.openBuddyCreatorRequestType,
_ShadowBridge.openBuddyCreatorResponseType,
input,
options.timeoutMs ?? 10 * 60 * 1e3
);
}
listBuddyInboxes(input = {}, options = {}) {
return this.request(
_ShadowBridge.listBuddyInboxesRequestType,
_ShadowBridge.listBuddyInboxesResponseType,
input,
options.timeoutMs ?? 15e3
);
}
ensureBuddyGrant(input, options = {}) {
return this.request(
_ShadowBridge.ensureBuddyGrantRequestType,
_ShadowBridge.ensureBuddyGrantResponseType,
input,
options.timeoutMs ?? 3e4
);
}
authorizeOAuth(input, options = {}) {
const payload = typeof input === "string" ? { authorizeUrl: input } : input;
return this.request(
_ShadowBridge.authorizeOAuthRequestType,
_ShadowBridge.authorizeOAuthResponseType,
payload,
options.timeoutMs ?? 10 * 60 * 1e3
);
}
unwrapCommandPayload(payload) {
return unwrapShadowServerAppCommandPayload(payload);
}
inboxDeliveries(payload) {
return getShadowServerAppInboxDeliveries(payload);
}
inboxErrors(payload) {
return getShadowServerAppInboxErrors(payload);
}
channelMessageDeliveries(payload) {
return getShadowServerAppChannelMessageDeliveries(payload);
}
channelMessageErrors(payload) {
return getShadowServerAppChannelMessageErrors(payload);
}
request(requestType, responseType, payload, timeoutMs = this.timeoutMs) {
if (!this.isAvailable()) {
return Promise.reject(
new Error("ShadowBridge is not available outside a Shadow launch frame")
);
}
const requestId = `req_${Math.random().toString(36).slice(2)}`;
return new Promise((resolve, reject) => {
this.pending.set(requestId, {
responseType,
resolve,
reject
});
this.postMessage({
type: requestType,
requestId,
...this.appKey ? { appKey: this.appKey } : {},
...payload
});
this.win?.setTimeout(() => {
if (!this.pending.has(requestId)) return;
this.pending.delete(requestId);
reject(new Error("Bridge request timed out"));
}, timeoutMs);
});
}
postMessage(message) {
if (!this.win) return;
if (this.win.ReactNativeWebView) {
this.win.ReactNativeWebView.postMessage(JSON.stringify(message));
return;
}
this.win.parent.postMessage(message, this.targetOrigin);
}
resolveLaunchContext() {
if (!this.win) return false;
const storageKey = this.appKey ? `shadow.bridge.launch:${this.appKey}` : null;
const memoryContexts = this.win.__shadowBridgeLaunchContexts ??= {};
const hasLaunchToken = new URLSearchParams(this.win.location.search).has("shadow_launch");
if (hasLaunchToken) {
if (this.appKey) memoryContexts[this.appKey] = true;
try {
if (storageKey) this.win.sessionStorage?.setItem(storageKey, "1");
} catch {
}
return true;
}
if (this.appKey && memoryContexts[this.appKey]) return true;
try {
return storageKey ? this.win.sessionStorage?.getItem(storageKey) === "1" : false;
} catch {
return false;
}
}
resolveLaunchAppKey() {
if (!this.win) return void 0;
const token = new URLSearchParams(this.win.location.search).get("shadow_launch");
return decodeShadowServerAppLaunchTokenHint(token)?.appKey;
}
};
export {
SHADOW_BRIDGE_CAPABILITIES,
shadowServerAppMountedPathPrefix,
shadowServerAppMountedPath,
ShadowServerAppBrowserClient,
createShadowServerAppClient,
createShadowServerAppBrowserClient,
createShadowServerAppRuntimeClient,
ShadowBridge
};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display