@tappify/extension-sdk 2.1.0-beta.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -54
- package/dist/{chunk-6HETNZJY.cjs → chunk-2DBSVKAU.cjs} +1 -1
- package/dist/{chunk-6HETNZJY.cjs.map → chunk-2DBSVKAU.cjs.map} +1 -1
- package/dist/{chunk-O357OJXL.cjs → chunk-3YWVYA42.cjs} +18 -14
- package/dist/chunk-3YWVYA42.cjs.map +1 -0
- package/dist/{chunk-GAUZYBXE.cjs → chunk-5AAPD5WV.cjs} +11 -11
- package/dist/chunk-5AAPD5WV.cjs.map +1 -0
- package/dist/{chunk-SGEYDV3K.mjs → chunk-BDCX6LRQ.mjs} +3 -3
- package/dist/chunk-BDCX6LRQ.mjs.map +1 -0
- package/dist/{chunk-KZ5NGFUP.mjs → chunk-C3YNVHBP.mjs} +7 -20
- package/dist/chunk-C3YNVHBP.mjs.map +1 -0
- package/dist/{chunk-QXD7WVKK.mjs → chunk-CSTYBKZC.mjs} +1 -1
- package/dist/chunk-CSTYBKZC.mjs.map +1 -0
- package/dist/{chunk-SWG7CNTZ.mjs → chunk-CTSZ5D7S.mjs} +5 -9
- package/dist/chunk-CTSZ5D7S.mjs.map +1 -0
- package/dist/chunk-EW54H7CR.cjs +722 -0
- package/dist/chunk-EW54H7CR.cjs.map +1 -0
- package/dist/chunk-EX3NFXDK.mjs +722 -0
- package/dist/chunk-EX3NFXDK.mjs.map +1 -0
- package/dist/{chunk-A74HCO6T.cjs → chunk-G7XZSN2Q.cjs} +18 -31
- package/dist/chunk-G7XZSN2Q.cjs.map +1 -0
- package/dist/{chunk-RBIQT75L.mjs → chunk-GG3IX52W.mjs} +2 -2
- package/dist/{chunk-SLGOHIBL.mjs → chunk-IGK2AP6J.mjs} +17 -13
- package/dist/chunk-IGK2AP6J.mjs.map +1 -0
- package/dist/{chunk-VV7ELNRW.cjs → chunk-JMUK3K47.cjs} +3 -3
- package/dist/{chunk-VV7ELNRW.cjs.map → chunk-JMUK3K47.cjs.map} +1 -1
- package/dist/{chunk-SGCYEEXD.mjs → chunk-MNDVYPEP.mjs} +2 -9
- package/dist/chunk-MNDVYPEP.mjs.map +1 -0
- package/dist/{chunk-BPCGXXMD.cjs → chunk-NFUPPCX4.cjs} +18 -22
- package/dist/chunk-NFUPPCX4.cjs.map +1 -0
- package/dist/{chunk-PRCZMOLD.cjs → chunk-OPPYEA7F.cjs} +3 -3
- package/dist/{chunk-PRCZMOLD.cjs.map → chunk-OPPYEA7F.cjs.map} +1 -1
- package/dist/{chunk-7YKH4LZN.cjs → chunk-PYJMJF44.cjs} +2 -9
- package/dist/chunk-PYJMJF44.cjs.map +1 -0
- package/dist/{chunk-QA5AL3VZ.mjs → chunk-QHM7XNRK.mjs} +2 -2
- package/dist/{chunk-QA5AL3VZ.mjs.map → chunk-QHM7XNRK.mjs.map} +1 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +7 -43
- package/dist/codegen.d.ts +7 -43
- package/dist/codegen.mjs.map +1 -1
- package/dist/{constants-Bh759FUI.d.cts → constants-DJ_KgZpN.d.cts} +8 -44
- package/dist/{constants-Bh759FUI.d.ts → constants-DJ_KgZpN.d.ts} +8 -44
- package/dist/{context-mr8R5CNt.d.ts → context-Dt6xh_5t.d.cts} +4 -5
- package/dist/{context-DI0EJCRD.d.cts → context-Ml7k_V14.d.ts} +4 -5
- package/dist/{errors-Cqh0wrqt.d.ts → errors-COsBh7kd.d.cts} +2 -3
- package/dist/{errors-Cqh0wrqt.d.cts → errors-COsBh7kd.d.ts} +2 -3
- package/dist/handler-CZIlZ5vu.d.ts +227 -0
- package/dist/handler-kU9Jf36K.d.cts +227 -0
- package/dist/host.cjs +4 -4
- package/dist/host.d.cts +4 -4
- package/dist/host.d.ts +4 -4
- package/dist/host.mjs +3 -3
- package/dist/index.cjs +34 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +627 -69
- package/dist/index.d.ts +627 -69
- package/dist/index.mjs +33 -4
- package/dist/manifest.cjs +24 -136
- package/dist/manifest.cjs.map +1 -1
- package/dist/manifest.d.cts +12 -97
- package/dist/manifest.d.ts +12 -97
- package/dist/manifest.mjs +10 -122
- package/dist/manifest.mjs.map +1 -1
- package/dist/server.cjs +66 -14
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +61 -9
- package/dist/server.mjs.map +1 -1
- package/dist/styles.css +367 -0
- package/dist/testing/index.cjs +13 -72
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +10 -29
- package/dist/testing/index.d.ts +10 -29
- package/dist/testing/index.mjs +13 -72
- package/dist/testing/index.mjs.map +1 -1
- package/dist/testing/jest.cjs +5 -5
- package/dist/testing/jest.mjs +2 -2
- package/dist/testing/vitest.cjs +5 -5
- package/dist/testing/vitest.cjs.map +1 -1
- package/dist/testing/vitest.d.cts +0 -10
- package/dist/testing/vitest.d.ts +0 -10
- package/dist/testing/vitest.mjs +2 -2
- package/dist/testing/vitest.mjs.map +1 -1
- package/dist/{types-uGHrRaOj.d.ts → types-DDAGLpov.d.ts} +3 -4
- package/dist/{types-BdDaLnqs.d.cts → types-DrpQSv_2.d.cts} +3 -4
- package/dist/vite-preview.cjs +125 -294
- package/dist/vite-preview.cjs.map +1 -1
- package/dist/vite-preview.d.cts +24 -6
- package/dist/vite-preview.d.ts +24 -6
- package/dist/vite-preview.mjs +118 -287
- package/dist/vite-preview.mjs.map +1 -1
- package/dist/vite.cjs +24 -137
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +7 -43
- package/dist/vite.d.ts +7 -43
- package/dist/vite.mjs +18 -131
- package/dist/vite.mjs.map +1 -1
- package/package.json +2 -12
- package/schema/extension-v2.json +121 -440
- package/dist/chunk-2CY5HVY7.mjs +0 -59
- package/dist/chunk-2CY5HVY7.mjs.map +0 -1
- package/dist/chunk-7YKH4LZN.cjs.map +0 -1
- package/dist/chunk-A74HCO6T.cjs.map +0 -1
- package/dist/chunk-BPCGXXMD.cjs.map +0 -1
- package/dist/chunk-EB5XWQAU.cjs +0 -132
- package/dist/chunk-EB5XWQAU.cjs.map +0 -1
- package/dist/chunk-GAUZYBXE.cjs.map +0 -1
- package/dist/chunk-KZ5NGFUP.mjs.map +0 -1
- package/dist/chunk-O357OJXL.cjs.map +0 -1
- package/dist/chunk-ONJMPO3P.cjs +0 -59
- package/dist/chunk-ONJMPO3P.cjs.map +0 -1
- package/dist/chunk-QJMZRV7A.mjs +0 -132
- package/dist/chunk-QJMZRV7A.mjs.map +0 -1
- package/dist/chunk-QXD7WVKK.mjs.map +0 -1
- package/dist/chunk-SGCYEEXD.mjs.map +0 -1
- package/dist/chunk-SGEYDV3K.mjs.map +0 -1
- package/dist/chunk-SLGOHIBL.mjs.map +0 -1
- package/dist/chunk-SWG7CNTZ.mjs.map +0 -1
- package/dist/client-BO3kavn0.d.ts +0 -25
- package/dist/client-DE-vMgdO.d.cts +0 -25
- package/dist/ui/styles.css +0 -740
- package/dist/ui.cjs +0 -1018
- package/dist/ui.cjs.map +0 -1
- package/dist/ui.d.cts +0 -231
- package/dist/ui.d.ts +0 -231
- package/dist/ui.mjs +0 -1018
- package/dist/ui.mjs.map +0 -1
- package/dist/vite-preview-bootstrap.cjs +0 -66
- package/dist/vite-preview-bootstrap.cjs.map +0 -1
- package/dist/vite-preview-bootstrap.d.cts +0 -15
- package/dist/vite-preview-bootstrap.d.ts +0 -15
- package/dist/vite-preview-bootstrap.mjs +0 -66
- package/dist/vite-preview-bootstrap.mjs.map +0 -1
- package/dist/work-events-BlOVpK0k.d.ts +0 -547
- package/dist/work-events-D97EoqvS.d.cts +0 -547
- /package/dist/{chunk-RBIQT75L.mjs.map → chunk-GG3IX52W.mjs.map} +0 -0
package/dist/server.mjs
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
sendEvent,
|
|
3
|
-
sendWorkEvent,
|
|
4
|
-
signWebhook
|
|
5
|
-
} from "./chunk-QJMZRV7A.mjs";
|
|
6
1
|
import {
|
|
7
2
|
DEFAULT_JWKS_URL,
|
|
8
3
|
INTERNAL_MESSAGE,
|
|
9
4
|
SERVER_ERROR_CODES,
|
|
10
5
|
createTappifyHandler,
|
|
6
|
+
readRecord,
|
|
7
|
+
readText,
|
|
11
8
|
verifyTappifyToken,
|
|
12
9
|
wireError
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-C3YNVHBP.mjs";
|
|
11
|
+
import "./chunk-MNDVYPEP.mjs";
|
|
15
12
|
import {
|
|
16
13
|
TapServerError
|
|
17
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CSTYBKZC.mjs";
|
|
18
15
|
import "./chunk-J5LGTIGS.mjs";
|
|
19
16
|
|
|
20
17
|
// src/server/adapters.ts
|
|
@@ -144,13 +141,68 @@ function toNode(handler) {
|
|
|
144
141
|
});
|
|
145
142
|
};
|
|
146
143
|
}
|
|
144
|
+
|
|
145
|
+
// src/server/events.ts
|
|
146
|
+
var DEFAULT_BASE_URL = "https://api.tappify.ai";
|
|
147
|
+
function toHex(buffer) {
|
|
148
|
+
return Array.from(new Uint8Array(buffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
149
|
+
}
|
|
150
|
+
async function signWebhook(secret, body) {
|
|
151
|
+
const encoder = new TextEncoder();
|
|
152
|
+
const key = await crypto.subtle.importKey(
|
|
153
|
+
"raw",
|
|
154
|
+
encoder.encode(secret),
|
|
155
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
156
|
+
false,
|
|
157
|
+
["sign"]
|
|
158
|
+
);
|
|
159
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
160
|
+
return toHex(signature);
|
|
161
|
+
}
|
|
162
|
+
async function rejectionCode(response) {
|
|
163
|
+
try {
|
|
164
|
+
const payload = await response.json();
|
|
165
|
+
return readText(readRecord(payload, "error"), "code");
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function sendEvent(name, payload, options) {
|
|
171
|
+
const envelope = {
|
|
172
|
+
event: name,
|
|
173
|
+
source: options.extensionId,
|
|
174
|
+
occurredAt: options.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
175
|
+
dedupeKey: options.dedupeKey ?? crypto.randomUUID(),
|
|
176
|
+
payload: { ...payload }
|
|
177
|
+
};
|
|
178
|
+
if (options.build !== void 0) envelope.build = options.build;
|
|
179
|
+
const body = JSON.stringify(envelope);
|
|
180
|
+
const signature = await signWebhook(options.secret, body);
|
|
181
|
+
const url = options.endpoint ?? `${options.baseUrl ?? DEFAULT_BASE_URL}/api/v1/extensions/hooks/${options.extensionId}/${options.installId}`;
|
|
182
|
+
const send = options.fetch ?? fetch;
|
|
183
|
+
const response = await send(url, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
"content-type": "application/json",
|
|
187
|
+
"x-tappify-signature": signature
|
|
188
|
+
},
|
|
189
|
+
body
|
|
190
|
+
});
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const rejection = await rejectionCode(response);
|
|
193
|
+
throw new TapServerError(
|
|
194
|
+
SERVER_ERROR_CODES.WEBHOOK_REJECTED,
|
|
195
|
+
`Tappify answered ${String(response.status)}${rejection === null ? "" : ` (${rejection})`} for ${name}. Check the inbound signing secret on the extension's Server page, and that the event is declared under contributes.webhooks.`,
|
|
196
|
+
response.status
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
147
200
|
export {
|
|
148
201
|
DEFAULT_JWKS_URL,
|
|
149
202
|
SERVER_ERROR_CODES,
|
|
150
203
|
TapServerError,
|
|
151
204
|
createTappifyHandler,
|
|
152
205
|
sendEvent,
|
|
153
|
-
sendWorkEvent,
|
|
154
206
|
signWebhook,
|
|
155
207
|
toExpress,
|
|
156
208
|
toNode,
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/adapters.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { TapServerError } from '../client/errors';\nimport { SERVER_ERROR_CODES } from './codes';\nimport type { TappifyFetchHandler } from './handler';\nimport { INTERNAL_MESSAGE, wireError } from './wire';\n\n/**\n * The part of a Node readable stream the adapters use, so neither adapter has to\n * name a framework's own request type.\n */\nexport interface BodyStream {\n /** Each chunk of the body, in order. */\n on(event: 'data', listener: (chunk: Uint8Array) => void): unknown;\n /** The body is complete. */\n on(event: 'end', listener: () => void): unknown;\n /** The stream failed; the adapter rejects with the error. */\n on(event: 'error', listener: (error: Error) => void): unknown;\n}\n\n/** The part of an Express request `toExpress` reads. */\nexport interface ExpressLikeRequest {\n method: string;\n /** Preferred over `url`, so a router-mounted path keeps its prefix. */\n originalUrl?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n /** A parsed body from `express.json()`, or a raw string. */\n body?: unknown;\n /** Used as the scheme of the url handed to the handler. Defaults to `https`. */\n protocol?: string;\n /** Present when no parser has consumed the stream, which is then read as-is. */\n on?: BodyStream['on'];\n}\n\n/** The part of an Express response `toExpress` writes. */\nexport interface ExpressLikeResponse {\n /** Sets the status and returns the response, so the call chains into `send`. */\n status(code: number): ExpressLikeResponse;\n /** Called once per header the handler's response carries. */\n setHeader(name: string, value: string): void;\n /** Called once, with the handler's response body as text. */\n send(body: string): void;\n}\n\n/** What `toExpress` returns: an Express middleware over the three arguments. */\nexport type ExpressMiddleware = (\n request: ExpressLikeRequest,\n response: ExpressLikeResponse,\n next: (error?: unknown) => void,\n) => void;\n\n/** What `toNode` returns: a listener for `http.createServer`. */\nexport type NodeRequestListener = (\n request: IncomingMessage,\n response: ServerResponse,\n) => void;\n\nfunction toHeaders(\n raw: Record<string, string | string[] | undefined>,\n): Headers {\n const headers = new Headers();\n for (const [name, value] of Object.entries(raw)) {\n if (typeof value === 'string') headers.set(name, value);\n else if (Array.isArray(value)) headers.set(name, value.join(','));\n }\n return headers;\n}\n\nfunction originOf(\n headers: Record<string, string | string[] | undefined>,\n protocol: string,\n): string {\n const host = headers.host;\n return `${protocol}://${typeof host === 'string' ? host : 'localhost'}`;\n}\n\nfunction hasBody(method: string): boolean {\n return method !== 'GET' && method !== 'HEAD';\n}\n\nfunction readStream(stream: BodyStream): Promise<ArrayBuffer> {\n return new Promise((resolve, reject) => {\n const chunks: Uint8Array[] = [];\n let size = 0;\n\n stream.on('data', chunk => {\n chunks.push(chunk);\n size += chunk.byteLength;\n });\n\n stream.on('end', () => {\n const body = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n resolve(body.buffer);\n });\n\n stream.on('error', reject);\n });\n}\n\nfunction isReadable(\n request: ExpressLikeRequest,\n): request is ExpressLikeRequest & BodyStream {\n return typeof request.on === 'function';\n}\n\nasync function expressBody(\n request: ExpressLikeRequest,\n): Promise<BodyInit | undefined> {\n if (typeof request.body === 'string') return request.body;\n if (request.body !== undefined && request.body !== null) {\n return JSON.stringify(request.body);\n }\n\n if (!isReadable(request)) {\n throw new TapServerError(\n SERVER_ERROR_CODES.BODY_INVALID,\n 'The Express request carried no parsed body and no readable stream, so the call would have run against an empty body. Mount express.json() before the Tappify middleware, or mount the middleware before any parser that consumes the stream.',\n 400,\n );\n }\n\n const bytes = await readStream(request);\n return bytes.byteLength > 0 ? bytes : undefined;\n}\n\nasync function write(\n response: ExpressLikeResponse,\n webResponse: Response,\n): Promise<void> {\n webResponse.headers.forEach((value, name) => {\n response.setHeader(name, value);\n });\n response.status(webResponse.status).send(await webResponse.text());\n}\n\n/**\n * Wraps the handler as Express middleware, for a server that speaks Express\n * rather than `fetch`.\n *\n * @remarks\n * It builds the url from `originalUrl` and the `host` header, forwards every\n * header, and takes the body from `request.body` when a parser has already read\n * it or from the stream when none has. A request with neither — a parser that\n * consumed the stream without leaving a body — fails with a `TapServerError`\n * carrying `TAP_BODY_INVALID` rather than calling your handler against an empty\n * body, so mount `express.json()` before this middleware or mount this one first.\n * A `TapServerError` is written as the handler's own JSON error; anything else\n * goes to `next`, so your own error middleware sees it.\n *\n * @example\n * ```ts\n * import { createTappifyHandler, toExpress } from '@tappify/extension-sdk/server';\n *\n * const middleware = toExpress(\n * createTappifyHandler({ extensionId: 'starter', health: () => ({ ok: true }) }),\n * );\n * ```\n */\nexport function toExpress(handler: TappifyFetchHandler): ExpressMiddleware {\n return (request, response, next) => {\n void (async () => {\n const url = new URL(\n request.originalUrl ?? request.url ?? '/',\n originOf(request.headers, request.protocol ?? 'https'),\n );\n\n const init: RequestInit = {\n method: request.method,\n headers: toHeaders(request.headers),\n };\n\n if (hasBody(request.method)) {\n init.body = await expressBody(request);\n }\n\n await write(response, await handler(new Request(url, init)));\n })().catch((error: unknown) => {\n if (TapServerError.is(error)) {\n void write(\n response,\n wireError(error.code, error.message, error.status ?? 400),\n );\n return;\n }\n next(error);\n });\n };\n}\n\n/**\n * Wraps the handler as a listener for Node's own `http.createServer`, with no\n * framework in between.\n *\n * @remarks\n * It builds the url from `request.url` and the `host` header over `http`, forwards\n * every header, and reads the body straight off the stream, so nothing may have\n * consumed it first. It answers the whole response itself: a `TapServerError`\n * becomes its own code and status, and anything else becomes 500\n * `TAP_INTERNAL_ERROR`. Put it behind a TLS-terminating proxy in production; the\n * scheme it builds is only what the handler sees in `request.url`.\n *\n * @example\n * ```ts\n * import { createTappifyHandler, toNode } from '@tappify/extension-sdk/server';\n * import { createServer } from 'node:http';\n *\n * const listener = toNode(\n * createTappifyHandler({ extensionId: 'starter', health: () => ({ ok: true }) }),\n * );\n * const server = createServer(listener);\n * ```\n */\nexport function toNode(handler: TappifyFetchHandler): NodeRequestListener {\n return (request, response) => {\n void (async () => {\n const method = request.method ?? 'GET';\n const url = new URL(\n request.url ?? '/',\n originOf(request.headers, 'http'),\n );\n\n const init: RequestInit = {\n method,\n headers: toHeaders(request.headers),\n };\n\n if (hasBody(method)) {\n const bytes = await readStream(request);\n if (bytes.byteLength > 0) init.body = bytes;\n }\n\n const webResponse = await handler(new Request(url, init));\n const headers: Record<string, string> = {};\n webResponse.headers.forEach((value, name) => {\n headers[name] = value;\n });\n\n response.writeHead(webResponse.status, headers);\n response.end(await webResponse.text());\n })().catch((error: unknown) => {\n const wire = TapServerError.is(error)\n ? {\n code: error.code,\n message: error.message,\n status: error.status ?? 400,\n }\n : {\n code: SERVER_ERROR_CODES.INTERNAL_ERROR,\n message: INTERNAL_MESSAGE,\n status: 500,\n };\n\n response.writeHead(wire.status, { 'content-type': 'application/json' });\n response.end(\n JSON.stringify({ error: { code: wire.code, message: wire.message } }),\n );\n });\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server/adapters.ts","../src/server/events.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { TapServerError } from '../client/errors';\nimport { SERVER_ERROR_CODES } from './codes';\nimport type { TappifyFetchHandler } from './handler';\nimport { INTERNAL_MESSAGE, wireError } from './wire';\n\n/**\n * The part of a Node readable stream the adapters use, so neither adapter has to\n * name a framework's own request type.\n */\nexport interface BodyStream {\n /** Each chunk of the body, in order. */\n on(event: 'data', listener: (chunk: Uint8Array) => void): unknown;\n /** The body is complete. */\n on(event: 'end', listener: () => void): unknown;\n /** The stream failed; the adapter rejects with the error. */\n on(event: 'error', listener: (error: Error) => void): unknown;\n}\n\n/** The part of an Express request `toExpress` reads. */\nexport interface ExpressLikeRequest {\n method: string;\n /** Preferred over `url`, so a router-mounted path keeps its prefix. */\n originalUrl?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n /** A parsed body from `express.json()`, or a raw string. */\n body?: unknown;\n /** Used as the scheme of the url handed to the handler. Defaults to `https`. */\n protocol?: string;\n /** Present when no parser has consumed the stream, which is then read as-is. */\n on?: BodyStream['on'];\n}\n\n/** The part of an Express response `toExpress` writes. */\nexport interface ExpressLikeResponse {\n /** Sets the status and returns the response, so the call chains into `send`. */\n status(code: number): ExpressLikeResponse;\n /** Called once per header the handler's response carries. */\n setHeader(name: string, value: string): void;\n /** Called once, with the handler's response body as text. */\n send(body: string): void;\n}\n\n/** What `toExpress` returns: an Express middleware over the three arguments. */\nexport type ExpressMiddleware = (\n request: ExpressLikeRequest,\n response: ExpressLikeResponse,\n next: (error?: unknown) => void,\n) => void;\n\n/** What `toNode` returns: a listener for `http.createServer`. */\nexport type NodeRequestListener = (\n request: IncomingMessage,\n response: ServerResponse,\n) => void;\n\nfunction toHeaders(\n raw: Record<string, string | string[] | undefined>,\n): Headers {\n const headers = new Headers();\n for (const [name, value] of Object.entries(raw)) {\n if (typeof value === 'string') headers.set(name, value);\n else if (Array.isArray(value)) headers.set(name, value.join(','));\n }\n return headers;\n}\n\nfunction originOf(\n headers: Record<string, string | string[] | undefined>,\n protocol: string,\n): string {\n const host = headers.host;\n return `${protocol}://${typeof host === 'string' ? host : 'localhost'}`;\n}\n\nfunction hasBody(method: string): boolean {\n return method !== 'GET' && method !== 'HEAD';\n}\n\nfunction readStream(stream: BodyStream): Promise<ArrayBuffer> {\n return new Promise((resolve, reject) => {\n const chunks: Uint8Array[] = [];\n let size = 0;\n\n stream.on('data', chunk => {\n chunks.push(chunk);\n size += chunk.byteLength;\n });\n\n stream.on('end', () => {\n const body = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n resolve(body.buffer);\n });\n\n stream.on('error', reject);\n });\n}\n\nfunction isReadable(\n request: ExpressLikeRequest,\n): request is ExpressLikeRequest & BodyStream {\n return typeof request.on === 'function';\n}\n\nasync function expressBody(\n request: ExpressLikeRequest,\n): Promise<BodyInit | undefined> {\n if (typeof request.body === 'string') return request.body;\n if (request.body !== undefined && request.body !== null) {\n return JSON.stringify(request.body);\n }\n\n if (!isReadable(request)) {\n throw new TapServerError(\n SERVER_ERROR_CODES.BODY_INVALID,\n 'The Express request carried no parsed body and no readable stream, so the call would have run against an empty body. Mount express.json() before the Tappify middleware, or mount the middleware before any parser that consumes the stream.',\n 400,\n );\n }\n\n const bytes = await readStream(request);\n return bytes.byteLength > 0 ? bytes : undefined;\n}\n\nasync function write(\n response: ExpressLikeResponse,\n webResponse: Response,\n): Promise<void> {\n webResponse.headers.forEach((value, name) => {\n response.setHeader(name, value);\n });\n response.status(webResponse.status).send(await webResponse.text());\n}\n\n/**\n * Wraps the handler as Express middleware, for a server that speaks Express\n * rather than `fetch`.\n *\n * @remarks\n * It builds the url from `originalUrl` and the `host` header, forwards every\n * header, and takes the body from `request.body` when a parser has already read\n * it or from the stream when none has. A request with neither — a parser that\n * consumed the stream without leaving a body — fails with a `TapServerError`\n * carrying `TAP_BODY_INVALID` rather than calling your handler against an empty\n * body, so mount `express.json()` before this middleware or mount this one first.\n * A `TapServerError` is written as the handler's own JSON error; anything else\n * goes to `next`, so your own error middleware sees it.\n *\n * @example\n * ```ts\n * import { createTappifyHandler, toExpress } from '@tappify/extension-sdk/server';\n *\n * const middleware = toExpress(\n * createTappifyHandler({ extensionId: 'starter', health: () => ({ ok: true }) }),\n * );\n * ```\n */\nexport function toExpress(handler: TappifyFetchHandler): ExpressMiddleware {\n return (request, response, next) => {\n void (async () => {\n const url = new URL(\n request.originalUrl ?? request.url ?? '/',\n originOf(request.headers, request.protocol ?? 'https'),\n );\n\n const init: RequestInit = {\n method: request.method,\n headers: toHeaders(request.headers),\n };\n\n if (hasBody(request.method)) {\n init.body = await expressBody(request);\n }\n\n await write(response, await handler(new Request(url, init)));\n })().catch((error: unknown) => {\n if (TapServerError.is(error)) {\n void write(\n response,\n wireError(error.code, error.message, error.status ?? 400),\n );\n return;\n }\n next(error);\n });\n };\n}\n\n/**\n * Wraps the handler as a listener for Node's own `http.createServer`, with no\n * framework in between.\n *\n * @remarks\n * It builds the url from `request.url` and the `host` header over `http`, forwards\n * every header, and reads the body straight off the stream, so nothing may have\n * consumed it first. It answers the whole response itself: a `TapServerError`\n * becomes its own code and status, and anything else becomes 500\n * `TAP_INTERNAL_ERROR`. Put it behind a TLS-terminating proxy in production; the\n * scheme it builds is only what the handler sees in `request.url`.\n *\n * @example\n * ```ts\n * import { createTappifyHandler, toNode } from '@tappify/extension-sdk/server';\n * import { createServer } from 'node:http';\n *\n * const listener = toNode(\n * createTappifyHandler({ extensionId: 'starter', health: () => ({ ok: true }) }),\n * );\n * const server = createServer(listener);\n * ```\n */\nexport function toNode(handler: TappifyFetchHandler): NodeRequestListener {\n return (request, response) => {\n void (async () => {\n const method = request.method ?? 'GET';\n const url = new URL(\n request.url ?? '/',\n originOf(request.headers, 'http'),\n );\n\n const init: RequestInit = {\n method,\n headers: toHeaders(request.headers),\n };\n\n if (hasBody(method)) {\n const bytes = await readStream(request);\n if (bytes.byteLength > 0) init.body = bytes;\n }\n\n const webResponse = await handler(new Request(url, init));\n const headers: Record<string, string> = {};\n webResponse.headers.forEach((value, name) => {\n headers[name] = value;\n });\n\n response.writeHead(webResponse.status, headers);\n response.end(await webResponse.text());\n })().catch((error: unknown) => {\n const wire = TapServerError.is(error)\n ? {\n code: error.code,\n message: error.message,\n status: error.status ?? 400,\n }\n : {\n code: SERVER_ERROR_CODES.INTERNAL_ERROR,\n message: INTERNAL_MESSAGE,\n status: 500,\n };\n\n response.writeHead(wire.status, { 'content-type': 'application/json' });\n response.end(\n JSON.stringify({ error: { code: wire.code, message: wire.message } }),\n );\n });\n };\n}\n","import { TapServerError } from '../client/errors';\nimport type { WebhookName, WebhookPayload } from '../client/types';\nimport { SERVER_ERROR_CODES } from './codes';\nimport { readRecord, readText } from './wire';\n\n/** The body `sendEvent` posts to Tappify, and the body the signature covers. */\nexport interface WebhookEnvelope {\n /** The declared webhook name. */\n event: string;\n /** The extension id the event came from. */\n source: string;\n /** When the event happened, as an ISO timestamp. */\n occurredAt: string;\n /** The build the event belongs to, when it belongs to one. */\n build?: string;\n /** The key Tappify deduplicates on, so a retry is not counted twice. */\n dedupeKey: string;\n payload: Record<string, unknown>;\n}\n\n/** The third argument of `sendEvent`. */\nexport interface SendEventOptions {\n /** Which install the event belongs to. */\n installId: string;\n extensionId: string;\n /** The inbound signing secret from the extension's Server page. */\n secret: string;\n /** A full url to post to, which replaces `baseUrl` and the built path. */\n endpoint?: string;\n /** The Tappify origin to build the path under. Defaults to production. */\n baseUrl?: string;\n /** Defaults to a fresh UUID, so give your own to make a retry idempotent. */\n dedupeKey?: string;\n build?: string;\n /** Defaults to now, in ISO form. */\n occurredAt?: string;\n /** A `fetch` of your own, for a test or a proxy. Defaults to the global one. */\n fetch?: typeof fetch;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.tappify.ai';\n\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map(byte => byte.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Returns the hex HMAC-SHA-256 of a raw body under a shared secret.\n *\n * @remarks\n * This is the value `sendEvent` sends as `X-Tappify-Signature`, computed over the\n * exact bytes of the body it posts. Call it directly only when you build the\n * request yourself, and sign the serialised string you send rather than\n * re-serialising the object, because a different key order is a different\n * signature. Uses Web Crypto, so it runs on Node, Cloudflare Workers and Vercel\n * alike.\n *\n * @example\n * ```ts\n * import { signWebhook } from '@tappify/extension-sdk/server';\n *\n * async function signed(body: string): Promise<Record<string, string>> {\n * return { 'x-tappify-signature': await signWebhook('whsec_test', body) };\n * }\n * ```\n */\nexport async function signWebhook(\n secret: string,\n body: string,\n): Promise<string> {\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(body));\n return toHex(signature);\n}\n\nasync function rejectionCode(response: Response): Promise<string | null> {\n try {\n const payload: unknown = await response.json();\n return readText(readRecord(payload, 'error'), 'code');\n } catch {\n return null;\n }\n}\n\n/**\n * Posts one of your declared webhook events to Tappify, signed with the install's\n * inbound secret.\n *\n * @remarks\n * The name and the payload are narrowed by the `TapWebhookMap` augmentation\n * `tappify extension types` writes. It builds the envelope, signs the serialised\n * body and posts it to the install's hook path under `baseUrl`, or to `endpoint`\n * when you give one. A response Tappify did not accept rejects with a\n * `TapServerError` whose `code` is always `TAP_WEBHOOK_REJECTED`, carrying the HTTP\n * status, and with Tappify's own code in the message text rather than in `code`;\n * the two usual causes are the wrong signing\n * secret and an event the manifest does not declare. Pass your own `dedupeKey` to\n * make a retry idempotent, because the default is a fresh UUID.\n *\n * @example\n * ```ts\n * import { sendEvent } from '@tappify/extension-sdk/server';\n *\n * async function reportAnomaly(installId: string, why: string): Promise<void> {\n * await sendEvent('anomaly.detected', { why }, {\n * installId,\n * extensionId: 'starter',\n * secret: process.env.TAPPIFY_INBOUND_SECRET ?? '',\n * dedupeKey: `anomaly-${installId}-${why}`,\n * });\n * }\n * ```\n */\nexport async function sendEvent<E extends WebhookName>(\n name: E,\n payload: WebhookPayload<E>,\n options: SendEventOptions,\n): Promise<void> {\n const envelope: WebhookEnvelope = {\n event: name,\n source: options.extensionId,\n occurredAt: options.occurredAt ?? new Date().toISOString(),\n dedupeKey: options.dedupeKey ?? crypto.randomUUID(),\n payload: { ...payload },\n };\n\n if (options.build !== undefined) envelope.build = options.build;\n\n const body = JSON.stringify(envelope);\n const signature = await signWebhook(options.secret, body);\n const url =\n options.endpoint ??\n `${options.baseUrl ?? DEFAULT_BASE_URL}/api/v1/extensions/hooks/${options.extensionId}/${options.installId}`;\n\n const send = options.fetch ?? fetch;\n const response = await send(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-tappify-signature': signature,\n },\n body,\n });\n\n if (!response.ok) {\n const rejection = await rejectionCode(response);\n throw new TapServerError(\n SERVER_ERROR_CODES.WEBHOOK_REJECTED,\n `Tappify answered ${String(response.status)}${rejection === null ? '' : ` (${rejection})`} for ${name}. Check the inbound signing secret on the extension's Server page, and that the event is declared under contributes.webhooks.`,\n response.status,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
.tap-root {
|
|
2
|
+
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
|
|
3
|
+
color: var(--fg-default, #121212);
|
|
4
|
+
container-type: inline-size;
|
|
5
|
+
container-name: tap-mount;
|
|
6
|
+
line-height: 1.45;
|
|
7
|
+
font-size: 13px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.tap-root *,
|
|
11
|
+
.tap-root *::before,
|
|
12
|
+
.tap-root *::after {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.tap-stack {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--tap-space, 8px);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tap-row {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: var(--tap-space, 8px);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.tap-btn {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
gap: 6px;
|
|
33
|
+
border: 1px solid transparent;
|
|
34
|
+
border-radius: var(--tap-radius, 6px);
|
|
35
|
+
font: inherit;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
padding: 6px 12px;
|
|
39
|
+
transition:
|
|
40
|
+
background-color 120ms ease,
|
|
41
|
+
border-color 120ms ease;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tap-btn:disabled {
|
|
45
|
+
opacity: 0.55;
|
|
46
|
+
cursor: not-allowed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tap-btn:focus-visible {
|
|
50
|
+
outline: 2px solid var(--tap-accent, #4f46e5);
|
|
51
|
+
outline-offset: 2px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tap-btn--sm {
|
|
55
|
+
padding: 3px 8px;
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.tap-btn--primary {
|
|
60
|
+
background: var(--tap-accent, #4f46e5);
|
|
61
|
+
color: var(--tap-accent-fg, #ffffff);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.tap-btn--secondary {
|
|
65
|
+
background: var(--bg-raised, #ffffff);
|
|
66
|
+
color: var(--fg-default, #121212);
|
|
67
|
+
border-color: var(--divider, rgba(18, 18, 18, 0.12));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tap-btn--ghost {
|
|
71
|
+
background: transparent;
|
|
72
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.tap-btn--danger {
|
|
76
|
+
background: var(--tap-danger, #b42318);
|
|
77
|
+
color: var(--tap-accent-fg, #ffffff);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.tap-card {
|
|
81
|
+
background: var(--bg-raised, #ffffff);
|
|
82
|
+
border: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
83
|
+
border-radius: var(--tap-radius, 6px);
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.tap-card__head {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: space-between;
|
|
93
|
+
gap: var(--tap-space, 8px);
|
|
94
|
+
padding: 10px 12px;
|
|
95
|
+
border-bottom: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.tap-card__body {
|
|
100
|
+
padding: 12px;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.tap-card__foot {
|
|
105
|
+
padding: 8px 12px;
|
|
106
|
+
border-top: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
107
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.tap-page-header {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: flex-start;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
gap: var(--tap-space, 8px);
|
|
115
|
+
padding-bottom: 12px;
|
|
116
|
+
border-bottom: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
117
|
+
margin-bottom: 16px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.tap-page-header__title {
|
|
121
|
+
margin: 0;
|
|
122
|
+
font-size: 18px;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.tap-page-header__description {
|
|
127
|
+
margin: 4px 0 0;
|
|
128
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.tap-skeleton {
|
|
132
|
+
display: block;
|
|
133
|
+
height: 12px;
|
|
134
|
+
border-radius: 4px;
|
|
135
|
+
background: var(--bg-subtle, rgba(18, 18, 18, 0.06));
|
|
136
|
+
animation: tap-pulse 1.4s ease-in-out infinite;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.tap-skeleton + .tap-skeleton {
|
|
140
|
+
margin-top: 6px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@keyframes tap-pulse {
|
|
144
|
+
0%,
|
|
145
|
+
100% {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
}
|
|
148
|
+
50% {
|
|
149
|
+
opacity: 0.5;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (prefers-reduced-motion: reduce) {
|
|
154
|
+
.tap-skeleton {
|
|
155
|
+
animation: none;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tap-state {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 6px;
|
|
164
|
+
text-align: center;
|
|
165
|
+
padding: 24px 16px;
|
|
166
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.tap-state__title {
|
|
170
|
+
color: var(--fg-default, #121212);
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.tap-state--error .tap-state__title {
|
|
175
|
+
color: var(--tap-danger, #b42318);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.tap-state__code {
|
|
179
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
|
|
180
|
+
font-size: 11px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.tap-stat {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
gap: 2px;
|
|
187
|
+
min-width: 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.tap-stat__label {
|
|
191
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
192
|
+
font-size: 11px;
|
|
193
|
+
text-transform: uppercase;
|
|
194
|
+
letter-spacing: 0.04em;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.tap-stat__value {
|
|
198
|
+
font-size: 22px;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
font-variant-numeric: tabular-nums;
|
|
201
|
+
line-height: 1.1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.tap-stat__delta--up {
|
|
205
|
+
color: var(--tap-accent, #4f46e5);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.tap-stat__delta--down {
|
|
209
|
+
color: var(--tap-danger, #b42318);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.tap-field {
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-direction: column;
|
|
215
|
+
gap: 4px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.tap-field__label {
|
|
219
|
+
font-weight: 500;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.tap-field__hint,
|
|
223
|
+
.tap-field__error {
|
|
224
|
+
font-size: 11px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.tap-field__hint {
|
|
228
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.tap-field__error {
|
|
232
|
+
color: var(--tap-danger, #b42318);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.tap-control {
|
|
236
|
+
font: inherit;
|
|
237
|
+
color: inherit;
|
|
238
|
+
background: var(--bg-base, #ffffff);
|
|
239
|
+
border: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
240
|
+
border-radius: var(--tap-radius, 6px);
|
|
241
|
+
padding: 6px 8px;
|
|
242
|
+
width: 100%;
|
|
243
|
+
min-width: 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.tap-control:focus-visible {
|
|
247
|
+
outline: 2px solid var(--tap-accent, #4f46e5);
|
|
248
|
+
outline-offset: 1px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.tap-control--invalid {
|
|
252
|
+
border-color: var(--tap-danger, #b42318);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.tap-control--checkbox {
|
|
256
|
+
width: auto;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
textarea.tap-control {
|
|
260
|
+
min-height: 76px;
|
|
261
|
+
resize: vertical;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.tap-table-wrap {
|
|
265
|
+
overflow-x: auto;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.tap-table {
|
|
269
|
+
width: 100%;
|
|
270
|
+
border-collapse: collapse;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.tap-table th,
|
|
274
|
+
.tap-table td {
|
|
275
|
+
text-align: start;
|
|
276
|
+
padding: 6px 8px;
|
|
277
|
+
border-bottom: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
278
|
+
white-space: nowrap;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.tap-table th {
|
|
282
|
+
color: var(--fg-muted, rgba(18, 18, 18, 0.6));
|
|
283
|
+
font-size: 11px;
|
|
284
|
+
font-weight: 500;
|
|
285
|
+
text-transform: uppercase;
|
|
286
|
+
letter-spacing: 0.04em;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.tap-table__end {
|
|
290
|
+
text-align: end;
|
|
291
|
+
font-variant-numeric: tabular-nums;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.tap-table tr[data-clickable='true'] {
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.tap-table tr[data-clickable='true']:hover td {
|
|
299
|
+
background: var(--bg-subtle, rgba(18, 18, 18, 0.04));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.tap-dialog-scrim {
|
|
303
|
+
position: fixed;
|
|
304
|
+
inset: 0;
|
|
305
|
+
background: var(--tap-scrim, rgba(18, 18, 18, 0.7));
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
justify-content: center;
|
|
309
|
+
padding: 24px;
|
|
310
|
+
z-index: 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.tap-dialog {
|
|
314
|
+
background: var(--bg-raised, #ffffff);
|
|
315
|
+
color: var(--fg-default, #121212);
|
|
316
|
+
border-radius: var(--tap-radius, 6px);
|
|
317
|
+
border: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
318
|
+
width: min(520px, 100%);
|
|
319
|
+
max-height: 100%;
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-direction: column;
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.tap-dialog__head {
|
|
326
|
+
padding: 12px 16px;
|
|
327
|
+
border-bottom: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.tap-dialog__body {
|
|
332
|
+
padding: 16px;
|
|
333
|
+
overflow: auto;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.tap-dialog__foot {
|
|
337
|
+
padding: 12px 16px;
|
|
338
|
+
border-top: 1px solid var(--divider, rgba(18, 18, 18, 0.12));
|
|
339
|
+
display: flex;
|
|
340
|
+
justify-content: flex-end;
|
|
341
|
+
gap: 8px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.tap-form {
|
|
345
|
+
display: flex;
|
|
346
|
+
flex-direction: column;
|
|
347
|
+
gap: 12px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.tap-form__actions {
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: flex-end;
|
|
353
|
+
gap: 8px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@container tap-mount (max-width: 360px) {
|
|
357
|
+
.tap-page-header,
|
|
358
|
+
.tap-form__actions,
|
|
359
|
+
.tap-dialog__foot {
|
|
360
|
+
flex-direction: column;
|
|
361
|
+
align-items: stretch;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.tap-stat__value {
|
|
365
|
+
font-size: 18px;
|
|
366
|
+
}
|
|
367
|
+
}
|
package/dist/testing/index.cjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var _chunkO357OJXLcjs = require('../chunk-O357OJXL.cjs');
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
|
|
@@ -15,27 +14,25 @@ var _chunkO357OJXLcjs = require('../chunk-O357OJXL.cjs');
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
var _chunkNFUPPCX4cjs = require('../chunk-NFUPPCX4.cjs');
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
var _chunk5AAPD5WVcjs = require('../chunk-5AAPD5WV.cjs');
|
|
21
22
|
|
|
22
|
-
var _chunkBPCGXXMDcjs = require('../chunk-BPCGXXMD.cjs');
|
|
23
23
|
|
|
24
|
+
var _chunkJMUK3K47cjs = require('../chunk-JMUK3K47.cjs');
|
|
25
|
+
require('../chunk-OPPYEA7F.cjs');
|
|
26
|
+
require('../chunk-G7XZSN2Q.cjs');
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
var
|
|
29
|
+
var _chunkPYJMJF44cjs = require('../chunk-PYJMJF44.cjs');
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
var _chunkVV7ELNRWcjs = require('../chunk-VV7ELNRW.cjs');
|
|
30
|
-
require('../chunk-PRCZMOLD.cjs');
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
var
|
|
34
|
-
require('../chunk-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
var _chunk7YKH4LZNcjs = require('../chunk-7YKH4LZN.cjs');
|
|
38
|
-
require('../chunk-6HETNZJY.cjs');
|
|
34
|
+
var _chunk3YWVYA42cjs = require('../chunk-3YWVYA42.cjs');
|
|
35
|
+
require('../chunk-2DBSVKAU.cjs');
|
|
39
36
|
require('../chunk-Q7SFCCGT.cjs');
|
|
40
37
|
|
|
41
38
|
// src/testing/render.tsx
|
|
@@ -45,18 +42,18 @@ var _reactquery = require('@tanstack/react-query');
|
|
|
45
42
|
var _react = require('@testing-library/react');
|
|
46
43
|
var _jsxruntime = require('react/jsx-runtime');
|
|
47
44
|
function renderWithTap(ui, options = {}) {
|
|
48
|
-
const mock = _nullishCoalesce(options.mock, () => (
|
|
45
|
+
const mock = _nullishCoalesce(options.mock, () => ( _chunkNFUPPCX4cjs.createTapMock.call(void 0, options)));
|
|
49
46
|
const queryClient = _nullishCoalesce(options.queryClient, () => ( new (0, _reactquery.QueryClient)({
|
|
50
47
|
defaultOptions: { queries: { retry: false, gcTime: 0 } }
|
|
51
48
|
})));
|
|
52
49
|
const installId = mock.tap.extension.installId;
|
|
53
50
|
mock.tap.invalidate = (name) => {
|
|
54
51
|
void queryClient.invalidateQueries({
|
|
55
|
-
queryKey: name === void 0 ?
|
|
52
|
+
queryKey: name === void 0 ? _chunk5AAPD5WVcjs.installPrefix.call(void 0, installId) : _chunk5AAPD5WVcjs.procedurePrefix.call(void 0, installId, name)
|
|
56
53
|
});
|
|
57
54
|
};
|
|
58
55
|
function Wrapper({ children }) {
|
|
59
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactquery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
56
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactquery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJMUK3K47cjs.TapHostProvider, { tap: mock.tap, children }) });
|
|
60
57
|
}
|
|
61
58
|
const result = _react.render.call(void 0, ui, { wrapper: Wrapper, container: options.container });
|
|
62
59
|
result.baseElement.append(mock.portal);
|
|
@@ -124,67 +121,11 @@ function validateToolResponse(value, returns) {
|
|
|
124
121
|
`A "${returns}" tool returns an object the host draws as a card, not ${typeof value}.`
|
|
125
122
|
];
|
|
126
123
|
}
|
|
127
|
-
return
|
|
124
|
+
return _chunkPYJMJF44cjs.CARD_KEYS[returns].filter((key) => !(key in body)).map(
|
|
128
125
|
(key) => `A "${returns}" tool response needs a "${key}" key; the host draws the card from it.`
|
|
129
126
|
);
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
// src/testing/work-events.ts
|
|
133
|
-
function workCommentCreatedFixture(overrides = {}) {
|
|
134
|
-
return {
|
|
135
|
-
type: "comment.created",
|
|
136
|
-
externalItemId: "item_test",
|
|
137
|
-
providerEventId: "event_comment_test",
|
|
138
|
-
occurredAt: "2026-10-15T12:00:00.000Z",
|
|
139
|
-
dedupeKey: "item_test:event_comment_test",
|
|
140
|
-
comment: {
|
|
141
|
-
externalCommentId: "comment_test",
|
|
142
|
-
body: "A provider comment.",
|
|
143
|
-
author: { id: "author_test", displayName: "Sam" },
|
|
144
|
-
createdAt: "2026-10-15T12:00:00.000Z"
|
|
145
|
-
},
|
|
146
|
-
...overrides
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
function workStatusChangedFixture(overrides = {}) {
|
|
150
|
-
return {
|
|
151
|
-
type: "status.changed",
|
|
152
|
-
externalItemId: "item_test",
|
|
153
|
-
providerEventId: "event_status_test",
|
|
154
|
-
occurredAt: "2026-10-15T12:00:00.000Z",
|
|
155
|
-
dedupeKey: "item_test:event_status_test",
|
|
156
|
-
status: { id: "resolved_test", label: "Resolved", stage: "done" },
|
|
157
|
-
...overrides
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
function workItemDeletedFixture(overrides = {}) {
|
|
161
|
-
return {
|
|
162
|
-
type: "item.deleted",
|
|
163
|
-
externalItemId: "item_test",
|
|
164
|
-
providerEventId: "event_deleted_test",
|
|
165
|
-
occurredAt: "2026-10-15T12:00:00.000Z",
|
|
166
|
-
dedupeKey: "item_test:event_deleted_test",
|
|
167
|
-
...overrides
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
async function signedWorkEventRequest(event, options) {
|
|
171
|
-
let request;
|
|
172
|
-
await _chunkEB5XWQAUcjs.sendWorkEvent.call(void 0, event, {
|
|
173
|
-
...options,
|
|
174
|
-
fetch: (input, init) => {
|
|
175
|
-
request = new Request(input, init);
|
|
176
|
-
return Promise.resolve(new Response(null, { status: 202 }));
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
if (!request)
|
|
180
|
-
throw new Error("The work event sender did not produce a request.");
|
|
181
|
-
return request;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
129
|
|
|
189
130
|
|
|
190
131
|
|
|
@@ -206,5 +147,5 @@ async function signedWorkEventRequest(event, options) {
|
|
|
206
147
|
|
|
207
148
|
|
|
208
149
|
|
|
209
|
-
exports.answerFixtureQuery =
|
|
150
|
+
exports.answerFixtureQuery = _chunkNFUPPCX4cjs.answerFixtureQuery; exports.createTapMock = _chunkNFUPPCX4cjs.createTapMock; exports.createTestClient = _chunkNFUPPCX4cjs.createTestClient; exports.fixtureApps = _chunkNFUPPCX4cjs.fixtureApps; exports.fixtureCrashes = _chunkNFUPPCX4cjs.fixtureCrashes; exports.fixtureKeywords = _chunkNFUPPCX4cjs.fixtureKeywords; exports.fixtureListings = _chunkNFUPPCX4cjs.fixtureListings; exports.fixtureProject = _chunkNFUPPCX4cjs.fixtureProject; exports.fixtureReleases = _chunkNFUPPCX4cjs.fixtureReleases; exports.fixtureRevenue = _chunkNFUPPCX4cjs.fixtureRevenue; exports.fixtureReviews = _chunkNFUPPCX4cjs.fixtureReviews; exports.fixtureSeries = _chunkNFUPPCX4cjs.fixtureSeries; exports.fixtures = _chunkNFUPPCX4cjs.fixtures; exports.installHostTheme = _chunk3YWVYA42cjs.installHostTheme; exports.registerTapMatchers = _chunk3YWVYA42cjs.registerTapMatchers; exports.renderWithTap = renderWithTap; exports.signTestToken = _chunkNFUPPCX4cjs.signTestToken; exports.tapMatchers = _chunk3YWVYA42cjs.tapMatchers; exports.testJwks = _chunkNFUPPCX4cjs.testJwks; exports.validateMetricsResponse = validateMetricsResponse; exports.validateToolResponse = validateToolResponse;
|
|
210
151
|
//# sourceMappingURL=index.cjs.map
|