@strav/instant 1.0.0-alpha.42

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.
Files changed (56) hide show
  1. package/package.json +49 -0
  2. package/src/drivers/line/index.ts +50 -0
  3. package/src/drivers/line/liff_client.ts +126 -0
  4. package/src/drivers/line/liff_sign_in_route.ts +112 -0
  5. package/src/drivers/line/line_beacon.ts +14 -0
  6. package/src/drivers/line/line_config.ts +55 -0
  7. package/src/drivers/line/line_driver.ts +202 -0
  8. package/src/drivers/line/line_flex.ts +117 -0
  9. package/src/drivers/line/line_liff.ts +102 -0
  10. package/src/drivers/line/line_message_mapper.ts +118 -0
  11. package/src/drivers/line/line_provider.ts +33 -0
  12. package/src/drivers/line/line_rich_menu.ts +68 -0
  13. package/src/drivers/line/line_webhook.ts +168 -0
  14. package/src/drivers/messenger/index.ts +16 -0
  15. package/src/drivers/messenger/messenger_config.ts +20 -0
  16. package/src/drivers/messenger/messenger_driver.ts +151 -0
  17. package/src/drivers/messenger/messenger_message_mapper.ts +146 -0
  18. package/src/drivers/messenger/messenger_profile.ts +43 -0
  19. package/src/drivers/messenger/messenger_provider.ts +31 -0
  20. package/src/drivers/messenger/messenger_webhook.ts +165 -0
  21. package/src/drivers/telegram/index.ts +23 -0
  22. package/src/drivers/telegram/telegram_config.ts +19 -0
  23. package/src/drivers/telegram/telegram_driver.ts +121 -0
  24. package/src/drivers/telegram/telegram_message_mapper.ts +147 -0
  25. package/src/drivers/telegram/telegram_provider.ts +32 -0
  26. package/src/drivers/telegram/telegram_web_app.ts +108 -0
  27. package/src/drivers/telegram/telegram_webhook.ts +200 -0
  28. package/src/drivers/whatsapp/flows/index.ts +15 -0
  29. package/src/drivers/whatsapp/flows/whatsapp_flow_builder.ts +55 -0
  30. package/src/drivers/whatsapp/flows/whatsapp_flow_crypto.ts +81 -0
  31. package/src/drivers/whatsapp/index.ts +14 -0
  32. package/src/drivers/whatsapp/whatsapp_config.ts +35 -0
  33. package/src/drivers/whatsapp/whatsapp_driver.ts +111 -0
  34. package/src/drivers/whatsapp/whatsapp_message_mapper.ts +157 -0
  35. package/src/drivers/whatsapp/whatsapp_provider.ts +31 -0
  36. package/src/drivers/whatsapp/whatsapp_webhook.ts +170 -0
  37. package/src/drivers/zalo/index.ts +16 -0
  38. package/src/drivers/zalo/zalo_config.ts +37 -0
  39. package/src/drivers/zalo/zalo_driver.ts +130 -0
  40. package/src/drivers/zalo/zalo_message_mapper.ts +146 -0
  41. package/src/drivers/zalo/zalo_mini_app.ts +17 -0
  42. package/src/drivers/zalo/zalo_provider.ts +31 -0
  43. package/src/drivers/zalo/zalo_webhook.ts +139 -0
  44. package/src/errors.ts +122 -0
  45. package/src/index.ts +52 -0
  46. package/src/instant_capabilities.ts +49 -0
  47. package/src/instant_driver.ts +109 -0
  48. package/src/instant_manager.ts +113 -0
  49. package/src/instant_provider.ts +45 -0
  50. package/src/internal/fetch_json.ts +58 -0
  51. package/src/internal/meta/meta_graph_client.ts +51 -0
  52. package/src/internal/meta/meta_signature.ts +22 -0
  53. package/src/internal/meta/meta_webhook_challenge.ts +19 -0
  54. package/src/message.ts +65 -0
  55. package/src/types.ts +32 -0
  56. package/src/webhook_event.ts +93 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `InstantDriver` — the driver contract every adapter implements.
3
+ *
4
+ * One driver represents a configured provider instance
5
+ * (`config.instant.providers['line']`). The manager holds one
6
+ * driver per configured name and routes send / reply / webhook
7
+ * calls into it.
8
+ *
9
+ * Methods drivers don't support throw `ProviderUnsupportedError`
10
+ * synchronously. The driver's `capabilities` set declares the
11
+ * supported surfaces — apps that branch on capability avoid the
12
+ * throw by checking first.
13
+ */
14
+
15
+ import type { InstantCapability } from './instant_capabilities.ts'
16
+ import type { OutgoingMessage, SendResult } from './message.ts'
17
+ import type { WebhookEvent } from './webhook_event.ts'
18
+
19
+ /**
20
+ * Inbound webhook verification + parsing. Drivers that lack a
21
+ * webhook surface (rare for an instant-messaging provider)
22
+ * declare neither `webhook.signature` nor `webhook.parse` and
23
+ * throw `ProviderUnsupportedError` from these methods.
24
+ */
25
+ export interface WebhookOps {
26
+ /**
27
+ * Verify the provider signature against the raw request body.
28
+ * Returns `true` when the signature matches. Drivers throw
29
+ * `WebhookSignatureError` only when the header is missing /
30
+ * malformed; a clean mismatch returns `false` so the route
31
+ * can decide how to respond.
32
+ */
33
+ verifySignature(rawBody: string, signature: string | null | undefined): boolean
34
+ /**
35
+ * Parse a verified raw body into the framework's normalized
36
+ * `WebhookEvent` union. Drivers translate every event variant
37
+ * they can recognise; unknown shapes map to `{ type: 'unknown', raw }`.
38
+ */
39
+ parse(rawBody: string): WebhookEvent[]
40
+ /**
41
+ * Optional GET-handshake responder used by webhook providers
42
+ * that require URL verification before they post events
43
+ * (Meta Cloud / Send API). Receivers call this with the
44
+ * query params; the driver returns the challenge value to
45
+ * echo back when the verify token matches, or `null` to
46
+ * reject. Drivers without a handshake omit this method.
47
+ */
48
+ verifyChallenge?(params: Record<string, string | undefined>): string | null
49
+ }
50
+
51
+ /**
52
+ * Optional profile lookup. LINE / Messenger / WhatsApp all expose
53
+ * a "get user profile by id" endpoint with diverging fields;
54
+ * the driver returns whatever it can fill.
55
+ */
56
+ export interface UserProfile {
57
+ userId: string
58
+ displayName?: string
59
+ pictureUrl?: string
60
+ statusMessage?: string
61
+ language?: string
62
+ raw?: unknown
63
+ }
64
+
65
+ export interface InstantDriver {
66
+ /** Driver identifier — matches the `driver:` discriminator in `ProviderConfig`. */
67
+ readonly name: string
68
+ /** App-chosen instance name (`config.instant.providers[name]`). */
69
+ readonly instanceName: string
70
+ /** Declared feature set. Apps check this to branch around `ProviderUnsupportedError`. */
71
+ readonly capabilities: ReadonlySet<InstantCapability>
72
+
73
+ /**
74
+ * Send a message to a single recipient. Whether this uses the
75
+ * provider's "push" endpoint, "send" endpoint, or a reply
76
+ * window is provider-defined; the common shape is "I have a
77
+ * recipient id, deliver this message."
78
+ */
79
+ send(to: string, message: OutgoingMessage): Promise<SendResult>
80
+
81
+ /**
82
+ * Reply to an inbound event within the provider's reply
83
+ * window. `replyToken` is provider-issued (LINE reply token,
84
+ * WhatsApp context message id, …).
85
+ */
86
+ reply?(replyToken: string, message: OutgoingMessage): Promise<SendResult>
87
+
88
+ /** Push to a recipient outside a reply window. Distinct from `send` only on providers that distinguish. */
89
+ push?(to: string, message: OutgoingMessage): Promise<SendResult>
90
+
91
+ /** Multicast — same message to many recipients. */
92
+ multicast?(to: readonly string[], message: OutgoingMessage): Promise<SendResult>
93
+
94
+ /** Broadcast — same message to every follower. */
95
+ broadcast?(message: OutgoingMessage): Promise<SendResult>
96
+
97
+ /** Fetch a profile from the provider. */
98
+ profile?(userId: string): Promise<UserProfile>
99
+
100
+ readonly webhook: WebhookOps
101
+ }
102
+
103
+ /** Factory the manager invokes for each configured provider. */
104
+ export type InstantDriverFactory = (config: {
105
+ /** App-chosen instance name (`'line'`, `'line-marketing'`, …). */
106
+ instanceName: string
107
+ /** Provider-config object with `driver:` + driver-specific fields. */
108
+ config: Record<string, unknown> & { driver: string }
109
+ }) => InstantDriver
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `InstantManager` — the facade apps use for instant-messaging
3
+ * workflows.
4
+ *
5
+ * Mirrors the manager-pattern shared with `@strav/payment` and
6
+ * `@strav/notification`:
7
+ *
8
+ * - **Drivers.** Apps declare providers in
9
+ * `config.instant.providers`. The manager constructs each
10
+ * driver lazily on first `use(name)` + memoizes. Adapter
11
+ * packages register their factories via
12
+ * `manager.extend(name, factory)`.
13
+ *
14
+ * - **Default routing.** `manager.send(to, msg)` routes to the
15
+ * default driver. `manager.use('marketing').send(...)`
16
+ * targets a named one.
17
+ *
18
+ * - **Webhooks.** `manager.verify(provider, rawBody, sig)` and
19
+ * `manager.parseWebhook(provider, rawBody)` delegate into the
20
+ * driver's `WebhookOps`. Apps wire these into their HTTP
21
+ * route (one route per provider).
22
+ */
23
+
24
+ import { InstantConfigError, UnknownProviderError } from './errors.ts'
25
+ import type { InstantDriver, InstantDriverFactory } from './instant_driver.ts'
26
+ import type { OutgoingMessage, SendResult } from './message.ts'
27
+ import type { InstantConfig, ProviderConfig } from './types.ts'
28
+ import type { WebhookEvent } from './webhook_event.ts'
29
+
30
+ export interface InstantManagerOptions {
31
+ config: InstantConfig
32
+ }
33
+
34
+ export class InstantManager {
35
+ readonly config: InstantConfig
36
+
37
+ private readonly drivers = new Map<string, InstantDriver>()
38
+ private readonly extensions = new Map<string, InstantDriverFactory>()
39
+
40
+ constructor(options: InstantManagerOptions) {
41
+ const { config } = options
42
+ if (!config.providers[config.default]) {
43
+ throw new InstantConfigError(
44
+ `InstantManager: default provider "${config.default}" is not configured.`,
45
+ {
46
+ context: {
47
+ default: config.default,
48
+ available: Object.keys(config.providers),
49
+ },
50
+ },
51
+ )
52
+ }
53
+ this.config = config
54
+ }
55
+
56
+ // ─── Driver routing ──────────────────────────────────────────────────
57
+
58
+ /** Resolve a driver by app-chosen instance name (or the default when omitted). */
59
+ use(name?: string): InstantDriver {
60
+ const key = name ?? this.config.default
61
+ const cached = this.drivers.get(key)
62
+ if (cached) return cached
63
+
64
+ const cfg = this.config.providers[key]
65
+ if (!cfg) {
66
+ throw new UnknownProviderError(key, Object.keys(this.config.providers))
67
+ }
68
+ const ext = this.extensions.get(cfg.driver)
69
+ if (!ext) {
70
+ throw new InstantConfigError(
71
+ `InstantManager: unknown driver "${cfg.driver}" for provider "${key}". Register it via \`manager.extend("${cfg.driver}", factory)\` or install the matching adapter package.`,
72
+ { context: { driver: cfg.driver, available: [...this.extensions.keys()] } },
73
+ )
74
+ }
75
+ const driver = ext({
76
+ instanceName: key,
77
+ config: cfg as ProviderConfig & { driver: string },
78
+ })
79
+ this.drivers.set(key, driver)
80
+ return driver
81
+ }
82
+
83
+ /**
84
+ * Register a driver factory. Adapter packages call this from
85
+ * their ServiceProvider's `register()` step.
86
+ */
87
+ extend(driverName: string, factory: InstantDriverFactory): void {
88
+ this.extensions.set(driverName, factory)
89
+ }
90
+
91
+ /** Hand-wire a driver instance under an app-chosen name (tests / one-offs). */
92
+ useDriver(instanceName: string, driver: InstantDriver): void {
93
+ this.drivers.set(instanceName, driver)
94
+ }
95
+
96
+ // ─── Convenience delegates to the default driver ─────────────────────
97
+
98
+ send(to: string, message: OutgoingMessage): Promise<SendResult> {
99
+ return this.use().send(to, message)
100
+ }
101
+
102
+ // ─── Webhook routing ─────────────────────────────────────────────────
103
+
104
+ /** Verify a webhook signature using the named provider's driver. */
105
+ verify(provider: string, rawBody: string, signature: string | null | undefined): boolean {
106
+ return this.use(provider).webhook.verifySignature(rawBody, signature)
107
+ }
108
+
109
+ /** Parse a verified raw webhook body into the framework's normalized event union. */
110
+ parseWebhook(provider: string, rawBody: string): WebhookEvent[] {
111
+ return this.use(provider).webhook.parse(rawBody)
112
+ }
113
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `InstantProvider` — `ServiceProvider` that wires `InstantManager`
3
+ * into the container from `config.instant`.
4
+ *
5
+ * Adapter packages register their drivers separately via their
6
+ * own ServiceProvider (e.g. `LineInstantProvider`). Apps list
7
+ * the adapter providers AFTER `InstantProvider` in
8
+ * `bootstrap/providers.ts` — `register()` runs in declaration
9
+ * order, then `boot()` runs in the same order. Adapter
10
+ * `register()` calls `manager.extend(driver, factory)`; this
11
+ * provider's `boot()` eagerly resolves so config errors surface
12
+ * at startup.
13
+ */
14
+
15
+ import { type Application, ConfigRepository, ServiceProvider } from '@strav/kernel'
16
+ import { InstantConfigError } from './errors.ts'
17
+ import { InstantManager } from './instant_manager.ts'
18
+ import type { InstantConfig } from './types.ts'
19
+
20
+ export class InstantProvider extends ServiceProvider {
21
+ override readonly name = 'instant'
22
+ override readonly dependencies = ['config']
23
+
24
+ override register(app: Application): void {
25
+ app.singleton(InstantManager, (c) => {
26
+ const raw = c.resolve(ConfigRepository).get('instant') as InstantConfig | undefined
27
+ if (!raw) {
28
+ throw new InstantConfigError(
29
+ 'InstantProvider: `config.instant` is missing. Add `config/instant.ts` with at least one provider.',
30
+ )
31
+ }
32
+ if (!raw.providers || Object.keys(raw.providers).length === 0) {
33
+ throw new InstantConfigError(
34
+ 'InstantProvider: `config.instant.providers` is empty. Configure at least one provider.',
35
+ )
36
+ }
37
+ return new InstantManager({ config: raw })
38
+ })
39
+ }
40
+
41
+ override boot(app: Application): void {
42
+ // Force-resolve so config errors surface at boot, not on first send().
43
+ app.resolve(InstantManager)
44
+ }
45
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tiny `fetch` wrapper that returns parsed JSON or throws
3
+ * `InstantProviderError` with the provider/operation/status
4
+ * stamped on it. Used by every driver so vendor REST failures
5
+ * surface with the same shape regardless of which messenger
6
+ * tripped.
7
+ *
8
+ * The body comes back as `T` without runtime validation —
9
+ * callers know the endpoint shape and the framework's error
10
+ * handler is happy with whatever shape `.cause` carries.
11
+ */
12
+
13
+ import { InstantProviderError } from '../errors.ts'
14
+
15
+ export interface FetchJsonOptions {
16
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
17
+ headers?: Record<string, string>
18
+ body?: string | Uint8Array | FormData
19
+ provider: string
20
+ operation: string
21
+ }
22
+
23
+ export async function fetchJson<T>(url: string, options: FetchJsonOptions): Promise<T> {
24
+ const { provider, operation, method = 'GET', headers, body } = options
25
+ let response: Response
26
+ try {
27
+ response = await fetch(url, { method, headers, body })
28
+ } catch (cause) {
29
+ throw new InstantProviderError(`${provider} ${operation} request failed`, {
30
+ provider,
31
+ operation,
32
+ cause,
33
+ })
34
+ }
35
+ const text = await response.text()
36
+ if (!response.ok) {
37
+ throw new InstantProviderError(
38
+ `${provider} ${operation} returned HTTP ${response.status}`,
39
+ {
40
+ provider,
41
+ operation,
42
+ status: 502,
43
+ context: { httpStatus: response.status, body: text.slice(0, 1024) },
44
+ },
45
+ )
46
+ }
47
+ if (text.length === 0) return undefined as T
48
+ try {
49
+ return JSON.parse(text) as T
50
+ } catch (cause) {
51
+ throw new InstantProviderError(`${provider} ${operation} returned non-JSON body`, {
52
+ provider,
53
+ operation,
54
+ cause,
55
+ context: { body: text.slice(0, 256) },
56
+ })
57
+ }
58
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Thin typed wrapper around `graph.facebook.com/v<x>`.
3
+ * Shared by WhatsApp Cloud and Messenger Send API.
4
+ *
5
+ * Operations stamp `provider` + `operation` onto the
6
+ * `InstantProviderError` thrown by `fetchJson` so callers
7
+ * don't need to wrap again.
8
+ */
9
+
10
+ import { fetchJson } from '../fetch_json.ts'
11
+
12
+ export interface MetaGraphClientOptions {
13
+ accessToken: string
14
+ apiVersion?: string
15
+ provider: string
16
+ }
17
+
18
+ export class MetaGraphClient {
19
+ private readonly base: string
20
+ private readonly accessToken: string
21
+ private readonly provider: string
22
+
23
+ constructor(options: MetaGraphClientOptions) {
24
+ const version = options.apiVersion ?? 'v20.0'
25
+ this.base = `https://graph.facebook.com/${version}`
26
+ this.accessToken = options.accessToken
27
+ this.provider = options.provider
28
+ }
29
+
30
+ post<T>(path: string, body: unknown, operation: string): Promise<T> {
31
+ return fetchJson<T>(`${this.base}${path}`, {
32
+ method: 'POST',
33
+ headers: {
34
+ Authorization: `Bearer ${this.accessToken}`,
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify(body),
38
+ provider: this.provider,
39
+ operation,
40
+ })
41
+ }
42
+
43
+ get<T>(path: string, operation: string): Promise<T> {
44
+ return fetchJson<T>(`${this.base}${path}`, {
45
+ method: 'GET',
46
+ headers: { Authorization: `Bearer ${this.accessToken}` },
47
+ provider: this.provider,
48
+ operation,
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Meta's `X-Hub-Signature-256` verifier — used by both WhatsApp
3
+ * Cloud and Messenger. The header is `sha256=<hex>` of an
4
+ * HMAC-SHA256 over the raw request body keyed on the App
5
+ * Secret. Compared in constant time to defeat timing oracles.
6
+ */
7
+
8
+ import { createHmac, timingSafeEqual } from 'node:crypto'
9
+
10
+ export function verifyMetaSignature(
11
+ rawBody: string,
12
+ header: string | null | undefined,
13
+ appSecret: string,
14
+ ): boolean {
15
+ if (!header) return false
16
+ const prefix = 'sha256='
17
+ if (!header.startsWith(prefix)) return false
18
+ const provided = header.slice(prefix.length)
19
+ const expected = createHmac('sha256', appSecret).update(rawBody).digest('hex')
20
+ if (provided.length !== expected.length) return false
21
+ return timingSafeEqual(Buffer.from(provided, 'hex'), Buffer.from(expected, 'hex'))
22
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Meta's GET-handshake responder. Used by WhatsApp Cloud and
3
+ * Messenger Send API webhook verification.
4
+ *
5
+ * The receiver passes the request query params; the driver
6
+ * returns the challenge string to echo back when the verify
7
+ * token matches, or `null` to reject.
8
+ */
9
+
10
+ export function verifyMetaChallenge(
11
+ params: Record<string, string | undefined>,
12
+ verifyToken: string,
13
+ ): string | null {
14
+ const mode = params['hub.mode']
15
+ const token = params['hub.verify_token']
16
+ const challenge = params['hub.challenge']
17
+ if (mode === 'subscribe' && token === verifyToken && challenge) return challenge
18
+ return null
19
+ }
package/src/message.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `OutgoingMessage` — the lowest-common-denominator wire shape the
3
+ * `InstantManager` exposes for portable sends. Apps that target
4
+ * multiple providers stick to these fields; apps that need
5
+ * provider-specific richness (LINE Flex, WhatsApp templates,
6
+ * Messenger generic templates) drop down to `raw` or call into the
7
+ * subpath driver directly.
8
+ *
9
+ * Every field is optional so the same shape can carry "just text",
10
+ * "text + image", "quick replies only", etc. Drivers throw
11
+ * `ProviderUnsupportedError` for fields they can't fulfil — apps
12
+ * gate on `driver.capabilities` to branch ahead of the call.
13
+ */
14
+
15
+ export interface OutgoingMessage {
16
+ /** Plain-text body. Required for `send.text` calls. */
17
+ text?: string
18
+ /** Media + structured content attached to the message. */
19
+ attachments?: Attachment[]
20
+ /** Buttons rendered alongside the message (LINE quick reply, WhatsApp buttons, Messenger quick replies). */
21
+ quickReplies?: QuickReply[]
22
+ /**
23
+ * Provider-native message object. When set, the driver
24
+ * forwards it verbatim and ignores the LCD fields above.
25
+ * Use this when reaching for provider-specific richness
26
+ * (e.g. a `FlexBubble` from `@strav/instant/line`).
27
+ */
28
+ raw?: unknown
29
+ }
30
+
31
+ export type Attachment =
32
+ | { type: 'image'; url: string; previewUrl?: string }
33
+ | { type: 'video'; url: string; previewUrl?: string; durationMs?: number }
34
+ | { type: 'audio'; url: string; durationMs?: number }
35
+ | { type: 'file'; url: string; fileName?: string; sizeBytes?: number }
36
+ | { type: 'location'; latitude: number; longitude: number; title?: string; address?: string }
37
+ | { type: 'sticker'; packageId: string; stickerId: string }
38
+
39
+ export interface QuickReply {
40
+ label: string
41
+ /** Action fired when the reply is tapped. */
42
+ action:
43
+ | { type: 'message'; text: string }
44
+ | { type: 'postback'; data: string; displayText?: string }
45
+ | { type: 'uri'; uri: string }
46
+ /** Icon shown next to the label, where the provider supports it. */
47
+ iconUrl?: string
48
+ }
49
+
50
+ /**
51
+ * `SendResult` — what every send call returns. Drivers populate
52
+ * `messageId` when the provider hands one back (LINE's
53
+ * `x-line-request-id`, WhatsApp's `messages[0].id`); some only
54
+ * confirm acceptance and leave it undefined.
55
+ */
56
+ export interface SendResult {
57
+ /** Driver name (`'line'`, `'whatsapp'`, …). */
58
+ provider: string
59
+ /** Whether the upstream API accepted the send. */
60
+ accepted: boolean
61
+ /** Provider-issued reference, when one exists. */
62
+ messageId?: string
63
+ /** Raw provider response for advanced inspection. */
64
+ raw?: unknown
65
+ }
package/src/types.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@strav/instant` — runtime config shapes.
3
+ *
4
+ * `InstantConfig` is the `config.instant` shape apps declare in
5
+ * `config/instant.ts`. Multi-provider by default (apps could run
6
+ * one LINE bot for support, another for marketing). `providers`
7
+ * is keyed by app-chosen instance name; each entry carries a
8
+ * `driver` discriminator the framework resolves to a concrete
9
+ * `InstantDriver`. Apps with a single provider just register one
10
+ * entry.
11
+ *
12
+ * `ProviderConfig` is free-form so each adapter owns its own
13
+ * config shape (`LineProviderConfig` etc.) without forcing the
14
+ * core to know every vendor field.
15
+ */
16
+
17
+ export interface InstantConfig {
18
+ /** Key into `providers`. The default routing target for unqualified `instant.*` calls. */
19
+ default: string
20
+ providers: Record<string, ProviderConfig>
21
+ }
22
+
23
+ export interface ProviderConfig {
24
+ /**
25
+ * Driver identifier — must match a name registered via
26
+ * `manager.extend(name, factory)` (typically by an adapter
27
+ * ServiceProvider).
28
+ */
29
+ driver: string
30
+ /** Driver-specific fields — see each adapter's `*Config`. */
31
+ [key: string]: unknown
32
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * `WebhookEvent` — the normalized inbound event union the manager
3
+ * exposes to apps. Drivers convert their provider-native event
4
+ * shape (LINE event, WhatsApp change, Messenger entry) into one
5
+ * of these variants. Events the driver can't normalize map to
6
+ * `unknown` and surface via `raw` so apps that opt into raw
7
+ * handling don't lose anything.
8
+ *
9
+ * Every variant carries `provider`, `userId` (the opaque
10
+ * recipient identifier — LINE `userId`, WhatsApp phone, Messenger
11
+ * PSID), `timestamp`, and `raw` so apps can drop down when the
12
+ * normalized shape isn't enough.
13
+ */
14
+
15
+ export interface WebhookEventBase {
16
+ /** Driver name. */
17
+ provider: string
18
+ /** Opaque user identifier from the provider. */
19
+ userId: string
20
+ /** Provider-issued event timestamp (Date). */
21
+ timestamp: Date
22
+ /** Conversation source — direct chat (`user`), group (`group`), room (`room`), etc. */
23
+ source: 'user' | 'group' | 'room' | 'unknown'
24
+ /** Group / room id when `source !== 'user'`. */
25
+ sourceId?: string
26
+ /** Reply token (LINE) or message context for short-lived reply windows. */
27
+ replyToken?: string
28
+ /** Provider-native event payload. */
29
+ raw: unknown
30
+ }
31
+
32
+ export interface TextMessageEvent extends WebhookEventBase {
33
+ type: 'message.text'
34
+ text: string
35
+ /** Provider-issued message id. */
36
+ messageId: string
37
+ }
38
+
39
+ export interface MediaMessageEvent extends WebhookEventBase {
40
+ type: 'message.image' | 'message.video' | 'message.audio' | 'message.file'
41
+ messageId: string
42
+ /** When the provider hands back content directly (vs. needing a fetch). */
43
+ contentUrl?: string
44
+ }
45
+
46
+ export interface LocationMessageEvent extends WebhookEventBase {
47
+ type: 'message.location'
48
+ messageId: string
49
+ latitude: number
50
+ longitude: number
51
+ title?: string
52
+ address?: string
53
+ }
54
+
55
+ export interface StickerMessageEvent extends WebhookEventBase {
56
+ type: 'message.sticker'
57
+ messageId: string
58
+ packageId?: string
59
+ stickerId?: string
60
+ }
61
+
62
+ export interface PostbackEvent extends WebhookEventBase {
63
+ type: 'postback'
64
+ data: string
65
+ }
66
+
67
+ export interface FollowEvent extends WebhookEventBase {
68
+ type: 'follow' | 'unfollow'
69
+ }
70
+
71
+ export interface JoinEvent extends WebhookEventBase {
72
+ type: 'join' | 'leave'
73
+ }
74
+
75
+ export interface BeaconEvent extends WebhookEventBase {
76
+ type: 'beacon'
77
+ beacon: { hwid: string; kind: 'enter' | 'banner' | 'stay'; dm?: string }
78
+ }
79
+
80
+ export interface UnknownEvent extends WebhookEventBase {
81
+ type: 'unknown'
82
+ }
83
+
84
+ export type WebhookEvent =
85
+ | TextMessageEvent
86
+ | MediaMessageEvent
87
+ | LocationMessageEvent
88
+ | StickerMessageEvent
89
+ | PostbackEvent
90
+ | FollowEvent
91
+ | JoinEvent
92
+ | BeaconEvent
93
+ | UnknownEvent