spectrum-ts 4.0.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/{attachment-CEpGtZLm.d.ts → attachment-CnivEhr6.d.ts} +1 -1
- package/dist/{authoring-CP3vRza8.d.ts → authoring-b9AhXgPI.d.ts} +2 -2
- package/dist/authoring.d.ts +3 -3
- package/dist/authoring.js +3 -3
- package/dist/{chunk-PV4AVMNN.js → chunk-ARL2NOBO.js} +4 -4
- package/dist/{chunk-OGTHPDG7.js → chunk-B52VPQO3.js} +69 -29
- package/dist/{chunk-5VCWWPFW.js → chunk-DMPDLSFU.js} +8 -4
- package/dist/{chunk-57NECZQZ.js → chunk-N6THJDZV.js} +6 -4
- package/dist/{chunk-W5HNZ7YT.js → chunk-NLMQ75LH.js} +33 -76
- package/dist/{chunk-VEF6FUE7.js → chunk-WXLQNANA.js} +4 -1
- 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 +33 -18
- package/dist/index.js +428 -36
- package/dist/providers/imessage/index.d.ts +10 -32
- package/dist/providers/imessage/index.js +7 -6
- package/dist/providers/index.d.ts +4 -4
- package/dist/providers/index.js +16 -16
- package/dist/providers/slack/index.d.ts +1 -1
- package/dist/providers/slack/index.js +2 -2
- package/dist/providers/telegram/index.d.ts +2 -2
- package/dist/providers/telegram/index.js +3 -3
- package/dist/providers/terminal/index.d.ts +1 -1
- package/dist/providers/terminal/index.js +3 -3
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +4 -4
- package/dist/read-C4uvozGX.d.ts +53 -0
- package/dist/{types-Be0T6E0e.d.ts → types-CyfLJXgu.d.ts} +26 -1
- package/dist/{types-CDYXH2R7.d.ts → types-ZgFTj5hJ.d.ts} +11 -6
- package/package.json +25 -1
- package/dist/photo-content-BJKnqgN-.d.ts +0 -13
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,
|
|
@@ -44,14 +46,17 @@ import {
|
|
|
44
46
|
definePlatform,
|
|
45
47
|
edit,
|
|
46
48
|
markdown,
|
|
49
|
+
read,
|
|
47
50
|
rename,
|
|
48
51
|
reply,
|
|
49
52
|
senderAttrs,
|
|
50
53
|
typing,
|
|
51
54
|
unsend,
|
|
52
55
|
wrapProviderMessage
|
|
53
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-B52VPQO3.js";
|
|
54
57
|
import {
|
|
58
|
+
asAttachment,
|
|
59
|
+
asCustom,
|
|
55
60
|
attachment,
|
|
56
61
|
custom,
|
|
57
62
|
reaction,
|
|
@@ -1995,7 +2000,7 @@ import {
|
|
|
1995
2000
|
withSpan
|
|
1996
2001
|
} from "@photon-ai/otel";
|
|
1997
2002
|
import { RawInboundEvent } from "@photon-ai/proto/photon/fusor/v1/inbound";
|
|
1998
|
-
import
|
|
2003
|
+
import z2 from "zod";
|
|
1999
2004
|
|
|
2000
2005
|
// src/build-env.ts
|
|
2001
2006
|
var SPECTRUM_SDK_VERSION = "local";
|
|
@@ -2706,28 +2711,284 @@ function createStore() {
|
|
|
2706
2711
|
};
|
|
2707
2712
|
}
|
|
2708
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
|
+
|
|
2709
2968
|
// src/spectrum.ts
|
|
2710
2969
|
var PHOTON_OTEL_ENDPOINT = "https://otlp.photon.codes";
|
|
2711
2970
|
var STREAM_CLOSE_TIMEOUT_MS = 5e3;
|
|
2712
2971
|
var lifecycleLog = createLogger3("spectrum.lifecycle");
|
|
2713
2972
|
var ignoreCleanupError = () => void 0;
|
|
2714
|
-
var spectrumOptionsSchema =
|
|
2715
|
-
flattenGroups:
|
|
2973
|
+
var spectrumOptionsSchema = z2.object({
|
|
2974
|
+
flattenGroups: z2.boolean().optional()
|
|
2716
2975
|
}).optional();
|
|
2717
|
-
var spectrumConfigSchema =
|
|
2718
|
-
|
|
2719
|
-
projectId:
|
|
2720
|
-
projectSecret:
|
|
2721
|
-
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()),
|
|
2722
2981
|
options: spectrumOptionsSchema,
|
|
2723
|
-
telemetry:
|
|
2982
|
+
telemetry: z2.boolean().optional(),
|
|
2983
|
+
webhookSecret: z2.string().min(1).optional()
|
|
2724
2984
|
}),
|
|
2725
|
-
|
|
2726
|
-
projectId:
|
|
2727
|
-
projectSecret:
|
|
2728
|
-
providers:
|
|
2985
|
+
z2.object({
|
|
2986
|
+
projectId: z2.undefined().optional(),
|
|
2987
|
+
projectSecret: z2.undefined().optional(),
|
|
2988
|
+
providers: z2.array(z2.custom()),
|
|
2729
2989
|
options: spectrumOptionsSchema,
|
|
2730
|
-
telemetry:
|
|
2990
|
+
telemetry: z2.boolean().optional(),
|
|
2991
|
+
webhookSecret: z2.string().min(1).optional()
|
|
2731
2992
|
})
|
|
2732
2993
|
]);
|
|
2733
2994
|
function bootstrapTelemetry(opts) {
|
|
@@ -2757,9 +3018,11 @@ async function Spectrum(options) {
|
|
|
2757
3018
|
projectSecret,
|
|
2758
3019
|
providers,
|
|
2759
3020
|
options: runtimeOptions,
|
|
2760
|
-
telemetry
|
|
3021
|
+
telemetry,
|
|
3022
|
+
webhookSecret
|
|
2761
3023
|
} = options;
|
|
2762
3024
|
const flattenGroups = runtimeOptions?.flattenGroups ?? false;
|
|
3025
|
+
const resolvedWebhookSecret = webhookSecret ?? process.env.SPECTRUM_WEBHOOK_SECRET;
|
|
2763
3026
|
const otelHandle = telemetry ? bootstrapTelemetry({ projectId, projectSecret }) : void 0;
|
|
2764
3027
|
const projectConfig = projectId !== void 0 && projectSecret !== void 0 ? await cloud.getProject(projectId, projectSecret) : void 0;
|
|
2765
3028
|
const platformStates = /* @__PURE__ */ new Map();
|
|
@@ -3216,16 +3479,25 @@ async function Spectrum(options) {
|
|
|
3216
3479
|
};
|
|
3217
3480
|
const readWebhookInput = async (request) => {
|
|
3218
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
|
+
}
|
|
3219
3486
|
return {
|
|
3220
3487
|
asWeb: true,
|
|
3221
|
-
bodyBytes: new Uint8Array(await request.arrayBuffer())
|
|
3488
|
+
bodyBytes: new Uint8Array(await request.arrayBuffer()),
|
|
3489
|
+
headers: headers2
|
|
3222
3490
|
};
|
|
3223
3491
|
}
|
|
3224
3492
|
const raw = request;
|
|
3225
3493
|
const bodyBytes = raw.body instanceof ArrayBuffer ? new Uint8Array(raw.body) : raw.body;
|
|
3226
|
-
|
|
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 };
|
|
3227
3499
|
};
|
|
3228
|
-
const deliverWebhookMessages = async (collected, runtime, handler,
|
|
3500
|
+
const deliverWebhookMessages = async (collected, runtime, handler, context) => {
|
|
3229
3501
|
for (const record of collected) {
|
|
3230
3502
|
const tuples = await resolveRecordToMessages(record, runtime);
|
|
3231
3503
|
for (const [space, message] of tuples) {
|
|
@@ -3235,8 +3507,8 @@ async function Spectrum(options) {
|
|
|
3235
3507
|
lifecycleLog.error(
|
|
3236
3508
|
`spectrum.webhook: handler threw (async), ${error}`,
|
|
3237
3509
|
{
|
|
3238
|
-
eventId:
|
|
3239
|
-
platform:
|
|
3510
|
+
eventId: context.eventId,
|
|
3511
|
+
platform: context.platform,
|
|
3240
3512
|
messageId: message.id,
|
|
3241
3513
|
error: error instanceof Error ? error.message : String(error)
|
|
3242
3514
|
}
|
|
@@ -3289,13 +3561,132 @@ async function Spectrum(options) {
|
|
|
3289
3561
|
}
|
|
3290
3562
|
return result;
|
|
3291
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
|
+
};
|
|
3292
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
|
+
}
|
|
3293
3685
|
if (!fusorCore) {
|
|
3294
3686
|
throw new Error(
|
|
3295
|
-
"spectrum.webhook()
|
|
3687
|
+
"spectrum.webhook() received a non-Spectrum (fusor) request but no fusor provider is configured"
|
|
3296
3688
|
);
|
|
3297
3689
|
}
|
|
3298
|
-
const { asWeb, bodyBytes } = await readWebhookInput(request);
|
|
3299
3690
|
const event = decodeWebhookEvent(bodyBytes);
|
|
3300
3691
|
if (!event) {
|
|
3301
3692
|
return buildWebhookResult(asWeb, {
|
|
@@ -3358,6 +3749,7 @@ export {
|
|
|
3358
3749
|
option,
|
|
3359
3750
|
poll,
|
|
3360
3751
|
reaction,
|
|
3752
|
+
read,
|
|
3361
3753
|
rename,
|
|
3362
3754
|
reply,
|
|
3363
3755
|
resolveContents,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { C as ContentBuilder, h as ContentInput,
|
|
1
|
+
import { C as ContentBuilder, h as ContentInput, a as SchemaMessage, P as Platform, b as PlatformDef, S as Space, d as Store } from '../../types-CyfLJXgu.js';
|
|
2
2
|
import * as zod_v4_core from 'zod/v4/core';
|
|
3
3
|
import * as z from 'zod';
|
|
4
4
|
import z__default from 'zod';
|
|
5
|
-
import { A as Attachment } from '../../attachment-
|
|
6
|
-
import { P as PhotoInput } from '../../
|
|
5
|
+
import { A as Attachment } from '../../attachment-CnivEhr6.js';
|
|
6
|
+
import { P as PhotoInput } from '../../read-C4uvozGX.js';
|
|
7
|
+
export { r as read } from '../../read-C4uvozGX.js';
|
|
7
8
|
import { MessageEffect, AdvancedIMessage } from '@photon-ai/advanced-imessage';
|
|
8
9
|
import { IMessageSDK } from '@photon-ai/imessage-kit';
|
|
9
10
|
import 'hotscript';
|
|
@@ -114,26 +115,6 @@ declare function customizedMiniApp(input: CustomizedMiniAppInput): ContentBuilde
|
|
|
114
115
|
type IMessageMessageEffect = MessageEffect;
|
|
115
116
|
declare function effect(input: ContentInput, messageEffect: IMessageMessageEffect): ContentBuilder;
|
|
116
117
|
|
|
117
|
-
/**
|
|
118
|
-
* Mark the chat containing `target` as read. iMessage-only, remote-only.
|
|
119
|
-
*
|
|
120
|
-
* Implemented via `chats.markRead(chatGuid)`, which marks **every unread
|
|
121
|
-
* message in the chat** as read — there is no per-message read receipt in
|
|
122
|
-
* the SDK. `target` is used only to identify the chat (and to give
|
|
123
|
-
* `message.read()` something to pass), so passing any message from a chat
|
|
124
|
-
* marks the whole chat as read.
|
|
125
|
-
*
|
|
126
|
-
* `space.send(read(message))` is the canonical form; `space.read(message)`
|
|
127
|
-
* and `message.read()` are sugar attached via the iMessage platform's
|
|
128
|
-
* `space.actions` / `message.actions` slots.
|
|
129
|
-
*
|
|
130
|
-
* `Read` is intentionally not a member of the universal `Content` union —
|
|
131
|
-
* the `as unknown as Content` cast keeps the builder shape compatible with
|
|
132
|
-
* the framework's `ContentBuilder.build(): Promise<Content>` signature. The
|
|
133
|
-
* framework treats it as a fire-and-forget control signal at runtime.
|
|
134
|
-
*/
|
|
135
|
-
declare function read(target: Message): ContentBuilder;
|
|
136
|
-
|
|
137
118
|
interface RemoteClient {
|
|
138
119
|
client: AdvancedIMessage;
|
|
139
120
|
phone: string;
|
|
@@ -143,8 +124,8 @@ declare const userSchema: z__default.ZodObject<{}, z__default.core.$strip>;
|
|
|
143
124
|
declare const spaceSchema: z__default.ZodObject<{
|
|
144
125
|
id: z__default.ZodString;
|
|
145
126
|
type: z__default.ZodEnum<{
|
|
146
|
-
group: "group";
|
|
147
127
|
dm: "dm";
|
|
128
|
+
group: "group";
|
|
148
129
|
}>;
|
|
149
130
|
phone: z__default.ZodString;
|
|
150
131
|
}, z__default.core.$strip>;
|
|
@@ -170,8 +151,8 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
170
151
|
}, zod_v4_core.$strip>]>, z.ZodType<object, unknown, zod_v4_core.$ZodTypeInternals<object, unknown>> | undefined, z.ZodObject<{
|
|
171
152
|
id: z.ZodString;
|
|
172
153
|
type: z.ZodEnum<{
|
|
173
|
-
group: "group";
|
|
174
154
|
dm: "dm";
|
|
155
|
+
group: "group";
|
|
175
156
|
}>;
|
|
176
157
|
phone: z.ZodString;
|
|
177
158
|
}, zod_v4_core.$strip>, z.ZodObject<{
|
|
@@ -180,7 +161,7 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
180
161
|
id: string;
|
|
181
162
|
}, {
|
|
182
163
|
id: string;
|
|
183
|
-
type: "
|
|
164
|
+
type: "dm" | "group";
|
|
184
165
|
phone: string;
|
|
185
166
|
}, z.ZodObject<{
|
|
186
167
|
partIndex: z.ZodOptional<z.ZodNumber>;
|
|
@@ -189,10 +170,7 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
189
170
|
background: (space: Space, input: BackgroundInput, opts?: {
|
|
190
171
|
mimeType?: string;
|
|
191
172
|
}) => Promise<void>;
|
|
192
|
-
|
|
193
|
-
}, {
|
|
194
|
-
read: (message: Message) => Promise<void>;
|
|
195
|
-
}, {
|
|
173
|
+
}, Record<never, never>, {
|
|
196
174
|
getMessage: ({ client }: {
|
|
197
175
|
client: IMessageClient;
|
|
198
176
|
config: {
|
|
@@ -212,7 +190,7 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
212
190
|
store: Store;
|
|
213
191
|
}, space: {
|
|
214
192
|
id: string;
|
|
215
|
-
type: "
|
|
193
|
+
type: "dm" | "group";
|
|
216
194
|
phone: string;
|
|
217
195
|
} & {
|
|
218
196
|
id: string;
|
|
@@ -241,4 +219,4 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
|
|
|
241
219
|
};
|
|
242
220
|
}>;
|
|
243
221
|
|
|
244
|
-
export { type BackgroundInput, type CustomizedMiniApp, type CustomizedMiniAppInput, type CustomizedMiniAppLayout, type IMessageMessageEffect, background, customizedMiniApp, effect, imessage
|
|
222
|
+
export { type BackgroundInput, type CustomizedMiniApp, type CustomizedMiniAppInput, type CustomizedMiniAppLayout, type IMessageMessageEffect, background, customizedMiniApp, effect, imessage };
|
|
@@ -3,17 +3,18 @@ import {
|
|
|
3
3
|
background,
|
|
4
4
|
customizedMiniApp,
|
|
5
5
|
effect,
|
|
6
|
-
imessage
|
|
7
|
-
|
|
8
|
-
} from "../../chunk-W5HNZ7YT.js";
|
|
6
|
+
imessage
|
|
7
|
+
} from "../../chunk-NLMQ75LH.js";
|
|
9
8
|
import "../../chunk-ZR3TKZMT.js";
|
|
10
9
|
import "../../chunk-LZXPLXZF.js";
|
|
11
10
|
import "../../chunk-2D27WW5B.js";
|
|
12
|
-
import "../../chunk-3GEJYGZK.js";
|
|
13
11
|
import "../../chunk-A37PM5N2.js";
|
|
14
|
-
import "../../chunk-5XEFJBN2.js";
|
|
15
12
|
import "../../chunk-6UZFVXQF.js";
|
|
16
|
-
import "../../chunk-
|
|
13
|
+
import "../../chunk-3GEJYGZK.js";
|
|
14
|
+
import "../../chunk-5XEFJBN2.js";
|
|
15
|
+
import {
|
|
16
|
+
read
|
|
17
|
+
} from "../../chunk-B52VPQO3.js";
|
|
17
18
|
import "../../chunk-UXAKIXVM.js";
|
|
18
19
|
export {
|
|
19
20
|
background,
|