diff --git a/docs-src/spectrum-ts/content.mdx.vel b/docs-src/spectrum-ts/content.mdx.vel index ad98706..1cb7a65 100644 --- a/docs-src/spectrum-ts/content.mdx.vel +++ b/docs-src/spectrum-ts/content.mdx.vel @@ -29,7 +29,7 @@ Use this section when you need to choose the right outbound content shape or und Share contact cards from structured data, users, or vCards. - Render URLs as rich previews with lazy Open Graph metadata. + Send URLs as rich preview cards rendered by the receiving platform. Present URLs as tappable app-style cards where providers support them. diff --git a/docs-src/spectrum-ts/content/rich-links.mdx.vel b/docs-src/spectrum-ts/content/rich-links.mdx.vel index 3e4fcc7..fa04518 100644 --- a/docs-src/spectrum-ts/content/rich-links.mdx.vel +++ b/docs-src/spectrum-ts/content/rich-links.mdx.vel @@ -1,9 +1,9 @@ --- title: "Rich links" -description: "Render URLs as rich previews with lazy Open Graph metadata." +description: "Send URLs as rich preview cards rendered by the receiving platform." --- -Use `richlink()` to render a URL as a rich preview card with title, summary, and cover image. Spectrum scrapes Open Graph metadata at send time. Pass just the URL and the builder fills in the rest. +Use `richlink()` to send a URL as a rich preview card. Each platform's native client renders the preview — iMessage enables `enableLinkPreview`, Telegram auto-unfurls the bare URL. Spectrum carries only the URL; metadata fetching is left to the platform. ```ts import { richlink } from "spectrum-ts"; @@ -11,13 +11,13 @@ import { richlink } from "spectrum-ts"; await space.send(richlink("https://example.com/article")); ``` -`title()`, `summary()`, and `cover()` are lazy async accessors. The metadata fetch happens only if the receiving platform needs it. Platforms without rich-link support fall back to the URL as plain text. +Platforms without rich-link support fall back to the URL as plain text. - +`richlink` is **outbound-only**. Inbound URLs always arrive as `text` content — a URL received from any platform is normalized to its URL string and deserialized as plain `text`, never as a `richlink` payload. + + | Field | Type | Description | |---|---|---| - | `url` | `string` | The original URL. | - | `title()` | `() => Promise` | OG title. | - | `summary()` | `() => Promise` | OG description. | - | `cover()` | `() => Promise<{ mimeType?, read(), stream() } \| undefined>` | OG image. | + | `type` | `"richlink"` | Content discriminator. | + | `url` | `string` | The URL to preview. | diff --git a/docs-src/spectrum-ts/introduction.mdx.vel b/docs-src/spectrum-ts/introduction.mdx.vel index 30d16d6..2e52f94 100644 --- a/docs-src/spectrum-ts/introduction.mdx.vel +++ b/docs-src/spectrum-ts/introduction.mdx.vel @@ -116,7 +116,7 @@ You do not have to manage Spectrum only from the dashboard. Use the dashboard fo Install `spectrum-ts` and send your first message. - Receive messages via HTTP with native and Fusor webhook support, plus Hono, Express, and Elysia adapters. + Receive messages via HTTP with native and Fusor webhook support, plus Hono, Express, Elysia, and Fastify adapters. Add a new interface with Spectrum's provider model. diff --git a/docs-src/spectrum-ts/webhooks.mdx.vel b/docs-src/spectrum-ts/webhooks.mdx.vel index 243f9a5..3e3e8a9 100644 --- a/docs-src/spectrum-ts/webhooks.mdx.vel +++ b/docs-src/spectrum-ts/webhooks.mdx.vel @@ -168,9 +168,39 @@ First-party adapters mount the endpoint for you and handle raw-body parsing corr .listen(3000); ``` + + ```ts + import Fastify from "fastify"; + import { Spectrum } from "spectrum-ts"; + import { imessage } from "spectrum-ts/providers/imessage"; + import { spectrum } from "@spectrum-ts/fastify"; + + const app = await Spectrum({ + projectId: process.env.PROJECT_ID!, + projectSecret: process.env.PROJECT_SECRET!, + providers: [imessage.config()], + webhookSecret: process.env.SPECTRUM_WEBHOOK_SECRET, + }); + + const server = Fastify(); + + server.register(spectrum, { + app, + onMessage: async (space, message) => { + if (message.content.type === "text") { + await space.send(`echo: ${message.content.text}`); + } + }, + }); + + await server.listen({ port: 3000 }); + ``` + + Fastify auto-parses known content types before your route handler runs, which breaks HMAC verification. The `@spectrum-ts/fastify` plugin registers a wildcard raw-body parser in its own encapsulated scope, so the webhook route receives exact wire bytes while your other routes keep their normal JSON parsing. + -All three adapters accept the same options: +All four adapters accept the same options: | Option | Type | Default | Description | |---|---|---|---| diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11734b2..51e33cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2341,8 +2341,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.14: - resolution: {integrity: sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==} + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -3704,8 +3704,8 @@ snapshots: '@typescript-eslint/project-service@8.58.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.3) + '@typescript-eslint/types': 8.58.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -5217,7 +5217,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.14: {} + nanoid@3.3.15: {} napi-build-utils@2.0.0: optional: true @@ -5404,7 +5404,7 @@ snapshots: postcss@8.5.15: dependencies: - nanoid: 3.3.14 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1