@gathertown/webhook-object-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/client.d.ts +7 -1
- package/dist/src/client.js +3 -1
- package/dist/src/client.js.map +2 -2
- package/package.json +23 -2
package/dist/src/client.d.ts
CHANGED
|
@@ -48,6 +48,12 @@ export type PingResult = {
|
|
|
48
48
|
spaceId: string;
|
|
49
49
|
preset: WebhookObjectPreset | null;
|
|
50
50
|
capabilities: Record<string, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Current-orientation colors `variant.set` will actually resolve. A color not in this list is
|
|
53
|
+
* silently ignored. Optional: a server predating this field omits it, so an SDK upgraded ahead of
|
|
54
|
+
* the server still pings successfully (`colors` is then `undefined`).
|
|
55
|
+
*/
|
|
56
|
+
colors?: string[];
|
|
51
57
|
};
|
|
52
58
|
/** `set_state` → `setState`: fluent method names are idiomatic camelCase versions of wire names. */
|
|
53
59
|
type SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}` ? `${Head}${Capitalize<SnakeToCamel<Tail>>}` : S;
|
|
@@ -75,7 +81,7 @@ export type WebhookObjectClient<P extends WebhookObjectPreset | undefined = unde
|
|
|
75
81
|
* for empty-payload events (e.g. `send("counter.reset")`).
|
|
76
82
|
*/
|
|
77
83
|
send<T extends AllowedEventType<P>>(type: T, ...args: DataArgs<T>): Promise<SendResult>;
|
|
78
|
-
/** Verify the URL + secret and fetch the object's declared
|
|
84
|
+
/** Verify the URL + secret and fetch the object's declared capabilities and available colors. */
|
|
79
85
|
ping(): Promise<PingResult>;
|
|
80
86
|
} & CapabilityNamespaces<AllowedEventType<P>>;
|
|
81
87
|
/**
|
package/dist/src/client.js
CHANGED
|
@@ -45,7 +45,9 @@ const assertValidUrl = (url) => {
|
|
|
45
45
|
const wireMethodName = (method) => method.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
|
|
46
46
|
const PROBED_PROPS = /* @__PURE__ */ new Set(["then", "toJSON"]);
|
|
47
47
|
const isSendResult = (body) => body.status === "dispatched" || body.status === "space_idle";
|
|
48
|
-
const isPingResult = (body) => body.status === "pong" && typeof body.objectId === "string" && typeof body.spaceId === "string" && (body.preset === null || typeof body.preset === "string") && typeof body.capabilities === "object" && body.capabilities !== null
|
|
48
|
+
const isPingResult = (body) => body.status === "pong" && typeof body.objectId === "string" && typeof body.spaceId === "string" && (body.preset === null || typeof body.preset === "string") && typeof body.capabilities === "object" && body.capabilities !== null && // `colors` is optional so the SDK stays compatible with a server that predates the field: an
|
|
49
|
+
// older pong omits it, and rejecting that would break ping() when the SDK is upgraded first.
|
|
50
|
+
(body.colors === void 0 || Array.isArray(body.colors) && body.colors.every((color) => typeof color === "string"));
|
|
49
51
|
const serializeEnvelope = (type, data) => {
|
|
50
52
|
const body = JSON.stringify({ type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data });
|
|
51
53
|
const byteLength = new TextEncoder().encode(body).length;
|
package/dist/src/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/client.ts"],
|
|
4
|
-
"sourcesContent": ["import { Webhook } from \"standardwebhooks\"\n\nimport type {\n DataFor,\n EventTypeForPreset,\n ReservedEventType,\n WebhookEventType,\n WebhookObjectPreset,\n} from \"@gathertown/webhook-object-types\"\nimport { DEFAULT_MAX_ATTEMPTS, deliver, MAX_BODY_BYTES } from \"./deliver\"\nimport { WebhookObjectError } from \"./errors\"\nimport { revealSecret, WebhookObjectSecret } from \"./secret\"\n\nexport type WebhookObjectClientConfig = {\n /** The object's full webhook URL (copied from the object's \u22EE menu in Gather). */\n url: string\n /**\n * The object's signing secret (per-object; never shared between objects). Either the raw\n * `whsec_\u2026` string, or (recommended) an opaque handle from `secretFromEnv()` /\n * `unsafeSecretLiteral()`, which redacts itself if the config is ever logged or serialized.\n */\n secret: WebhookObjectSecret | string\n /**\n * Compile-time narrowing only: when set, `send` accepts only event types this preset\n * declares (plus reserved `webhook.*`). The server enforces the same thing at runtime.\n */\n preset?: WebhookObjectPreset\n /** Total delivery attempts per event (initial + retries on 429/5xx/network). Default 3. */\n maxAttempts?: number\n /** Custom fetch implementation (tests, proxies). Defaults to the global fetch (Node 18+). */\n fetch?: typeof fetch\n /**\n * Override the per-event `webhook-id` generator. Defaults to the global `crypto.randomUUID`;\n * inject your own for runtimes without global `crypto`, or for deterministic tests. Must return\n * a unique value per event (the server dedups by id, so reused ids would drop distinct events).\n */\n newWebhookId?: () => string\n /**\n * Cancels an in-flight `send`/`ping`, including any pending retry backoff, when aborted.\n * The call rejects with a `WebhookObjectError` whose `code` is `aborted`.\n */\n signal?: AbortSignal\n}\n\n/**\n * Event types a client scoped to preset `P` may send. Intersected with `WebhookEventType`\n * so `DataFor<T>` stays applicable while `P` is still generic.\n */\nexport type AllowedEventType<P extends WebhookObjectPreset | undefined> =\n (P extends WebhookObjectPreset ? EventTypeForPreset<P> : WebhookEventType) & WebhookEventType\n\n/** Empty-payload events take no `data` argument; everything else requires one. */\ntype DataArgs<T extends WebhookEventType> =\n DataFor<T> extends Record<string, never> ? [] : [data: DataFor<T>]\n\n/** Both statuses mean \"accepted\": `space_idle` means the space wasn't live, and the event was persisted for it. */\nexport type SendResult = { status: \"dispatched\" | \"space_idle\" }\n\n/** The `webhook.ping` snapshot: what this object is and what it currently accepts. */\nexport type PingResult = {\n status: \"pong\"\n objectId: string\n spaceId: string\n preset: WebhookObjectPreset | null\n capabilities: Record<string, unknown>\n}\n\n/** `set_state` \u2192 `setState`: fluent method names are idiomatic camelCase versions of wire names. */\ntype SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}`\n ? `${Head}${Capitalize<SnakeToCamel<Tail>>}`\n : S\n\n/** The capability segment of an event type: `\"counter.reset\"` \u2192 `\"counter\"`. */\ntype CapabilityNameOf<E extends string> = E extends `${infer Capability}.${string}`\n ? Capability\n : never\n\n/**\n * Capability-namespaced sugar over `send`: `client.counter.reset()`,\n * `client.switch.setState({ on: true })`. Derived entirely from the generated event union, so\n * new capabilities appear via a types-package bump; reserved `webhook.*` events are excluded\n * (`ping()` is first-class). Backed by a `Proxy` at runtime: no codegen, no runtime catalog.\n */\nexport type CapabilityNamespaces<Events extends WebhookEventType> = {\n [Capability in CapabilityNameOf<Exclude<Events, ReservedEventType>>]: {\n [Event in Extract<\n Events,\n `${Capability}.${string}`\n > as Event extends `${Capability}.${infer Method}` ? SnakeToCamel<Method> : never]: (\n ...args: DataArgs<Event>\n ) => Promise<SendResult>\n }\n}\n\n/**\n * A client bound to one webhook object. Exposes `send`/`ping` plus the capability-namespaced\n * fluent sugar (`client.counter.increment(...)`). The optional preset `P` narrows every surface\n * to the event types that preset declares.\n */\nexport type WebhookObjectClient<P extends WebhookObjectPreset | undefined = undefined> = {\n /**\n * Sign and POST one event. `data` is inferred from the event type and omitted entirely\n * for empty-payload events (e.g. `send(\"counter.reset\")`).\n */\n send<T extends AllowedEventType<P>>(type: T, ...args: DataArgs<T>): Promise<SendResult>\n /** Verify the URL + secret and fetch the object's declared capability state. */\n ping(): Promise<PingResult>\n} & CapabilityNamespaces<AllowedEventType<P>>\n\n/** Hosts allowed to receive cleartext http, so local development keeps working. */\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"])\n\n/**\n * Signing authenticates but does not encrypt: over plain http the payload, headers, and\n * signature travel in cleartext. Refuse everything but https, except toward localhost.\n */\nconst assertValidUrl = (url: string): void => {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch (cause) {\n throw new WebhookObjectError(\"invalid_url\", `webhook object URL is not a valid URL: ${url}`, {\n cause,\n })\n }\n if (parsed.protocol === \"https:\") return\n if (parsed.protocol === \"http:\" && LOCAL_HOSTNAMES.has(parsed.hostname)) return\n throw new WebhookObjectError(\n \"invalid_url\",\n `webhook object URL must use https (got ${parsed.protocol}//); http is allowed only for localhost`,\n )\n}\n\n/** Fluent method \u2192 wire method segment: `setState` \u2192 `set_state`. */\nconst wireMethodName = (method: string): string =>\n method.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`)\n\n/**\n * Properties runtimes probe on arbitrary objects (`await` checks `then`, serializers check\n * `toJSON`). The capability proxy must not answer these, or `await client.counter` would hang.\n */\nconst PROBED_PROPS = new Set([\"then\", \"toJSON\"])\n\n/** Both \"accepted\" statuses; extra body fields (if the receiver ever adds any) pass through. */\nconst isSendResult = (body: Record<string, unknown>): body is SendResult =>\n body.status === \"dispatched\" || body.status === \"space_idle\"\n\n/** The pong snapshot, checked field by field: the wire is untrusted, so the shape is proven. */\nconst isPingResult = (body: Record<string, unknown>): body is PingResult =>\n body.status === \"pong\" &&\n typeof body.objectId === \"string\" &&\n typeof body.spaceId === \"string\" &&\n (body.preset === null || typeof body.preset === \"string\") &&\n typeof body.capabilities === \"object\" &&\n body.capabilities !== null\n\nconst serializeEnvelope = (type: string, data: unknown): string => {\n const body = JSON.stringify({ type, timestamp: new Date().toISOString(), data })\n const byteLength = new TextEncoder().encode(body).length\n if (byteLength > MAX_BODY_BYTES)\n throw new WebhookObjectError(\n \"payload_too_large\",\n `webhook object payload is ${byteLength} bytes; the server caps bodies at ${MAX_BODY_BYTES} bytes`,\n )\n return body\n}\n\n/**\n * Create a client for one webhook object. Signing follows Standard Webhooks v1: HMAC over\n * `${id}.${timestamp}.${body}` via the `standardwebhooks` reference library, sent as the\n * `webhook-id` / `webhook-timestamp` / `webhook-signature` headers. The exact serialized\n * body is signed and sent unchanged, so the two can never drift.\n */\nexport const createWebhookObjectClient = <P extends WebhookObjectPreset | undefined = undefined>(\n config: WebhookObjectClientConfig & { preset?: P },\n): WebhookObjectClient<P> => {\n assertValidUrl(config.url)\n // Revealed once here and captured only by the standardwebhooks signer; the returned\n // client never holds the raw value on an enumerable property.\n const webhook = new Webhook(revealSecret(config.secret))\n const fetchImpl = config.fetch ?? fetch\n const newWebhookId = config.newWebhookId ?? (() => crypto.randomUUID())\n const maxAttempts = config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n\n /**\n * Sign, deliver, and narrow the response to the caller's expected shape. The caller controls\n * the result type, but through a runtime guard rather than a cast: the wire is untrusted, so\n * an off-contract 200 body throws `unexpected_response` instead of lying about its shape.\n */\n const dispatch = async <R extends Record<string, unknown>>(\n type: string,\n data: unknown,\n isExpectedBody: (body: Record<string, unknown>) => body is R,\n ): Promise<R> => {\n const body = serializeEnvelope(type, data)\n // One id per event, reused across retries: the server dedups the last 10 ids per\n // object, so a retry can never double-apply.\n const webhookId = newWebhookId()\n const responseBody = await deliver({\n url: config.url,\n body,\n webhookId,\n sign: (timestampSeconds) => webhook.sign(webhookId, new Date(timestampSeconds * 1000), body),\n fetchImpl,\n maxAttempts,\n signal: config.signal,\n })\n if (!isExpectedBody(responseBody))\n throw new WebhookObjectError(\n \"unexpected_response\",\n `webhook object ${type} succeeded but returned an unrecognized body (status: ${String(responseBody.status)})`,\n )\n return responseBody\n }\n\n const sendEvent = (type: string, data: unknown): Promise<SendResult> =>\n dispatch(type, data, isSendResult)\n\n const base = {\n send: <T extends AllowedEventType<P>>(type: T, ...args: DataArgs<T>): Promise<SendResult> =>\n sendEvent(type, args[0] ?? {}),\n ping: (): Promise<PingResult> => dispatch(\"webhook.ping\", {}, isPingResult),\n }\n\n /** `client.counter` \u2192 namespace whose method calls become `sendEvent(\"counter.<method>\")`. */\n const capabilityNamespace = (capability: string) =>\n new Proxy(\n {},\n {\n get: (_target, method) => {\n if (typeof method !== \"string\" || PROBED_PROPS.has(method)) return undefined\n return (data?: unknown) =>\n sendEvent(`${capability}.${wireMethodName(method)}`, data ?? {})\n },\n },\n )\n\n // The fluent surface is type-driven (CapabilityNamespaces); the proxy only assembles wire\n // names, so new capabilities work via a types-package bump with zero SDK changes.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return new Proxy(base, {\n get: (target, prop, receiver) => {\n // Real members (send/ping) win over capability namespaces, as do symbol lookups.\n if (prop in target || typeof prop !== \"string\") return Reflect.get(target, prop, receiver)\n if (PROBED_PROPS.has(prop)) return undefined\n return capabilityNamespace(prop)\n },\n }) as WebhookObjectClient<P>\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAwB;AASxB,qBAA8D;AAC9D,oBAAmC;AACnC,oBAAkD;
|
|
4
|
+
"sourcesContent": ["import { Webhook } from \"standardwebhooks\"\n\nimport type {\n DataFor,\n EventTypeForPreset,\n ReservedEventType,\n WebhookEventType,\n WebhookObjectPreset,\n} from \"@gathertown/webhook-object-types\"\nimport { DEFAULT_MAX_ATTEMPTS, deliver, MAX_BODY_BYTES } from \"./deliver\"\nimport { WebhookObjectError } from \"./errors\"\nimport { revealSecret, WebhookObjectSecret } from \"./secret\"\n\nexport type WebhookObjectClientConfig = {\n /** The object's full webhook URL (copied from the object's \u22EE menu in Gather). */\n url: string\n /**\n * The object's signing secret (per-object; never shared between objects). Either the raw\n * `whsec_\u2026` string, or (recommended) an opaque handle from `secretFromEnv()` /\n * `unsafeSecretLiteral()`, which redacts itself if the config is ever logged or serialized.\n */\n secret: WebhookObjectSecret | string\n /**\n * Compile-time narrowing only: when set, `send` accepts only event types this preset\n * declares (plus reserved `webhook.*`). The server enforces the same thing at runtime.\n */\n preset?: WebhookObjectPreset\n /** Total delivery attempts per event (initial + retries on 429/5xx/network). Default 3. */\n maxAttempts?: number\n /** Custom fetch implementation (tests, proxies). Defaults to the global fetch (Node 18+). */\n fetch?: typeof fetch\n /**\n * Override the per-event `webhook-id` generator. Defaults to the global `crypto.randomUUID`;\n * inject your own for runtimes without global `crypto`, or for deterministic tests. Must return\n * a unique value per event (the server dedups by id, so reused ids would drop distinct events).\n */\n newWebhookId?: () => string\n /**\n * Cancels an in-flight `send`/`ping`, including any pending retry backoff, when aborted.\n * The call rejects with a `WebhookObjectError` whose `code` is `aborted`.\n */\n signal?: AbortSignal\n}\n\n/**\n * Event types a client scoped to preset `P` may send. Intersected with `WebhookEventType`\n * so `DataFor<T>` stays applicable while `P` is still generic.\n */\nexport type AllowedEventType<P extends WebhookObjectPreset | undefined> =\n (P extends WebhookObjectPreset ? EventTypeForPreset<P> : WebhookEventType) & WebhookEventType\n\n/** Empty-payload events take no `data` argument; everything else requires one. */\ntype DataArgs<T extends WebhookEventType> =\n DataFor<T> extends Record<string, never> ? [] : [data: DataFor<T>]\n\n/** Both statuses mean \"accepted\": `space_idle` means the space wasn't live, and the event was persisted for it. */\nexport type SendResult = { status: \"dispatched\" | \"space_idle\" }\n\n/** The `webhook.ping` snapshot: what this object is and what it currently accepts. */\nexport type PingResult = {\n status: \"pong\"\n objectId: string\n spaceId: string\n preset: WebhookObjectPreset | null\n capabilities: Record<string, unknown>\n /**\n * Current-orientation colors `variant.set` will actually resolve. A color not in this list is\n * silently ignored. Optional: a server predating this field omits it, so an SDK upgraded ahead of\n * the server still pings successfully (`colors` is then `undefined`).\n */\n colors?: string[]\n}\n\n/** `set_state` \u2192 `setState`: fluent method names are idiomatic camelCase versions of wire names. */\ntype SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}`\n ? `${Head}${Capitalize<SnakeToCamel<Tail>>}`\n : S\n\n/** The capability segment of an event type: `\"counter.reset\"` \u2192 `\"counter\"`. */\ntype CapabilityNameOf<E extends string> = E extends `${infer Capability}.${string}`\n ? Capability\n : never\n\n/**\n * Capability-namespaced sugar over `send`: `client.counter.reset()`,\n * `client.switch.setState({ on: true })`. Derived entirely from the generated event union, so\n * new capabilities appear via a types-package bump; reserved `webhook.*` events are excluded\n * (`ping()` is first-class). Backed by a `Proxy` at runtime: no codegen, no runtime catalog.\n */\nexport type CapabilityNamespaces<Events extends WebhookEventType> = {\n [Capability in CapabilityNameOf<Exclude<Events, ReservedEventType>>]: {\n [Event in Extract<\n Events,\n `${Capability}.${string}`\n > as Event extends `${Capability}.${infer Method}` ? SnakeToCamel<Method> : never]: (\n ...args: DataArgs<Event>\n ) => Promise<SendResult>\n }\n}\n\n/**\n * A client bound to one webhook object. Exposes `send`/`ping` plus the capability-namespaced\n * fluent sugar (`client.counter.increment(...)`). The optional preset `P` narrows every surface\n * to the event types that preset declares.\n */\nexport type WebhookObjectClient<P extends WebhookObjectPreset | undefined = undefined> = {\n /**\n * Sign and POST one event. `data` is inferred from the event type and omitted entirely\n * for empty-payload events (e.g. `send(\"counter.reset\")`).\n */\n send<T extends AllowedEventType<P>>(type: T, ...args: DataArgs<T>): Promise<SendResult>\n /** Verify the URL + secret and fetch the object's declared capabilities and available colors. */\n ping(): Promise<PingResult>\n} & CapabilityNamespaces<AllowedEventType<P>>\n\n/** Hosts allowed to receive cleartext http, so local development keeps working. */\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"])\n\n/**\n * Signing authenticates but does not encrypt: over plain http the payload, headers, and\n * signature travel in cleartext. Refuse everything but https, except toward localhost.\n */\nconst assertValidUrl = (url: string): void => {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch (cause) {\n throw new WebhookObjectError(\"invalid_url\", `webhook object URL is not a valid URL: ${url}`, {\n cause,\n })\n }\n if (parsed.protocol === \"https:\") return\n if (parsed.protocol === \"http:\" && LOCAL_HOSTNAMES.has(parsed.hostname)) return\n throw new WebhookObjectError(\n \"invalid_url\",\n `webhook object URL must use https (got ${parsed.protocol}//); http is allowed only for localhost`,\n )\n}\n\n/** Fluent method \u2192 wire method segment: `setState` \u2192 `set_state`. */\nconst wireMethodName = (method: string): string =>\n method.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`)\n\n/**\n * Properties runtimes probe on arbitrary objects (`await` checks `then`, serializers check\n * `toJSON`). The capability proxy must not answer these, or `await client.counter` would hang.\n */\nconst PROBED_PROPS = new Set([\"then\", \"toJSON\"])\n\n/** Both \"accepted\" statuses; extra body fields (if the receiver ever adds any) pass through. */\nconst isSendResult = (body: Record<string, unknown>): body is SendResult =>\n body.status === \"dispatched\" || body.status === \"space_idle\"\n\n/** The pong snapshot, checked field by field: the wire is untrusted, so the shape is proven. */\nconst isPingResult = (body: Record<string, unknown>): body is PingResult =>\n body.status === \"pong\" &&\n typeof body.objectId === \"string\" &&\n typeof body.spaceId === \"string\" &&\n (body.preset === null || typeof body.preset === \"string\") &&\n typeof body.capabilities === \"object\" &&\n body.capabilities !== null &&\n // `colors` is optional so the SDK stays compatible with a server that predates the field: an\n // older pong omits it, and rejecting that would break ping() when the SDK is upgraded first.\n (body.colors === undefined ||\n (Array.isArray(body.colors) && body.colors.every((color) => typeof color === \"string\")))\n\nconst serializeEnvelope = (type: string, data: unknown): string => {\n const body = JSON.stringify({ type, timestamp: new Date().toISOString(), data })\n const byteLength = new TextEncoder().encode(body).length\n if (byteLength > MAX_BODY_BYTES)\n throw new WebhookObjectError(\n \"payload_too_large\",\n `webhook object payload is ${byteLength} bytes; the server caps bodies at ${MAX_BODY_BYTES} bytes`,\n )\n return body\n}\n\n/**\n * Create a client for one webhook object. Signing follows Standard Webhooks v1: HMAC over\n * `${id}.${timestamp}.${body}` via the `standardwebhooks` reference library, sent as the\n * `webhook-id` / `webhook-timestamp` / `webhook-signature` headers. The exact serialized\n * body is signed and sent unchanged, so the two can never drift.\n */\nexport const createWebhookObjectClient = <P extends WebhookObjectPreset | undefined = undefined>(\n config: WebhookObjectClientConfig & { preset?: P },\n): WebhookObjectClient<P> => {\n assertValidUrl(config.url)\n // Revealed once here and captured only by the standardwebhooks signer; the returned\n // client never holds the raw value on an enumerable property.\n const webhook = new Webhook(revealSecret(config.secret))\n const fetchImpl = config.fetch ?? fetch\n const newWebhookId = config.newWebhookId ?? (() => crypto.randomUUID())\n const maxAttempts = config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n\n /**\n * Sign, deliver, and narrow the response to the caller's expected shape. The caller controls\n * the result type, but through a runtime guard rather than a cast: the wire is untrusted, so\n * an off-contract 200 body throws `unexpected_response` instead of lying about its shape.\n */\n const dispatch = async <R extends Record<string, unknown>>(\n type: string,\n data: unknown,\n isExpectedBody: (body: Record<string, unknown>) => body is R,\n ): Promise<R> => {\n const body = serializeEnvelope(type, data)\n // One id per event, reused across retries: the server dedups the last 10 ids per\n // object, so a retry can never double-apply.\n const webhookId = newWebhookId()\n const responseBody = await deliver({\n url: config.url,\n body,\n webhookId,\n sign: (timestampSeconds) => webhook.sign(webhookId, new Date(timestampSeconds * 1000), body),\n fetchImpl,\n maxAttempts,\n signal: config.signal,\n })\n if (!isExpectedBody(responseBody))\n throw new WebhookObjectError(\n \"unexpected_response\",\n `webhook object ${type} succeeded but returned an unrecognized body (status: ${String(responseBody.status)})`,\n )\n return responseBody\n }\n\n const sendEvent = (type: string, data: unknown): Promise<SendResult> =>\n dispatch(type, data, isSendResult)\n\n const base = {\n send: <T extends AllowedEventType<P>>(type: T, ...args: DataArgs<T>): Promise<SendResult> =>\n sendEvent(type, args[0] ?? {}),\n ping: (): Promise<PingResult> => dispatch(\"webhook.ping\", {}, isPingResult),\n }\n\n /** `client.counter` \u2192 namespace whose method calls become `sendEvent(\"counter.<method>\")`. */\n const capabilityNamespace = (capability: string) =>\n new Proxy(\n {},\n {\n get: (_target, method) => {\n if (typeof method !== \"string\" || PROBED_PROPS.has(method)) return undefined\n return (data?: unknown) =>\n sendEvent(`${capability}.${wireMethodName(method)}`, data ?? {})\n },\n },\n )\n\n // The fluent surface is type-driven (CapabilityNamespaces); the proxy only assembles wire\n // names, so new capabilities work via a types-package bump with zero SDK changes.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return new Proxy(base, {\n get: (target, prop, receiver) => {\n // Real members (send/ping) win over capability namespaces, as do symbol lookups.\n if (prop in target || typeof prop !== \"string\") return Reflect.get(target, prop, receiver)\n if (PROBED_PROPS.has(prop)) return undefined\n return capabilityNamespace(prop)\n },\n }) as WebhookObjectClient<P>\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gathertown/webhook-object-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Typed client for emitting events to Gather webhook objects (Smart Objects): Standard Webhooks signing, retries, and a fully-inferred send API.",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -26,11 +26,32 @@
|
|
|
26
26
|
"author": "Gather Presence, Inc.",
|
|
27
27
|
"license": "MIT OR Apache-2.0",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@gathertown/webhook-object-types": "^0.
|
|
29
|
+
"@gathertown/webhook-object-types": "^0.3.0",
|
|
30
30
|
"standardwebhooks": "~1.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"expect-type": "~0.20.0",
|
|
34
34
|
"typescript": "~6.0.3"
|
|
35
|
+
},
|
|
36
|
+
"nx": {
|
|
37
|
+
"targets": {
|
|
38
|
+
"build": {
|
|
39
|
+
"outputs": [
|
|
40
|
+
"{projectRoot}/dist/**/*.js",
|
|
41
|
+
"{projectRoot}/dist/**/*.js.map",
|
|
42
|
+
"{projectRoot}/dist/**/*.html",
|
|
43
|
+
"{projectRoot}/dist/**/*.css",
|
|
44
|
+
"{projectRoot}/dist/**/*.glsl",
|
|
45
|
+
"{projectRoot}/dist/**/*.json",
|
|
46
|
+
"{projectRoot}/dist/**/*.py",
|
|
47
|
+
"{projectRoot}/dist/**/*.sh",
|
|
48
|
+
"{projectRoot}/dist/**/*.yml",
|
|
49
|
+
"{projectRoot}/dist/**/*.txt",
|
|
50
|
+
"{projectRoot}/dist/**/*.csv",
|
|
51
|
+
"{projectRoot}/dist/**/*.d.ts",
|
|
52
|
+
"{projectRoot}/dist/**/*.d.ts.map"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
35
56
|
}
|
|
36
57
|
}
|