spectrum-ts 4.1.0 → 4.2.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/authoring.js +3 -3
- package/dist/elysia.d.ts +94 -0
- package/dist/elysia.js +15 -0
- package/dist/express.d.ts +62 -0
- package/dist/express.js +19 -0
- package/dist/hono.d.ts +64 -0
- package/dist/hono.js +11 -0
- package/dist/index.d.ts +24 -10
- package/dist/index.js +425 -35
- package/dist/providers/imessage/index.d.ts +4 -4
- package/dist/providers/imessage/index.js +3 -3
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +15 -15
- package/dist/providers/telegram/index.d.ts +1 -1
- package/dist/providers/telegram/index.js +2 -2
- package/dist/providers/terminal/index.js +2 -2
- package/dist/providers/whatsapp-business/index.js +3 -3
- package/dist/{types-BIta6Kxi.d.ts → types-ZgFTj5hJ.d.ts} +10 -5
- package/package.json +25 -1
- package/dist/{chunk-IM5ADDZS.js → chunk-ARL2NOBO.js} +3 -3
- package/dist/{chunk-3KWFP4L2.js → chunk-DMPDLSFU.js} +3 -3
- package/dist/{chunk-U3QQ56YZ.js → chunk-N6THJDZV.js} +3 -3
- package/dist/{chunk-DMT6BFJV.js → chunk-NLMQ75LH.js} +7 -7
package/dist/authoring.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
asVoice
|
|
4
|
+
} from "./chunk-FAIFTUV2.js";
|
|
2
5
|
import {
|
|
3
6
|
asRichlink
|
|
4
7
|
} from "./chunk-ZR3TKZMT.js";
|
|
5
8
|
import {
|
|
6
9
|
asGroup
|
|
7
10
|
} from "./chunk-LZXPLXZF.js";
|
|
8
|
-
import {
|
|
9
|
-
asVoice
|
|
10
|
-
} from "./chunk-FAIFTUV2.js";
|
|
11
11
|
import {
|
|
12
12
|
asPoll,
|
|
13
13
|
asPollOption
|
package/dist/elysia.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { W as WebhookHandler } from './types-ZgFTj5hJ.js';
|
|
3
|
+
export { M as Message, S as Space } from './types-CyfLJXgu.js';
|
|
4
|
+
import 'hotscript';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The minimal structural surface of a Spectrum instance the plugin needs. Kept
|
|
9
|
+
* structural (rather than importing the generic `SpectrumInstance<Providers>`)
|
|
10
|
+
* so the plugin stays decoupled from provider typing; a real instance is
|
|
11
|
+
* assignable via its Web `Request` webhook overload.
|
|
12
|
+
*/
|
|
13
|
+
interface WebhookReceiver {
|
|
14
|
+
webhook(request: Request, handler: WebhookHandler): Promise<Response>;
|
|
15
|
+
}
|
|
16
|
+
interface SpectrumPluginOptions {
|
|
17
|
+
/** The Spectrum instance returned by `await Spectrum({...})`. */
|
|
18
|
+
app: WebhookReceiver;
|
|
19
|
+
/**
|
|
20
|
+
* Invoked once per inbound message, fire-and-forget after the response — the
|
|
21
|
+
* same `(space, message)` contract as `app.webhook(request, handler)`. Covers
|
|
22
|
+
* both native Spectrum webhooks and fusor webhooks identically.
|
|
23
|
+
*/
|
|
24
|
+
onMessage: WebhookHandler;
|
|
25
|
+
/**
|
|
26
|
+
* Route the webhook is mounted on.
|
|
27
|
+
*
|
|
28
|
+
* @default "/spectrum/webhook"
|
|
29
|
+
*/
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mount a Spectrum webhook endpoint on an Elysia app.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { Elysia } from "elysia";
|
|
38
|
+
* import { Spectrum } from "spectrum-ts";
|
|
39
|
+
* import { spectrum } from "spectrum-ts/elysia";
|
|
40
|
+
*
|
|
41
|
+
* const app = await Spectrum({ ..., webhookSecret: process.env.SPECTRUM_WEBHOOK_SECRET });
|
|
42
|
+
*
|
|
43
|
+
* new Elysia()
|
|
44
|
+
* .use(spectrum({
|
|
45
|
+
* app,
|
|
46
|
+
* onMessage: async (space, message) => {
|
|
47
|
+
* if (message.content.type === "text") await space.send(`echo: ${message.content.text}`);
|
|
48
|
+
* },
|
|
49
|
+
* }))
|
|
50
|
+
* .listen(3000);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function spectrum(options: SpectrumPluginOptions): Elysia<"", {
|
|
54
|
+
decorator: {};
|
|
55
|
+
store: {};
|
|
56
|
+
derive: {};
|
|
57
|
+
resolve: {};
|
|
58
|
+
}, {
|
|
59
|
+
typebox: {};
|
|
60
|
+
error: {};
|
|
61
|
+
}, {
|
|
62
|
+
schema: {};
|
|
63
|
+
standaloneSchema: {};
|
|
64
|
+
macro: {};
|
|
65
|
+
macroFn: {};
|
|
66
|
+
parser: {};
|
|
67
|
+
response: {};
|
|
68
|
+
}, {
|
|
69
|
+
[x: string]: {
|
|
70
|
+
post: {
|
|
71
|
+
body: unknown;
|
|
72
|
+
params: {};
|
|
73
|
+
query: unknown;
|
|
74
|
+
headers: unknown;
|
|
75
|
+
response: {
|
|
76
|
+
200: Response;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}, {
|
|
81
|
+
derive: {};
|
|
82
|
+
resolve: {};
|
|
83
|
+
schema: {};
|
|
84
|
+
standaloneSchema: {};
|
|
85
|
+
response: {};
|
|
86
|
+
}, {
|
|
87
|
+
derive: {};
|
|
88
|
+
resolve: {};
|
|
89
|
+
schema: {};
|
|
90
|
+
standaloneSchema: {};
|
|
91
|
+
response: {};
|
|
92
|
+
}>;
|
|
93
|
+
|
|
94
|
+
export { type SpectrumPluginOptions, WebhookHandler, spectrum };
|
package/dist/elysia.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/elysia.ts
|
|
4
|
+
import { Elysia } from "elysia";
|
|
5
|
+
function spectrum(options) {
|
|
6
|
+
const { app, onMessage, path = "/spectrum/webhook" } = options;
|
|
7
|
+
return new Elysia({ name: "spectrum-webhook", seed: path }).post(
|
|
8
|
+
path,
|
|
9
|
+
({ request }) => app.webhook(request, onMessage),
|
|
10
|
+
{ parse: "none" }
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
spectrum
|
|
15
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { W as WebhookHandler } from './types-ZgFTj5hJ.js';
|
|
3
|
+
export { M as Message, S as Space } from './types-CyfLJXgu.js';
|
|
4
|
+
import 'hotscript';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The minimal structural surface of a Spectrum instance the plugin needs. Kept
|
|
9
|
+
* structural (rather than importing the generic `SpectrumInstance<Providers>`)
|
|
10
|
+
* so the plugin stays decoupled from provider typing; a real instance is
|
|
11
|
+
* assignable via its raw (`{ body, headers }`) webhook overload.
|
|
12
|
+
*/
|
|
13
|
+
interface WebhookReceiver {
|
|
14
|
+
webhook(request: {
|
|
15
|
+
body: Uint8Array | ArrayBuffer;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
}, handler: WebhookHandler): Promise<{
|
|
18
|
+
body: Uint8Array;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
status: number;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
interface SpectrumPluginOptions {
|
|
24
|
+
/** The Spectrum instance returned by `await Spectrum({...})`. */
|
|
25
|
+
app: WebhookReceiver;
|
|
26
|
+
/**
|
|
27
|
+
* Invoked once per inbound message, fire-and-forget after the response — the
|
|
28
|
+
* same `(space, message)` contract as `app.webhook(request, handler)`. Covers
|
|
29
|
+
* both native Spectrum webhooks and fusor webhooks identically.
|
|
30
|
+
*/
|
|
31
|
+
onMessage: WebhookHandler;
|
|
32
|
+
/**
|
|
33
|
+
* Route the webhook is mounted on.
|
|
34
|
+
*
|
|
35
|
+
* @default "/spectrum/webhook"
|
|
36
|
+
*/
|
|
37
|
+
path?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Mount a Spectrum webhook endpoint on an Express app.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import express from "express";
|
|
45
|
+
* import { Spectrum } from "spectrum-ts";
|
|
46
|
+
* import { spectrum } from "spectrum-ts/express";
|
|
47
|
+
*
|
|
48
|
+
* const app = await Spectrum({ ..., webhookSecret: process.env.SPECTRUM_WEBHOOK_SECRET });
|
|
49
|
+
*
|
|
50
|
+
* const server = express();
|
|
51
|
+
* server.use(spectrum({ // mount before any global express.json()
|
|
52
|
+
* app,
|
|
53
|
+
* onMessage: async (space, message) => {
|
|
54
|
+
* if (message.content.type === "text") await space.send(`echo: ${message.content.text}`);
|
|
55
|
+
* },
|
|
56
|
+
* }));
|
|
57
|
+
* server.listen(3000);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function spectrum(options: SpectrumPluginOptions): Router;
|
|
61
|
+
|
|
62
|
+
export { type SpectrumPluginOptions, WebhookHandler, spectrum };
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/express.ts
|
|
4
|
+
import express from "express";
|
|
5
|
+
function spectrum(options) {
|
|
6
|
+
const { app, onMessage, path = "/spectrum/webhook" } = options;
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
router.post(path, express.raw({ type: "*/*" }), async (req, res) => {
|
|
9
|
+
const result = await app.webhook(
|
|
10
|
+
{ body: req.body, headers: req.headers },
|
|
11
|
+
onMessage
|
|
12
|
+
);
|
|
13
|
+
res.status(result.status).set(result.headers).send(Buffer.from(result.body));
|
|
14
|
+
});
|
|
15
|
+
return router;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
spectrum
|
|
19
|
+
};
|
package/dist/hono.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as hono_hono_base from 'hono/hono-base';
|
|
2
|
+
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
3
|
+
import * as hono_types from 'hono/types';
|
|
4
|
+
import { W as WebhookHandler } from './types-ZgFTj5hJ.js';
|
|
5
|
+
export { M as Message, S as Space } from './types-CyfLJXgu.js';
|
|
6
|
+
import 'hotscript';
|
|
7
|
+
import 'zod';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The minimal structural surface of a Spectrum instance the plugin needs. Kept
|
|
11
|
+
* structural (rather than importing the generic `SpectrumInstance<Providers>`)
|
|
12
|
+
* so the plugin stays decoupled from provider typing; a real instance is
|
|
13
|
+
* assignable via its Web `Request` webhook overload.
|
|
14
|
+
*/
|
|
15
|
+
interface WebhookReceiver {
|
|
16
|
+
webhook(request: Request, handler: WebhookHandler): Promise<Response>;
|
|
17
|
+
}
|
|
18
|
+
interface SpectrumPluginOptions {
|
|
19
|
+
/** The Spectrum instance returned by `await Spectrum({...})`. */
|
|
20
|
+
app: WebhookReceiver;
|
|
21
|
+
/**
|
|
22
|
+
* Invoked once per inbound message, fire-and-forget after the response — the
|
|
23
|
+
* same `(space, message)` contract as `app.webhook(request, handler)`. Covers
|
|
24
|
+
* both native Spectrum webhooks and fusor webhooks identically.
|
|
25
|
+
*/
|
|
26
|
+
onMessage: WebhookHandler;
|
|
27
|
+
/**
|
|
28
|
+
* Route the webhook is mounted on.
|
|
29
|
+
*
|
|
30
|
+
* @default "/spectrum/webhook"
|
|
31
|
+
*/
|
|
32
|
+
path?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mount a Spectrum webhook endpoint on a Hono app.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { Hono } from "hono";
|
|
40
|
+
* import { Spectrum } from "spectrum-ts";
|
|
41
|
+
* import { spectrum } from "spectrum-ts/hono";
|
|
42
|
+
*
|
|
43
|
+
* const app = await Spectrum({ ..., webhookSecret: process.env.SPECTRUM_WEBHOOK_SECRET });
|
|
44
|
+
*
|
|
45
|
+
* const server = new Hono().route("/", spectrum({
|
|
46
|
+
* app,
|
|
47
|
+
* onMessage: async (space, message) => {
|
|
48
|
+
* if (message.content.type === "text") await space.send(`echo: ${message.content.text}`);
|
|
49
|
+
* },
|
|
50
|
+
* }));
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function spectrum(options: SpectrumPluginOptions): hono_hono_base.HonoBase<hono_types.BlankEnv, {
|
|
54
|
+
[x: string]: {
|
|
55
|
+
$post: {
|
|
56
|
+
input: {};
|
|
57
|
+
output: {};
|
|
58
|
+
outputFormat: string;
|
|
59
|
+
status: hono_utils_http_status.StatusCode;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}, "/", string>;
|
|
63
|
+
|
|
64
|
+
export { type SpectrumPluginOptions, WebhookHandler, spectrum };
|
package/dist/hono.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/hono.ts
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
function spectrum(options) {
|
|
6
|
+
const { app, onMessage, path = "/spectrum/webhook" } = options;
|
|
7
|
+
return new Hono().post(path, (c) => app.webhook(c.req.raw, onMessage));
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
spectrum
|
|
11
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { C as ContentBuilder, M as Message, U as User, S as Space, h as ContentI
|
|
|
6
6
|
export { p as AnyPlatformDef, B as Broadcaster, q as CloudPlatform, D as DedicatedTokenData, F as FusorTokenData, r as ImessageInfoData, s as ManagedStream, t as PlatformInstance, u as PlatformMessage, v as PlatformRuntime, w as PlatformSpace, x as PlatformStatus, y as PlatformUser, z as PlatformsData, G as ProjectProfile, R as Reaction, H as ReactionBuilder, a as SchemaMessage, J as SharedTokenData, K as SlackTeamMeta, L as SlackTokenData, N as SpaceNamespace, O as SpectrumCloudError, Q as SubscriptionData, T as SubscriptionStatus, V as TokenData, W as broadcast, X as cloud, Y as mergeStreams, Z as reaction, _ as stream } from './types-CyfLJXgu.js';
|
|
7
7
|
import { S as StreamTextSource, T as TextStreamOptions, C as ContactInput, a as Contact } from './authoring-b9AhXgPI.js';
|
|
8
8
|
export { b as ContactAddress, c as ContactDetails, d as ContactEmail, e as ContactName, f as ContactOrg, g as ContactPhone, D as DeltaExtractor, G as Group, P as Poll, h as PollChoice, i as PollChoiceInput, j as PollOption, R as Richlink, k as StreamText, V as Voice, l as contact, m as custom, n as group, o as option, p as poll, r as richlink, t as text, v as voice } from './authoring-b9AhXgPI.js';
|
|
9
|
-
import { a as FusorVerify, F as FusorClient, b as FusorMessages, W as WebhookHandler, c as WebhookRawRequest, d as WebhookRawResult } from './types-
|
|
10
|
-
export { e as FusorEvent, f as FusorMessagesCtx, g as FusorMessagesReturn, h as FusorReply, i as FusorRespond, j as FusorVerifyRequest, k as fusorEvent, l as isFusorEvent } from './types-
|
|
9
|
+
import { a as FusorVerify, F as FusorClient, b as FusorMessages, W as WebhookHandler, c as WebhookRawRequest, d as WebhookRawResult } from './types-ZgFTj5hJ.js';
|
|
10
|
+
export { e as FusorEvent, f as FusorMessagesCtx, g as FusorMessagesReturn, h as FusorReply, i as FusorRespond, j as FusorVerifyRequest, k as fusorEvent, l as isFusorEvent } from './types-ZgFTj5hJ.js';
|
|
11
11
|
import 'hotscript';
|
|
12
12
|
import 'vcf';
|
|
13
13
|
|
|
@@ -2757,12 +2757,23 @@ type SpectrumInstance<Providers extends PlatformProviderConfig[] = PlatformProvi
|
|
|
2757
2757
|
edit(message: Message, newContent: ContentInput): Promise<void>;
|
|
2758
2758
|
responding<T>(space: Space, fn: () => T | Promise<T>): Promise<T>;
|
|
2759
2759
|
/**
|
|
2760
|
-
* Handle one inbound
|
|
2761
|
-
*
|
|
2762
|
-
*
|
|
2763
|
-
*
|
|
2764
|
-
*
|
|
2765
|
-
*
|
|
2760
|
+
* Handle one inbound webhook delivery. Call this from your HTTP server's
|
|
2761
|
+
* POST route — it auto-detects which of the two Spectrum webhook formats the
|
|
2762
|
+
* request carries and routes accordingly:
|
|
2763
|
+
*
|
|
2764
|
+
* - **Native Spectrum webhook** (the body is normalized JSON): Spectrum Cloud
|
|
2765
|
+
* POSTs already-normalized, HMAC-signed JSON. The signature is verified
|
|
2766
|
+
* against `Spectrum({ webhookSecret })` (a bad signature → 401), the slim
|
|
2767
|
+
* payload is deserialized into `[space, message]`, and a `200` is returned.
|
|
2768
|
+
* Works without any fusor provider configured.
|
|
2769
|
+
* - **Fusor webhook** (the body is a protobuf envelope): a protobuf wrapping a
|
|
2770
|
+
* raw provider request is decoded and routed to the matching provider's
|
|
2771
|
+
* verify + message pipeline; the HTTP response is that platform's
|
|
2772
|
+
* `respond()` reply (including protocol echoes like Slack
|
|
2773
|
+
* `url_verification`), computed synchronously and returned immediately.
|
|
2774
|
+
*
|
|
2775
|
+
* Detection is by payload shape, not headers — Spectrum signs both kinds with
|
|
2776
|
+
* `X-Spectrum-Signature`, so the header can't discriminate.
|
|
2766
2777
|
*
|
|
2767
2778
|
* `handler` is invoked once per resolved message **fire-and-forget** — it is
|
|
2768
2779
|
* dispatched after the response is computed and is NOT awaited, so its
|
|
@@ -2772,8 +2783,9 @@ type SpectrumInstance<Providers extends PlatformProviderConfig[] = PlatformProvi
|
|
|
2772
2783
|
* and process in a separate worker).
|
|
2773
2784
|
*
|
|
2774
2785
|
* Stateless and request-scoped: it does NOT feed `spectrum.messages`, and it
|
|
2775
|
-
* never opens the
|
|
2776
|
-
* should dedupe on `message`/the event id for exactly-once side
|
|
2786
|
+
* never opens the streaming connection. Both formats deliver at-least-once,
|
|
2787
|
+
* so `handler` should dedupe on `message`/the event id for exactly-once side
|
|
2788
|
+
* effects.
|
|
2777
2789
|
*/
|
|
2778
2790
|
webhook(request: Request, handler: WebhookHandler): Promise<Response>;
|
|
2779
2791
|
webhook(request: WebhookRawRequest, handler: WebhookHandler): Promise<WebhookRawResult>;
|
|
@@ -2800,6 +2812,7 @@ declare function Spectrum<const Providers extends PlatformProviderConfig[]>(opti
|
|
|
2800
2812
|
providers: [...Providers];
|
|
2801
2813
|
options?: SpectrumOptions;
|
|
2802
2814
|
telemetry?: boolean;
|
|
2815
|
+
webhookSecret?: string;
|
|
2803
2816
|
}): Promise<SpectrumInstance<Providers> & {
|
|
2804
2817
|
readonly config: ProjectData;
|
|
2805
2818
|
}>;
|
|
@@ -2809,6 +2822,7 @@ declare function Spectrum<const Providers extends PlatformProviderConfig[]>(opti
|
|
|
2809
2822
|
providers: [...Providers];
|
|
2810
2823
|
options?: SpectrumOptions;
|
|
2811
2824
|
telemetry?: boolean;
|
|
2825
|
+
webhookSecret?: string;
|
|
2812
2826
|
}): Promise<SpectrumInstance<Providers>>;
|
|
2813
2827
|
|
|
2814
2828
|
type UnsupportedKind = "content" | "action";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
richlink
|
|
4
|
-
} from "./chunk-ZR3TKZMT.js";
|
|
5
2
|
import {
|
|
6
3
|
FUSOR_MESSAGES_CHANNEL,
|
|
7
4
|
fusor,
|
|
@@ -9,33 +6,38 @@ import {
|
|
|
9
6
|
isFusorClient,
|
|
10
7
|
isFusorEvent
|
|
11
8
|
} from "./chunk-34FQGGD7.js";
|
|
12
|
-
import {
|
|
13
|
-
group
|
|
14
|
-
} from "./chunk-LZXPLXZF.js";
|
|
15
9
|
import {
|
|
16
10
|
voice
|
|
17
11
|
} from "./chunk-FAIFTUV2.js";
|
|
12
|
+
import {
|
|
13
|
+
asRichlink,
|
|
14
|
+
richlink
|
|
15
|
+
} from "./chunk-ZR3TKZMT.js";
|
|
16
|
+
import {
|
|
17
|
+
group
|
|
18
|
+
} from "./chunk-LZXPLXZF.js";
|
|
18
19
|
import {
|
|
19
20
|
option,
|
|
20
21
|
poll
|
|
21
22
|
} from "./chunk-2D27WW5B.js";
|
|
23
|
+
import {
|
|
24
|
+
asContact,
|
|
25
|
+
contact
|
|
26
|
+
} from "./chunk-A37PM5N2.js";
|
|
27
|
+
import {
|
|
28
|
+
fromVCard,
|
|
29
|
+
toVCard
|
|
30
|
+
} from "./chunk-6UZFVXQF.js";
|
|
22
31
|
import {
|
|
23
32
|
SpectrumCloudError,
|
|
24
33
|
cloud
|
|
25
34
|
} from "./chunk-3GEJYGZK.js";
|
|
26
|
-
import {
|
|
27
|
-
contact
|
|
28
|
-
} from "./chunk-A37PM5N2.js";
|
|
29
35
|
import {
|
|
30
36
|
broadcast,
|
|
31
37
|
createAsyncQueue,
|
|
32
38
|
mergeStreams,
|
|
33
39
|
stream
|
|
34
40
|
} from "./chunk-5XEFJBN2.js";
|
|
35
|
-
import {
|
|
36
|
-
fromVCard,
|
|
37
|
-
toVCard
|
|
38
|
-
} from "./chunk-6UZFVXQF.js";
|
|
39
41
|
import {
|
|
40
42
|
UnsupportedError,
|
|
41
43
|
avatar,
|
|
@@ -53,6 +55,8 @@ import {
|
|
|
53
55
|
wrapProviderMessage
|
|
54
56
|
} from "./chunk-B52VPQO3.js";
|
|
55
57
|
import {
|
|
58
|
+
asAttachment,
|
|
59
|
+
asCustom,
|
|
56
60
|
attachment,
|
|
57
61
|
custom,
|
|
58
62
|
reaction,
|
|
@@ -1996,7 +2000,7 @@ import {
|
|
|
1996
2000
|
withSpan
|
|
1997
2001
|
} from "@photon-ai/otel";
|
|
1998
2002
|
import { RawInboundEvent } from "@photon-ai/proto/photon/fusor/v1/inbound";
|
|
1999
|
-
import
|
|
2003
|
+
import z2 from "zod";
|
|
2000
2004
|
|
|
2001
2005
|
// src/build-env.ts
|
|
2002
2006
|
var SPECTRUM_SDK_VERSION = "local";
|
|
@@ -2707,28 +2711,284 @@ function createStore() {
|
|
|
2707
2711
|
};
|
|
2708
2712
|
}
|
|
2709
2713
|
|
|
2714
|
+
// src/webhook/deserialize.ts
|
|
2715
|
+
var MESSAGES_EVENT = "messages";
|
|
2716
|
+
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
2717
|
+
var DEFAULT_MIME_TYPE = "application/octet-stream";
|
|
2718
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
2719
|
+
var asString = (value) => typeof value === "string" ? value : "";
|
|
2720
|
+
var asOptionalDate = (value) => typeof value === "string" ? new Date(value) : void 0;
|
|
2721
|
+
function deserializeSpectrumMessage(envelope, ctx) {
|
|
2722
|
+
if (envelope.event !== MESSAGES_EVENT) {
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
const message = envelope.message;
|
|
2726
|
+
const platform = resolvePlatform(message);
|
|
2727
|
+
if (!platform) {
|
|
2728
|
+
return null;
|
|
2729
|
+
}
|
|
2730
|
+
const spaceRef = { ...message.space };
|
|
2731
|
+
return {
|
|
2732
|
+
platform,
|
|
2733
|
+
record: {
|
|
2734
|
+
id: message.id,
|
|
2735
|
+
direction: "inbound",
|
|
2736
|
+
content: deserializeContent(message.content, platform, spaceRef, ctx),
|
|
2737
|
+
space: spaceRef,
|
|
2738
|
+
sender: message.sender ? { ...message.sender } : void 0,
|
|
2739
|
+
timestamp: asOptionalDate(message.timestamp)
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
var resolvePlatform = (message) => message.platform ?? message.space.platform;
|
|
2744
|
+
var deserializeContent = (content, platform, spaceRef, ctx) => {
|
|
2745
|
+
try {
|
|
2746
|
+
return mapContent(content, platform, spaceRef, ctx);
|
|
2747
|
+
} catch {
|
|
2748
|
+
return asCustom(content);
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
var mapContent = (content, platform, spaceRef, ctx) => {
|
|
2752
|
+
const raw = content;
|
|
2753
|
+
switch (content.type) {
|
|
2754
|
+
case "text":
|
|
2755
|
+
return { type: "text", text: asString(raw.text) };
|
|
2756
|
+
case "richlink":
|
|
2757
|
+
return asRichlink({ url: asString(raw.url) });
|
|
2758
|
+
case "contact":
|
|
2759
|
+
return deserializeContact(raw);
|
|
2760
|
+
case "reaction":
|
|
2761
|
+
return deserializeReaction(raw, spaceRef);
|
|
2762
|
+
case "group":
|
|
2763
|
+
return deserializeGroup(raw, platform, spaceRef, ctx);
|
|
2764
|
+
case "attachment":
|
|
2765
|
+
return deserializeAttachment(raw, platform, spaceRef, ctx);
|
|
2766
|
+
default:
|
|
2767
|
+
return asCustom(content);
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
var deserializeAttachment = (raw, platform, spaceRef, ctx) => {
|
|
2771
|
+
const id = asString(raw.id);
|
|
2772
|
+
const bytes = ctx.resolveAttachment?.(platform, spaceRef, id);
|
|
2773
|
+
const unavailable = () => Promise.reject(
|
|
2774
|
+
UnsupportedError.action(
|
|
2775
|
+
"getAttachment",
|
|
2776
|
+
platform,
|
|
2777
|
+
`attachment "${id}" arrived without bytes over the Spectrum webhook and "${platform}" exposes no getAttachment`
|
|
2778
|
+
)
|
|
2779
|
+
);
|
|
2780
|
+
return asAttachment({
|
|
2781
|
+
id,
|
|
2782
|
+
name: asString(raw.name) || DEFAULT_ATTACHMENT_NAME,
|
|
2783
|
+
mimeType: asString(raw.mimeType) || DEFAULT_MIME_TYPE,
|
|
2784
|
+
size: typeof raw.size === "number" ? raw.size : void 0,
|
|
2785
|
+
read: bytes ? bytes.read : unavailable,
|
|
2786
|
+
stream: bytes?.stream
|
|
2787
|
+
});
|
|
2788
|
+
};
|
|
2789
|
+
var deserializeReaction = (raw, spaceRef) => (
|
|
2790
|
+
// `target` is a raw record; `wrapNestedContent` wraps it into a Message.
|
|
2791
|
+
{
|
|
2792
|
+
type: "reaction",
|
|
2793
|
+
emoji: asString(raw.emoji),
|
|
2794
|
+
target: buildTargetRecord(raw.target, spaceRef)
|
|
2795
|
+
}
|
|
2796
|
+
);
|
|
2797
|
+
var buildTargetRecord = (target, spaceRef) => {
|
|
2798
|
+
const ref = isRecord(target) ? target : {};
|
|
2799
|
+
return {
|
|
2800
|
+
id: asString(ref.id),
|
|
2801
|
+
// The target's full content is not delivered; the 80-char `contentPreview`
|
|
2802
|
+
// (text targets only) is the best available stand-in.
|
|
2803
|
+
content: { type: "text", text: asString(ref.contentPreview) },
|
|
2804
|
+
space: { ...spaceRef },
|
|
2805
|
+
sender: ref.sender ? { ...ref.sender } : void 0,
|
|
2806
|
+
timestamp: asOptionalDate(ref.timestamp)
|
|
2807
|
+
};
|
|
2808
|
+
};
|
|
2809
|
+
var deserializeGroup = (raw, platform, spaceRef, ctx) => {
|
|
2810
|
+
const rawItems = Array.isArray(raw.items) ? raw.items : [];
|
|
2811
|
+
return {
|
|
2812
|
+
type: "group",
|
|
2813
|
+
items: rawItems.map(
|
|
2814
|
+
(item) => buildItemRecord(item, platform, spaceRef, ctx)
|
|
2815
|
+
)
|
|
2816
|
+
};
|
|
2817
|
+
};
|
|
2818
|
+
var buildItemRecord = (item, platform, spaceRef, ctx) => {
|
|
2819
|
+
const record = isRecord(item) ? item : {};
|
|
2820
|
+
const itemSpace = isRecord(record.space) ? {
|
|
2821
|
+
...record.space,
|
|
2822
|
+
id: asString(record.space.id) || spaceRef.id
|
|
2823
|
+
} : spaceRef;
|
|
2824
|
+
const content = isRecord(record.content) ? deserializeContent(
|
|
2825
|
+
record.content,
|
|
2826
|
+
platform,
|
|
2827
|
+
itemSpace,
|
|
2828
|
+
ctx
|
|
2829
|
+
) : asCustom(record.content);
|
|
2830
|
+
return {
|
|
2831
|
+
id: asString(record.id),
|
|
2832
|
+
content,
|
|
2833
|
+
space: itemSpace,
|
|
2834
|
+
sender: isRecord(record.sender) ? { ...record.sender, id: asString(record.sender.id) } : void 0,
|
|
2835
|
+
timestamp: asOptionalDate(record.timestamp)
|
|
2836
|
+
};
|
|
2837
|
+
};
|
|
2838
|
+
var deserializeContact = (raw) => {
|
|
2839
|
+
const input = {};
|
|
2840
|
+
const name = normalizeContactName(raw.name);
|
|
2841
|
+
if (name) {
|
|
2842
|
+
input.name = name;
|
|
2843
|
+
}
|
|
2844
|
+
const phones = normalizeContactPhones(raw.phones);
|
|
2845
|
+
if (phones) {
|
|
2846
|
+
input.phones = phones;
|
|
2847
|
+
}
|
|
2848
|
+
if (typeof raw.note === "string") {
|
|
2849
|
+
input.note = raw.note;
|
|
2850
|
+
}
|
|
2851
|
+
if (raw.raw !== void 0) {
|
|
2852
|
+
input.raw = raw.raw;
|
|
2853
|
+
}
|
|
2854
|
+
return asContact(input);
|
|
2855
|
+
};
|
|
2856
|
+
var CONTACT_NAME_KEYS = [
|
|
2857
|
+
"formatted",
|
|
2858
|
+
"first",
|
|
2859
|
+
"last",
|
|
2860
|
+
"middle",
|
|
2861
|
+
"prefix",
|
|
2862
|
+
"suffix"
|
|
2863
|
+
];
|
|
2864
|
+
var normalizeContactName = (value) => {
|
|
2865
|
+
if (typeof value === "string") {
|
|
2866
|
+
return { formatted: value };
|
|
2867
|
+
}
|
|
2868
|
+
if (!isRecord(value)) {
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
const name = {};
|
|
2872
|
+
for (const key of CONTACT_NAME_KEYS) {
|
|
2873
|
+
const part = value[key];
|
|
2874
|
+
if (typeof part === "string") {
|
|
2875
|
+
name[key] = part;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
return Object.keys(name).length > 0 ? name : void 0;
|
|
2879
|
+
};
|
|
2880
|
+
var normalizeContactPhones = (value) => {
|
|
2881
|
+
if (!Array.isArray(value)) {
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
const phones = [];
|
|
2885
|
+
for (const entry of value) {
|
|
2886
|
+
if (typeof entry === "string") {
|
|
2887
|
+
phones.push({ value: entry });
|
|
2888
|
+
} else if (isRecord(entry) && typeof entry.value === "string") {
|
|
2889
|
+
phones.push({ value: entry.value });
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
return phones.length > 0 ? phones : void 0;
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
// src/webhook/types.ts
|
|
2896
|
+
import z from "zod";
|
|
2897
|
+
var slimSenderSchema = z.looseObject({
|
|
2898
|
+
id: z.string(),
|
|
2899
|
+
platform: z.string().optional()
|
|
2900
|
+
});
|
|
2901
|
+
var slimSpaceSchema = z.looseObject({
|
|
2902
|
+
id: z.string(),
|
|
2903
|
+
platform: z.string().optional()
|
|
2904
|
+
});
|
|
2905
|
+
var slimContentSchema = z.looseObject({
|
|
2906
|
+
type: z.string()
|
|
2907
|
+
});
|
|
2908
|
+
var slimMessageRefSchema = z.looseObject({
|
|
2909
|
+
id: z.string(),
|
|
2910
|
+
platform: z.string().optional(),
|
|
2911
|
+
timestamp: z.string().optional(),
|
|
2912
|
+
sender: slimSenderSchema.optional(),
|
|
2913
|
+
contentPreview: z.string().optional()
|
|
2914
|
+
});
|
|
2915
|
+
var slimMessageSchema = z.looseObject({
|
|
2916
|
+
id: z.string(),
|
|
2917
|
+
platform: z.string().optional(),
|
|
2918
|
+
// Webhooks are inbound-only; `direction` is left loose (not a `"inbound"`
|
|
2919
|
+
// literal) so a future direction value cannot fail an older SDK's parse.
|
|
2920
|
+
direction: z.string().optional(),
|
|
2921
|
+
timestamp: z.string().optional(),
|
|
2922
|
+
sender: slimSenderSchema.optional(),
|
|
2923
|
+
space: slimSpaceSchema,
|
|
2924
|
+
content: slimContentSchema
|
|
2925
|
+
});
|
|
2926
|
+
var slimEnvelopeSchema = z.looseObject({
|
|
2927
|
+
event: z.string(),
|
|
2928
|
+
space: slimSpaceSchema.optional(),
|
|
2929
|
+
message: slimMessageSchema
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
// src/webhook/verify.ts
|
|
2933
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
2934
|
+
var SIGNATURE_HEADER = "x-spectrum-signature";
|
|
2935
|
+
var TIMESTAMP_HEADER = "x-spectrum-timestamp";
|
|
2936
|
+
var SIGNATURE_PREFIX = "v0=";
|
|
2937
|
+
var SIGNATURE_SCHEME = "v0";
|
|
2938
|
+
var REPLAY_TOLERANCE_SECONDS = 300;
|
|
2939
|
+
var MILLIS_PER_SECOND = 1e3;
|
|
2940
|
+
function verifySpectrumSignature(input) {
|
|
2941
|
+
const { rawBody, headers, secret, now = Date.now() } = input;
|
|
2942
|
+
const provided = headers[SIGNATURE_HEADER];
|
|
2943
|
+
const timestamp = headers[TIMESTAMP_HEADER];
|
|
2944
|
+
if (!(provided && timestamp)) {
|
|
2945
|
+
return { ok: false, reason: "missing-headers" };
|
|
2946
|
+
}
|
|
2947
|
+
const timestampSeconds = Number(timestamp);
|
|
2948
|
+
if (!Number.isFinite(timestampSeconds)) {
|
|
2949
|
+
return { ok: false, reason: "missing-headers" };
|
|
2950
|
+
}
|
|
2951
|
+
const nowSeconds = Math.floor(now / MILLIS_PER_SECOND);
|
|
2952
|
+
if (Math.abs(nowSeconds - timestampSeconds) > REPLAY_TOLERANCE_SECONDS) {
|
|
2953
|
+
return { ok: false, reason: "expired" };
|
|
2954
|
+
}
|
|
2955
|
+
const base = Buffer.concat([
|
|
2956
|
+
Buffer.from(`${SIGNATURE_SCHEME}:${timestamp}:`, "utf8"),
|
|
2957
|
+
Buffer.from(rawBody)
|
|
2958
|
+
]);
|
|
2959
|
+
const expected = createHmac("sha256", secret).update(base).digest();
|
|
2960
|
+
const providedHex = provided.startsWith(SIGNATURE_PREFIX) ? provided.slice(SIGNATURE_PREFIX.length) : provided;
|
|
2961
|
+
const providedBytes = Buffer.from(providedHex, "hex");
|
|
2962
|
+
if (providedBytes.length !== expected.length || !timingSafeEqual(providedBytes, expected)) {
|
|
2963
|
+
return { ok: false, reason: "signature-mismatch" };
|
|
2964
|
+
}
|
|
2965
|
+
return { ok: true };
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2710
2968
|
// src/spectrum.ts
|
|
2711
2969
|
var PHOTON_OTEL_ENDPOINT = "https://otlp.photon.codes";
|
|
2712
2970
|
var STREAM_CLOSE_TIMEOUT_MS = 5e3;
|
|
2713
2971
|
var lifecycleLog = createLogger3("spectrum.lifecycle");
|
|
2714
2972
|
var ignoreCleanupError = () => void 0;
|
|
2715
|
-
var spectrumOptionsSchema =
|
|
2716
|
-
flattenGroups:
|
|
2973
|
+
var spectrumOptionsSchema = z2.object({
|
|
2974
|
+
flattenGroups: z2.boolean().optional()
|
|
2717
2975
|
}).optional();
|
|
2718
|
-
var spectrumConfigSchema =
|
|
2719
|
-
|
|
2720
|
-
projectId:
|
|
2721
|
-
projectSecret:
|
|
2722
|
-
providers:
|
|
2976
|
+
var spectrumConfigSchema = z2.union([
|
|
2977
|
+
z2.object({
|
|
2978
|
+
projectId: z2.string().min(1),
|
|
2979
|
+
projectSecret: z2.string().min(1),
|
|
2980
|
+
providers: z2.array(z2.custom()),
|
|
2723
2981
|
options: spectrumOptionsSchema,
|
|
2724
|
-
telemetry:
|
|
2982
|
+
telemetry: z2.boolean().optional(),
|
|
2983
|
+
webhookSecret: z2.string().min(1).optional()
|
|
2725
2984
|
}),
|
|
2726
|
-
|
|
2727
|
-
projectId:
|
|
2728
|
-
projectSecret:
|
|
2729
|
-
providers:
|
|
2985
|
+
z2.object({
|
|
2986
|
+
projectId: z2.undefined().optional(),
|
|
2987
|
+
projectSecret: z2.undefined().optional(),
|
|
2988
|
+
providers: z2.array(z2.custom()),
|
|
2730
2989
|
options: spectrumOptionsSchema,
|
|
2731
|
-
telemetry:
|
|
2990
|
+
telemetry: z2.boolean().optional(),
|
|
2991
|
+
webhookSecret: z2.string().min(1).optional()
|
|
2732
2992
|
})
|
|
2733
2993
|
]);
|
|
2734
2994
|
function bootstrapTelemetry(opts) {
|
|
@@ -2758,9 +3018,11 @@ async function Spectrum(options) {
|
|
|
2758
3018
|
projectSecret,
|
|
2759
3019
|
providers,
|
|
2760
3020
|
options: runtimeOptions,
|
|
2761
|
-
telemetry
|
|
3021
|
+
telemetry,
|
|
3022
|
+
webhookSecret
|
|
2762
3023
|
} = options;
|
|
2763
3024
|
const flattenGroups = runtimeOptions?.flattenGroups ?? false;
|
|
3025
|
+
const resolvedWebhookSecret = webhookSecret ?? process.env.SPECTRUM_WEBHOOK_SECRET;
|
|
2764
3026
|
const otelHandle = telemetry ? bootstrapTelemetry({ projectId, projectSecret }) : void 0;
|
|
2765
3027
|
const projectConfig = projectId !== void 0 && projectSecret !== void 0 ? await cloud.getProject(projectId, projectSecret) : void 0;
|
|
2766
3028
|
const platformStates = /* @__PURE__ */ new Map();
|
|
@@ -3217,16 +3479,25 @@ async function Spectrum(options) {
|
|
|
3217
3479
|
};
|
|
3218
3480
|
const readWebhookInput = async (request) => {
|
|
3219
3481
|
if (typeof Request !== "undefined" && request instanceof Request) {
|
|
3482
|
+
const headers2 = {};
|
|
3483
|
+
for (const [key, value] of request.headers) {
|
|
3484
|
+
headers2[key.toLowerCase()] = value;
|
|
3485
|
+
}
|
|
3220
3486
|
return {
|
|
3221
3487
|
asWeb: true,
|
|
3222
|
-
bodyBytes: new Uint8Array(await request.arrayBuffer())
|
|
3488
|
+
bodyBytes: new Uint8Array(await request.arrayBuffer()),
|
|
3489
|
+
headers: headers2
|
|
3223
3490
|
};
|
|
3224
3491
|
}
|
|
3225
3492
|
const raw = request;
|
|
3226
3493
|
const bodyBytes = raw.body instanceof ArrayBuffer ? new Uint8Array(raw.body) : raw.body;
|
|
3227
|
-
|
|
3494
|
+
const headers = {};
|
|
3495
|
+
for (const [key, value] of Object.entries(raw.headers ?? {})) {
|
|
3496
|
+
headers[key.toLowerCase()] = String(value);
|
|
3497
|
+
}
|
|
3498
|
+
return { asWeb: false, bodyBytes, headers };
|
|
3228
3499
|
};
|
|
3229
|
-
const deliverWebhookMessages = async (collected, runtime, handler,
|
|
3500
|
+
const deliverWebhookMessages = async (collected, runtime, handler, context) => {
|
|
3230
3501
|
for (const record of collected) {
|
|
3231
3502
|
const tuples = await resolveRecordToMessages(record, runtime);
|
|
3232
3503
|
for (const [space, message] of tuples) {
|
|
@@ -3236,8 +3507,8 @@ async function Spectrum(options) {
|
|
|
3236
3507
|
lifecycleLog.error(
|
|
3237
3508
|
`spectrum.webhook: handler threw (async), ${error}`,
|
|
3238
3509
|
{
|
|
3239
|
-
eventId:
|
|
3240
|
-
platform:
|
|
3510
|
+
eventId: context.eventId,
|
|
3511
|
+
platform: context.platform,
|
|
3241
3512
|
messageId: message.id,
|
|
3242
3513
|
error: error instanceof Error ? error.message : String(error)
|
|
3243
3514
|
}
|
|
@@ -3290,13 +3561,132 @@ async function Spectrum(options) {
|
|
|
3290
3561
|
}
|
|
3291
3562
|
return result;
|
|
3292
3563
|
};
|
|
3564
|
+
const looksLikeNativePayload = (bodyBytes) => {
|
|
3565
|
+
for (const byte of bodyBytes) {
|
|
3566
|
+
if (byte === 32 || byte === 9 || byte === 10 || byte === 13) {
|
|
3567
|
+
continue;
|
|
3568
|
+
}
|
|
3569
|
+
return byte === 123;
|
|
3570
|
+
}
|
|
3571
|
+
return false;
|
|
3572
|
+
};
|
|
3573
|
+
const webhookText = (status, text2) => ({
|
|
3574
|
+
status,
|
|
3575
|
+
headers: {},
|
|
3576
|
+
body: encodeText(text2)
|
|
3577
|
+
});
|
|
3578
|
+
const resolveWebhookAttachment = (platform, spaceRef, attachmentId) => {
|
|
3579
|
+
const runtime = platformStates.get(platform);
|
|
3580
|
+
const action = runtime?.definition?.actions?.getAttachment;
|
|
3581
|
+
if (!runtime || typeof action !== "function") {
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
const getAttachment = action;
|
|
3585
|
+
const phone = typeof spaceRef.phone === "string" ? spaceRef.phone : void 0;
|
|
3586
|
+
let cached;
|
|
3587
|
+
const fetchOnce = () => {
|
|
3588
|
+
cached ??= getAttachment(
|
|
3589
|
+
{
|
|
3590
|
+
client: runtime.client,
|
|
3591
|
+
config: runtime.config,
|
|
3592
|
+
store: runtime.store
|
|
3593
|
+
},
|
|
3594
|
+
attachmentId,
|
|
3595
|
+
phone
|
|
3596
|
+
);
|
|
3597
|
+
return cached;
|
|
3598
|
+
};
|
|
3599
|
+
return {
|
|
3600
|
+
read: async () => {
|
|
3601
|
+
const found = await fetchOnce();
|
|
3602
|
+
if (!found) {
|
|
3603
|
+
throw new Error(
|
|
3604
|
+
`Spectrum webhook attachment "${attachmentId}" not found on "${platform}"`
|
|
3605
|
+
);
|
|
3606
|
+
}
|
|
3607
|
+
return found.read();
|
|
3608
|
+
},
|
|
3609
|
+
stream: async () => {
|
|
3610
|
+
const found = await fetchOnce();
|
|
3611
|
+
if (!found?.stream) {
|
|
3612
|
+
throw new Error(
|
|
3613
|
+
`Spectrum webhook attachment "${attachmentId}" has no stream on "${platform}"`
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
return found.stream();
|
|
3617
|
+
}
|
|
3618
|
+
};
|
|
3619
|
+
};
|
|
3620
|
+
const handleSpectrumWebhook = async (bodyBytes, headers, handler) => {
|
|
3621
|
+
if (!resolvedWebhookSecret) {
|
|
3622
|
+
lifecycleLog.error(
|
|
3623
|
+
"spectrum.webhook: received a signed Spectrum webhook but no webhookSecret is configured (set Spectrum({ webhookSecret }) or SPECTRUM_WEBHOOK_SECRET)"
|
|
3624
|
+
);
|
|
3625
|
+
return webhookText(500, "webhook secret not configured");
|
|
3626
|
+
}
|
|
3627
|
+
const verification = verifySpectrumSignature({
|
|
3628
|
+
rawBody: bodyBytes,
|
|
3629
|
+
headers,
|
|
3630
|
+
secret: resolvedWebhookSecret
|
|
3631
|
+
});
|
|
3632
|
+
if (!verification.ok) {
|
|
3633
|
+
const status = verification.reason === "missing-headers" ? 400 : 401;
|
|
3634
|
+
return webhookText(status, verification.reason);
|
|
3635
|
+
}
|
|
3636
|
+
let envelope;
|
|
3637
|
+
try {
|
|
3638
|
+
const parsed = JSON.parse(new TextDecoder().decode(bodyBytes));
|
|
3639
|
+
envelope = slimEnvelopeSchema.parse(parsed);
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
lifecycleLog.warn(
|
|
3642
|
+
`spectrum.webhook: malformed Spectrum webhook payload, ${error}`
|
|
3643
|
+
);
|
|
3644
|
+
return webhookText(400, "malformed payload");
|
|
3645
|
+
}
|
|
3646
|
+
const deserialized = deserializeSpectrumMessage(envelope, {
|
|
3647
|
+
resolveAttachment: resolveWebhookAttachment
|
|
3648
|
+
});
|
|
3649
|
+
if (!deserialized) {
|
|
3650
|
+
return webhookText(200, "ok");
|
|
3651
|
+
}
|
|
3652
|
+
const { platform, record } = deserialized;
|
|
3653
|
+
const runtime = platformStates.get(platform);
|
|
3654
|
+
if (!runtime) {
|
|
3655
|
+
lifecycleLog.warn(
|
|
3656
|
+
`spectrum.webhook: no provider configured for platform "${platform}"; acknowledging without delivery`,
|
|
3657
|
+
{ platform }
|
|
3658
|
+
);
|
|
3659
|
+
return webhookText(200, "ok");
|
|
3660
|
+
}
|
|
3661
|
+
deliverWebhookMessages([record], runtime, handler, { platform }).catch(
|
|
3662
|
+
(error) => {
|
|
3663
|
+
lifecycleLog.error(
|
|
3664
|
+
`spectrum.webhook: Spectrum delivery failed (async), ${error}`,
|
|
3665
|
+
{
|
|
3666
|
+
platform,
|
|
3667
|
+
messageId: record.id,
|
|
3668
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3669
|
+
}
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
);
|
|
3673
|
+
return webhookText(200, "ok");
|
|
3674
|
+
};
|
|
3293
3675
|
const handleWebhook = async (request, handler) => {
|
|
3676
|
+
const { asWeb, bodyBytes, headers } = await readWebhookInput(request);
|
|
3677
|
+
if (looksLikeNativePayload(bodyBytes)) {
|
|
3678
|
+
const spectrumResult = await handleSpectrumWebhook(
|
|
3679
|
+
bodyBytes,
|
|
3680
|
+
headers,
|
|
3681
|
+
handler
|
|
3682
|
+
);
|
|
3683
|
+
return buildWebhookResult(asWeb, spectrumResult);
|
|
3684
|
+
}
|
|
3294
3685
|
if (!fusorCore) {
|
|
3295
3686
|
throw new Error(
|
|
3296
|
-
"spectrum.webhook()
|
|
3687
|
+
"spectrum.webhook() received a non-Spectrum (fusor) request but no fusor provider is configured"
|
|
3297
3688
|
);
|
|
3298
3689
|
}
|
|
3299
|
-
const { asWeb, bodyBytes } = await readWebhookInput(request);
|
|
3300
3690
|
const event = decodeWebhookEvent(bodyBytes);
|
|
3301
3691
|
if (!event) {
|
|
3302
3692
|
return buildWebhookResult(asWeb, {
|
|
@@ -124,8 +124,8 @@ declare const userSchema: z__default.ZodObject<{}, z__default.core.$strip>;
|
|
|
124
124
|
declare const spaceSchema: z__default.ZodObject<{
|
|
125
125
|
id: z__default.ZodString;
|
|
126
126
|
type: z__default.ZodEnum<{
|
|
127
|
-
group: "group";
|
|
128
127
|
dm: "dm";
|
|
128
|
+
group: "group";
|
|
129
129
|
}>;
|
|
130
130
|
phone: z__default.ZodString;
|
|
131
131
|
}, z__default.core.$strip>;
|
|
@@ -151,8 +151,8 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
151
151
|
}, zod_v4_core.$strip>]>, z.ZodType<object, unknown, zod_v4_core.$ZodTypeInternals<object, unknown>> | undefined, z.ZodObject<{
|
|
152
152
|
id: z.ZodString;
|
|
153
153
|
type: z.ZodEnum<{
|
|
154
|
-
group: "group";
|
|
155
154
|
dm: "dm";
|
|
155
|
+
group: "group";
|
|
156
156
|
}>;
|
|
157
157
|
phone: z.ZodString;
|
|
158
158
|
}, zod_v4_core.$strip>, z.ZodObject<{
|
|
@@ -161,7 +161,7 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
161
161
|
id: string;
|
|
162
162
|
}, {
|
|
163
163
|
id: string;
|
|
164
|
-
type: "
|
|
164
|
+
type: "dm" | "group";
|
|
165
165
|
phone: string;
|
|
166
166
|
}, z.ZodObject<{
|
|
167
167
|
partIndex: z.ZodOptional<z.ZodNumber>;
|
|
@@ -190,7 +190,7 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
190
190
|
store: Store;
|
|
191
191
|
}, space: {
|
|
192
192
|
id: string;
|
|
193
|
-
type: "
|
|
193
|
+
type: "dm" | "group";
|
|
194
194
|
phone: string;
|
|
195
195
|
} & {
|
|
196
196
|
id: string;
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
customizedMiniApp,
|
|
5
5
|
effect,
|
|
6
6
|
imessage
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-NLMQ75LH.js";
|
|
8
8
|
import "../../chunk-ZR3TKZMT.js";
|
|
9
9
|
import "../../chunk-LZXPLXZF.js";
|
|
10
10
|
import "../../chunk-2D27WW5B.js";
|
|
11
|
-
import "../../chunk-3GEJYGZK.js";
|
|
12
11
|
import "../../chunk-A37PM5N2.js";
|
|
13
|
-
import "../../chunk-5XEFJBN2.js";
|
|
14
12
|
import "../../chunk-6UZFVXQF.js";
|
|
13
|
+
import "../../chunk-3GEJYGZK.js";
|
|
14
|
+
import "../../chunk-5XEFJBN2.js";
|
|
15
15
|
import {
|
|
16
16
|
read
|
|
17
17
|
} from "../../chunk-B52VPQO3.js";
|
|
@@ -13,7 +13,7 @@ import '@photon-ai/advanced-imessage';
|
|
|
13
13
|
import '@photon-ai/imessage-kit';
|
|
14
14
|
import '@photon-ai/slack';
|
|
15
15
|
import '@photon-ai/telegram-ts';
|
|
16
|
-
import '../types-
|
|
16
|
+
import '../types-ZgFTj5hJ.js';
|
|
17
17
|
import 'node:child_process';
|
|
18
18
|
import 'node:net';
|
|
19
19
|
import '@photon-ai/whatsapp-business';
|
package/dist/providers/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
imessage
|
|
4
|
-
} from "../chunk-DMT6BFJV.js";
|
|
5
|
-
import "../chunk-ZR3TKZMT.js";
|
|
6
|
-
import {
|
|
7
|
-
slack
|
|
8
|
-
} from "../chunk-WXLQNANA.js";
|
|
9
2
|
import {
|
|
10
3
|
telegram
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-34FQGGD7.js";
|
|
13
|
-
import "../chunk-LZXPLXZF.js";
|
|
4
|
+
} from "../chunk-N6THJDZV.js";
|
|
14
5
|
import {
|
|
15
6
|
terminal
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import "../chunk-FAIFTUV2.js";
|
|
7
|
+
} from "../chunk-ARL2NOBO.js";
|
|
18
8
|
import {
|
|
19
9
|
whatsappBusiness
|
|
20
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-DMPDLSFU.js";
|
|
11
|
+
import "../chunk-34FQGGD7.js";
|
|
12
|
+
import "../chunk-FAIFTUV2.js";
|
|
13
|
+
import {
|
|
14
|
+
imessage
|
|
15
|
+
} from "../chunk-NLMQ75LH.js";
|
|
16
|
+
import "../chunk-ZR3TKZMT.js";
|
|
17
|
+
import "../chunk-LZXPLXZF.js";
|
|
21
18
|
import "../chunk-2D27WW5B.js";
|
|
22
|
-
import "../chunk-3GEJYGZK.js";
|
|
23
19
|
import "../chunk-A37PM5N2.js";
|
|
24
|
-
import "../chunk-5XEFJBN2.js";
|
|
25
20
|
import "../chunk-6UZFVXQF.js";
|
|
21
|
+
import {
|
|
22
|
+
slack
|
|
23
|
+
} from "../chunk-WXLQNANA.js";
|
|
24
|
+
import "../chunk-3GEJYGZK.js";
|
|
25
|
+
import "../chunk-5XEFJBN2.js";
|
|
26
26
|
import "../chunk-B52VPQO3.js";
|
|
27
27
|
import "../chunk-UXAKIXVM.js";
|
|
28
28
|
export {
|
|
@@ -3,7 +3,7 @@ import * as _photon_ai_telegram_ts from '@photon-ai/telegram-ts';
|
|
|
3
3
|
import * as zod_v4_core from 'zod/v4/core';
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import z__default from 'zod';
|
|
6
|
-
import { F as FusorClient } from '../../types-
|
|
6
|
+
import { F as FusorClient } from '../../types-ZgFTj5hJ.js';
|
|
7
7
|
import 'hotscript';
|
|
8
8
|
|
|
9
9
|
interface TelegramSpace {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
telegram
|
|
4
|
-
} from "../../chunk-
|
|
4
|
+
} from "../../chunk-N6THJDZV.js";
|
|
5
5
|
import "../../chunk-34FQGGD7.js";
|
|
6
|
-
import "../../chunk-LZXPLXZF.js";
|
|
7
6
|
import "../../chunk-FAIFTUV2.js";
|
|
7
|
+
import "../../chunk-LZXPLXZF.js";
|
|
8
8
|
import "../../chunk-6UZFVXQF.js";
|
|
9
9
|
import "../../chunk-B52VPQO3.js";
|
|
10
10
|
import "../../chunk-UXAKIXVM.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
terminal
|
|
4
|
-
} from "../../chunk-
|
|
4
|
+
} from "../../chunk-ARL2NOBO.js";
|
|
5
5
|
import "../../chunk-FAIFTUV2.js";
|
|
6
6
|
import "../../chunk-A37PM5N2.js";
|
|
7
|
-
import "../../chunk-5XEFJBN2.js";
|
|
8
7
|
import "../../chunk-6UZFVXQF.js";
|
|
8
|
+
import "../../chunk-5XEFJBN2.js";
|
|
9
9
|
import "../../chunk-B52VPQO3.js";
|
|
10
10
|
import "../../chunk-UXAKIXVM.js";
|
|
11
11
|
export {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
whatsappBusiness
|
|
4
|
-
} from "../../chunk-
|
|
4
|
+
} from "../../chunk-DMPDLSFU.js";
|
|
5
5
|
import "../../chunk-2D27WW5B.js";
|
|
6
|
-
import "../../chunk-3GEJYGZK.js";
|
|
7
6
|
import "../../chunk-A37PM5N2.js";
|
|
8
|
-
import "../../chunk-5XEFJBN2.js";
|
|
9
7
|
import "../../chunk-6UZFVXQF.js";
|
|
8
|
+
import "../../chunk-3GEJYGZK.js";
|
|
9
|
+
import "../../chunk-5XEFJBN2.js";
|
|
10
10
|
import "../../chunk-B52VPQO3.js";
|
|
11
11
|
import "../../chunk-UXAKIXVM.js";
|
|
12
12
|
export {
|
|
@@ -62,11 +62,16 @@ interface FusorClient<TPayload = unknown> {
|
|
|
62
62
|
type WebhookHandler = (space: Space, message: Message) => void | Promise<void>;
|
|
63
63
|
/**
|
|
64
64
|
* Raw webhook input for HTTP servers without Web `Request`/`Response` (Express,
|
|
65
|
-
* raw Node). `body` MUST be the exact bytes
|
|
66
|
-
* JSON/text body — so the protobuf decode
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
65
|
+
* raw Node). `body` MUST be the exact bytes POSTed — never a re-encoded
|
|
66
|
+
* JSON/text body — so both the protobuf decode (fusor) and the HMAC
|
|
67
|
+
* verification (native Spectrum webhook) work.
|
|
68
|
+
*
|
|
69
|
+
* `headers` ARE read for **native Spectrum webhooks**: `X-Spectrum-Signature` /
|
|
70
|
+
* `X-Spectrum-Timestamp` carry the HMAC verified against
|
|
71
|
+
* `Spectrum({ webhookSecret })`, and the signature header also selects the
|
|
72
|
+
* native path. For **fusor** envelopes they are ignored (authenticity is the
|
|
73
|
+
* per-platform `verify()` reading the inner reconstructed request). The natural
|
|
74
|
+
* `{ headers: req.headers, body: req.body }` shape works for both.
|
|
70
75
|
*/
|
|
71
76
|
interface WebhookRawRequest {
|
|
72
77
|
body: Uint8Array | ArrayBuffer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectrum-ts",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Bring agents to any interface — unified messaging SDK for TypeScript.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,18 @@
|
|
|
27
27
|
"types": "./dist/authoring.d.ts",
|
|
28
28
|
"default": "./dist/authoring.js"
|
|
29
29
|
},
|
|
30
|
+
"./elysia": {
|
|
31
|
+
"types": "./dist/elysia.d.ts",
|
|
32
|
+
"default": "./dist/elysia.js"
|
|
33
|
+
},
|
|
34
|
+
"./express": {
|
|
35
|
+
"types": "./dist/express.d.ts",
|
|
36
|
+
"default": "./dist/express.js"
|
|
37
|
+
},
|
|
38
|
+
"./hono": {
|
|
39
|
+
"types": "./dist/hono.d.ts",
|
|
40
|
+
"default": "./dist/hono.js"
|
|
41
|
+
},
|
|
30
42
|
"./providers": {
|
|
31
43
|
"types": "./dist/providers/index.d.ts",
|
|
32
44
|
"default": "./dist/providers/index.js"
|
|
@@ -55,12 +67,24 @@
|
|
|
55
67
|
"zod": "^4.2.1"
|
|
56
68
|
},
|
|
57
69
|
"peerDependencies": {
|
|
70
|
+
"elysia": "^1",
|
|
71
|
+
"express": "^4 || ^5",
|
|
58
72
|
"ffmpeg-static": "^5",
|
|
73
|
+
"hono": "^4",
|
|
59
74
|
"typescript": "^5 || ^6.0.0"
|
|
60
75
|
},
|
|
61
76
|
"peerDependenciesMeta": {
|
|
77
|
+
"elysia": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"express": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
62
83
|
"ffmpeg-static": {
|
|
63
84
|
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"hono": {
|
|
87
|
+
"optional": true
|
|
64
88
|
}
|
|
65
89
|
},
|
|
66
90
|
"license": "MIT"
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
asContact
|
|
7
7
|
} from "./chunk-A37PM5N2.js";
|
|
8
|
-
import {
|
|
9
|
-
stream
|
|
10
|
-
} from "./chunk-5XEFJBN2.js";
|
|
11
8
|
import {
|
|
12
9
|
fromVCard,
|
|
13
10
|
toVCard
|
|
14
11
|
} from "./chunk-6UZFVXQF.js";
|
|
12
|
+
import {
|
|
13
|
+
stream
|
|
14
|
+
} from "./chunk-5XEFJBN2.js";
|
|
15
15
|
import {
|
|
16
16
|
UnsupportedError,
|
|
17
17
|
definePlatform
|
|
@@ -2,12 +2,12 @@ import { createRequire as __spectrumCreateRequire } from "node:module"; const re
|
|
|
2
2
|
import {
|
|
3
3
|
asPollOption
|
|
4
4
|
} from "./chunk-2D27WW5B.js";
|
|
5
|
-
import {
|
|
6
|
-
cloud
|
|
7
|
-
} from "./chunk-3GEJYGZK.js";
|
|
8
5
|
import {
|
|
9
6
|
asContact
|
|
10
7
|
} from "./chunk-A37PM5N2.js";
|
|
8
|
+
import {
|
|
9
|
+
cloud
|
|
10
|
+
} from "./chunk-3GEJYGZK.js";
|
|
11
11
|
import {
|
|
12
12
|
mergeStreams,
|
|
13
13
|
stream
|
|
@@ -2,12 +2,12 @@ import { createRequire as __spectrumCreateRequire } from "node:module"; const re
|
|
|
2
2
|
import {
|
|
3
3
|
fusor
|
|
4
4
|
} from "./chunk-34FQGGD7.js";
|
|
5
|
-
import {
|
|
6
|
-
asGroup
|
|
7
|
-
} from "./chunk-LZXPLXZF.js";
|
|
8
5
|
import {
|
|
9
6
|
asVoice
|
|
10
7
|
} from "./chunk-FAIFTUV2.js";
|
|
8
|
+
import {
|
|
9
|
+
asGroup
|
|
10
|
+
} from "./chunk-LZXPLXZF.js";
|
|
11
11
|
import {
|
|
12
12
|
toVCard
|
|
13
13
|
} from "./chunk-6UZFVXQF.js";
|
|
@@ -10,20 +10,20 @@ import {
|
|
|
10
10
|
asPoll,
|
|
11
11
|
asPollOption
|
|
12
12
|
} from "./chunk-2D27WW5B.js";
|
|
13
|
-
import {
|
|
14
|
-
cloud
|
|
15
|
-
} from "./chunk-3GEJYGZK.js";
|
|
16
13
|
import {
|
|
17
14
|
asContact
|
|
18
15
|
} from "./chunk-A37PM5N2.js";
|
|
19
|
-
import {
|
|
20
|
-
mergeStreams,
|
|
21
|
-
stream
|
|
22
|
-
} from "./chunk-5XEFJBN2.js";
|
|
23
16
|
import {
|
|
24
17
|
fromVCard,
|
|
25
18
|
toVCard
|
|
26
19
|
} from "./chunk-6UZFVXQF.js";
|
|
20
|
+
import {
|
|
21
|
+
cloud
|
|
22
|
+
} from "./chunk-3GEJYGZK.js";
|
|
23
|
+
import {
|
|
24
|
+
mergeStreams,
|
|
25
|
+
stream
|
|
26
|
+
} from "./chunk-5XEFJBN2.js";
|
|
27
27
|
import {
|
|
28
28
|
UnsupportedError,
|
|
29
29
|
buildPhotoAction,
|