spectrum-ts 4.1.0 → 5.0.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.
Files changed (46) hide show
  1. package/README.md +29 -67
  2. package/dist/authoring.d.ts +1 -6
  3. package/dist/authoring.js +2 -36
  4. package/dist/elysia.d.ts +1 -0
  5. package/dist/elysia.js +2 -0
  6. package/dist/express.d.ts +1 -0
  7. package/dist/express.js +2 -0
  8. package/dist/hono.d.ts +1 -0
  9. package/dist/hono.js +2 -0
  10. package/dist/index.d.ts +1 -2837
  11. package/dist/index.js +2 -3373
  12. package/dist/manifest.json +5 -5
  13. package/dist/providers/imessage/index.d.ts +1 -222
  14. package/dist/providers/imessage/index.js +2 -25
  15. package/dist/providers/index.d.ts +6 -19
  16. package/dist/providers/index.js +6 -34
  17. package/dist/providers/slack/index.d.ts +1 -46
  18. package/dist/providers/slack/index.js +2 -11
  19. package/dist/providers/telegram/index.d.ts +1 -45
  20. package/dist/providers/telegram/index.js +2 -13
  21. package/dist/providers/terminal/index.d.ts +1 -119
  22. package/dist/providers/terminal/index.js +2 -13
  23. package/dist/providers/whatsapp-business/index.d.ts +1 -27
  24. package/dist/providers/whatsapp-business/index.js +2 -14
  25. package/package.json +23 -26
  26. package/dist/attachment-CnivEhr6.d.ts +0 -29
  27. package/dist/authoring-b9AhXgPI.d.ts +0 -304
  28. package/dist/chunk-2D27WW5B.js +0 -63
  29. package/dist/chunk-34FQGGD7.js +0 -34
  30. package/dist/chunk-3GEJYGZK.js +0 -84
  31. package/dist/chunk-3KWFP4L2.js +0 -864
  32. package/dist/chunk-5XEFJBN2.js +0 -197
  33. package/dist/chunk-6UZFVXQF.js +0 -374
  34. package/dist/chunk-A37PM5N2.js +0 -91
  35. package/dist/chunk-B52VPQO3.js +0 -1379
  36. package/dist/chunk-DMT6BFJV.js +0 -2980
  37. package/dist/chunk-FAIFTUV2.js +0 -139
  38. package/dist/chunk-IM5ADDZS.js +0 -887
  39. package/dist/chunk-LZXPLXZF.js +0 -35
  40. package/dist/chunk-U3QQ56YZ.js +0 -929
  41. package/dist/chunk-UXAKIXVM.js +0 -409
  42. package/dist/chunk-WXLQNANA.js +0 -539
  43. package/dist/chunk-ZR3TKZMT.js +0 -129
  44. package/dist/read-C4uvozGX.d.ts +0 -53
  45. package/dist/types-BIta6Kxi.d.ts +0 -82
  46. package/dist/types-CyfLJXgu.d.ts +0 -1530
@@ -1,864 +0,0 @@
1
- import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
- import {
3
- asPollOption
4
- } from "./chunk-2D27WW5B.js";
5
- import {
6
- cloud
7
- } from "./chunk-3GEJYGZK.js";
8
- import {
9
- asContact
10
- } from "./chunk-A37PM5N2.js";
11
- import {
12
- mergeStreams,
13
- stream
14
- } from "./chunk-5XEFJBN2.js";
15
- import {
16
- UnsupportedError,
17
- definePlatform
18
- } from "./chunk-B52VPQO3.js";
19
- import {
20
- asAttachment,
21
- asCustom,
22
- asReaction,
23
- asText
24
- } from "./chunk-UXAKIXVM.js";
25
-
26
- // src/providers/whatsapp-business/index.ts
27
- import { createClient as createClient2 } from "@photon-ai/whatsapp-business";
28
-
29
- // src/providers/whatsapp-business/auth.ts
30
- import {
31
- createClient,
32
- TypedEventStream
33
- } from "@photon-ai/whatsapp-business";
34
- var RENEWAL_RATIO = 0.8;
35
- var EXPIRY_BUFFER_MS = 3e4;
36
- var RETRY_DELAY_MS = 3e4;
37
- var RESUBSCRIBE_BACKOFF_MS = 500;
38
- var cloudAuthState = /* @__PURE__ */ new WeakMap();
39
- async function createCloudClients(projectId, projectSecret) {
40
- let tokenData = await cloud.issueWhatsappBusinessTokens(
41
- projectId,
42
- projectSecret
43
- );
44
- let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
45
- let disposed = false;
46
- let renewalTimer;
47
- const lines = /* @__PURE__ */ new Map();
48
- const buildRawClient = (phoneNumberId) => {
49
- const accessToken = tokenData.auth[phoneNumberId];
50
- if (!accessToken) {
51
- throw new Error(
52
- `WhatsApp Business line ${phoneNumberId} missing from token response`
53
- );
54
- }
55
- return createClient({ accessToken, appSecret: "", phoneNumberId });
56
- };
57
- const refreshTokens = async () => {
58
- tokenData = await cloud.issueWhatsappBusinessTokens(
59
- projectId,
60
- projectSecret
61
- );
62
- tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
63
- for (const [phoneNumberId, state] of lines) {
64
- if (!tokenData.auth[phoneNumberId]) {
65
- continue;
66
- }
67
- const old = state.current;
68
- state.current = buildRawClient(phoneNumberId);
69
- for (const sub of state.subscriptions) {
70
- sub.swap();
71
- }
72
- await old.close().catch(() => void 0);
73
- }
74
- };
75
- const clearRenewalTimer = () => {
76
- if (renewalTimer !== void 0) {
77
- clearTimeout(renewalTimer);
78
- renewalTimer = void 0;
79
- }
80
- };
81
- const scheduleRenewal = () => {
82
- if (disposed) {
83
- return;
84
- }
85
- clearRenewalTimer();
86
- const ttlMs = tokenData.expiresIn * 1e3;
87
- const renewInMs = Math.max(ttlMs * RENEWAL_RATIO, 5e3);
88
- renewalTimer = setTimeout(async () => {
89
- try {
90
- await refreshTokens();
91
- scheduleRenewal();
92
- } catch (err) {
93
- console.warn(
94
- `[spectrum-ts] WhatsApp Business token refresh failed; retrying in ${RETRY_DELAY_MS}ms.`,
95
- err
96
- );
97
- clearRenewalTimer();
98
- renewalTimer = setTimeout(() => scheduleRenewal(), RETRY_DELAY_MS);
99
- renewalTimer?.unref?.();
100
- }
101
- }, renewInMs);
102
- renewalTimer?.unref?.();
103
- };
104
- const refreshIfNeeded = async () => {
105
- if (Date.now() < tokenExpiresAt - EXPIRY_BUFFER_MS) {
106
- return;
107
- }
108
- await refreshTokens();
109
- scheduleRenewal();
110
- };
111
- scheduleRenewal();
112
- const clients = Object.keys(tokenData.auth).map(
113
- (phoneNumberId) => {
114
- const state = {
115
- current: buildRawClient(phoneNumberId),
116
- subscriptions: /* @__PURE__ */ new Set()
117
- };
118
- lines.set(phoneNumberId, state);
119
- return buildClientProxy(state, refreshIfNeeded);
120
- }
121
- );
122
- cloudAuthState.set(clients, {
123
- dispose: async () => {
124
- disposed = true;
125
- clearRenewalTimer();
126
- for (const state of lines.values()) {
127
- for (const sub of state.subscriptions) {
128
- sub.close();
129
- }
130
- }
131
- await Promise.allSettled(
132
- Array.from(lines.values()).map((s) => s.current.close())
133
- );
134
- lines.clear();
135
- }
136
- });
137
- return clients;
138
- }
139
- async function disposeCloudAuth(clients) {
140
- const auth = cloudAuthState.get(clients);
141
- if (!auth) {
142
- return;
143
- }
144
- await auth.dispose();
145
- cloudAuthState.delete(clients);
146
- }
147
- var buildClientProxy = (state, refresh) => {
148
- const forwarder = (pick) => new Proxy({}, {
149
- get: (_, prop) => async (...args) => {
150
- await refresh();
151
- const target = pick(state.current);
152
- const fn = target[prop];
153
- return Reflect.apply(fn, pick(state.current), args);
154
- }
155
- });
156
- const events = {
157
- fetchMissed: async (opts) => {
158
- await refresh();
159
- return state.current.events.fetchMissed(opts);
160
- },
161
- subscribe: (options) => resubscribableStream(state, options)
162
- };
163
- return {
164
- events,
165
- media: forwarder((c) => c.media),
166
- messages: forwarder((c) => c.messages),
167
- close: async () => {
168
- for (const sub of state.subscriptions) {
169
- sub.close();
170
- }
171
- await state.current.close();
172
- },
173
- [Symbol.asyncDispose]: async () => {
174
- for (const sub of state.subscriptions) {
175
- sub.close();
176
- }
177
- await state.current.close();
178
- }
179
- };
180
- };
181
- var pumpOnce = async (ctx) => {
182
- const sub = ctx.getCurrent().events.subscribe(ctx.options);
183
- ctx.setActive(sub);
184
- try {
185
- for await (const event of sub) {
186
- await ctx.emit(event);
187
- }
188
- return true;
189
- } catch {
190
- return false;
191
- } finally {
192
- ctx.setActive(void 0);
193
- }
194
- };
195
- var resubscribableStream = (state, options) => {
196
- let closed = false;
197
- let active;
198
- const source = stream((emit, end) => {
199
- const ctx = {
200
- emit,
201
- getCurrent: () => state.current,
202
- options,
203
- setActive: (s) => {
204
- active = s;
205
- }
206
- };
207
- const pump = (async () => {
208
- while (!closed) {
209
- await pumpOnce(ctx);
210
- if (!closed) {
211
- await new Promise((r) => setTimeout(r, RESUBSCRIBE_BACKOFF_MS));
212
- }
213
- }
214
- end();
215
- })();
216
- return async () => {
217
- closed = true;
218
- active?.close().catch(() => void 0);
219
- active = void 0;
220
- state.subscriptions.delete(subscription);
221
- await pump;
222
- };
223
- });
224
- const subscription = {
225
- close: () => {
226
- closed = true;
227
- active?.close().catch(() => void 0);
228
- },
229
- swap: () => {
230
- active?.close().catch(() => void 0);
231
- }
232
- };
233
- state.subscriptions.add(subscription);
234
- return new TypedEventStream(source, async () => {
235
- closed = true;
236
- active?.close().catch(() => void 0);
237
- state.subscriptions.delete(subscription);
238
- await source.close();
239
- });
240
- };
241
-
242
- // src/providers/whatsapp-business/messages.ts
243
- import { extension as mimeExtension } from "mime-types";
244
-
245
- // src/providers/whatsapp-business/poll.ts
246
- import {
247
- button,
248
- buttons,
249
- list
250
- } from "@photon-ai/whatsapp-business";
251
- var MAX_BUTTON_OPTIONS = 3;
252
- var LIST_BUTTON_TEXT = "View options";
253
- var LIST_SECTION_TITLE = "Options";
254
- var pollOptionId = (index) => `opt_${index}`;
255
- var pollToInteractive = (content) => {
256
- if (content.options.length <= MAX_BUTTON_OPTIONS) {
257
- return buttons(
258
- content.title,
259
- ...content.options.map((o, i) => button(pollOptionId(i), o.title))
260
- );
261
- }
262
- return list(content.title, LIST_BUTTON_TEXT).section(
263
- LIST_SECTION_TITLE,
264
- content.options.map((o, i) => ({ id: pollOptionId(i), title: o.title }))
265
- );
266
- };
267
-
268
- // src/providers/whatsapp-business/messages.ts
269
- var primary = (clients) => {
270
- const client = clients[0];
271
- if (!client) {
272
- throw new Error("No WhatsApp Business client available");
273
- }
274
- return client;
275
- };
276
- var toRecord = (result, spaceId, content) => ({
277
- id: result.messageId,
278
- content,
279
- space: { id: spaceId },
280
- timestamp: /* @__PURE__ */ new Date()
281
- });
282
- var MAX_POLL_CACHE_SIZE = 1e3;
283
- var OPTION_ID_PREFIX = "opt_";
284
- var pollCaches = /* @__PURE__ */ new WeakMap();
285
- var getPollCache = (client) => {
286
- let cache = pollCaches.get(client);
287
- if (!cache) {
288
- cache = /* @__PURE__ */ new Map();
289
- pollCaches.set(client, cache);
290
- }
291
- return cache;
292
- };
293
- var cachePoll = (client, messageId, poll) => {
294
- const cache = getPollCache(client);
295
- if (cache.has(messageId)) {
296
- cache.delete(messageId);
297
- }
298
- cache.set(messageId, poll);
299
- if (cache.size > MAX_POLL_CACHE_SIZE) {
300
- const first = cache.keys().next().value;
301
- if (first !== void 0) {
302
- cache.delete(first);
303
- }
304
- }
305
- };
306
- var optionIndexFromId = (id) => {
307
- if (!id.startsWith(OPTION_ID_PREFIX)) {
308
- return;
309
- }
310
- const index = Number(id.slice(OPTION_ID_PREFIX.length));
311
- if (!Number.isInteger(index) || index < 0 || pollOptionId(index) !== id) {
312
- return;
313
- }
314
- return index;
315
- };
316
- var mapWaPhoneType = (type) => {
317
- if (!type) {
318
- return;
319
- }
320
- const upper = type.toUpperCase();
321
- if (upper === "CELL" || upper === "MOBILE" || upper === "IPHONE") {
322
- return "mobile";
323
- }
324
- if (upper === "HOME") {
325
- return "home";
326
- }
327
- if (upper === "WORK" || upper === "BUSINESS") {
328
- return "work";
329
- }
330
- return "other";
331
- };
332
- var mapWaSimpleType = (type) => {
333
- if (!type) {
334
- return;
335
- }
336
- const upper = type.toUpperCase();
337
- if (upper === "HOME") {
338
- return "home";
339
- }
340
- if (upper === "WORK" || upper === "BUSINESS") {
341
- return "work";
342
- }
343
- return "other";
344
- };
345
- var waNameToSpectrum = (name) => {
346
- const result = { formatted: name.formattedName };
347
- if (name.firstName) {
348
- result.first = name.firstName;
349
- }
350
- if (name.lastName) {
351
- result.last = name.lastName;
352
- }
353
- if (name.middleName) {
354
- result.middle = name.middleName;
355
- }
356
- if (name.prefix) {
357
- result.prefix = name.prefix;
358
- }
359
- if (name.suffix) {
360
- result.suffix = name.suffix;
361
- }
362
- return result;
363
- };
364
- var waPhoneToSpectrum = (phone) => {
365
- const entry = { value: phone.phone };
366
- const type = mapWaPhoneType(phone.type);
367
- if (type) {
368
- entry.type = type;
369
- }
370
- return entry;
371
- };
372
- var waEmailToSpectrum = (email) => {
373
- const entry = { value: email.email };
374
- const type = mapWaSimpleType(email.type);
375
- if (type) {
376
- entry.type = type;
377
- }
378
- return entry;
379
- };
380
- var waAddressToSpectrum = (address) => {
381
- const entry = {};
382
- if (address.street) {
383
- entry.street = address.street;
384
- }
385
- if (address.city) {
386
- entry.city = address.city;
387
- }
388
- if (address.state) {
389
- entry.region = address.state;
390
- }
391
- if (address.zip) {
392
- entry.postalCode = address.zip;
393
- }
394
- if (address.country) {
395
- entry.country = address.country;
396
- }
397
- const type = mapWaSimpleType(address.type);
398
- if (type) {
399
- entry.type = type;
400
- }
401
- return entry;
402
- };
403
- var waOrgToSpectrum = (org) => {
404
- const entry = {};
405
- if (org.company) {
406
- entry.name = org.company;
407
- }
408
- if (org.title) {
409
- entry.title = org.title;
410
- }
411
- if (org.department) {
412
- entry.department = org.department;
413
- }
414
- return entry;
415
- };
416
- var waContactToSpectrum = (card) => {
417
- const input = { raw: card };
418
- input.name = waNameToSpectrum(card.name);
419
- if (card.phones.length > 0) {
420
- input.phones = card.phones.map(waPhoneToSpectrum);
421
- }
422
- if (card.emails.length > 0) {
423
- input.emails = card.emails.map(waEmailToSpectrum);
424
- }
425
- if (card.addresses.length > 0) {
426
- input.addresses = card.addresses.map(waAddressToSpectrum);
427
- }
428
- if (card.org) {
429
- input.org = waOrgToSpectrum(card.org);
430
- }
431
- if (card.urls.length > 0) {
432
- input.urls = card.urls.map((u) => u.url);
433
- }
434
- if (card.birthday) {
435
- input.birthday = card.birthday;
436
- }
437
- return asContact(input);
438
- };
439
- var toMessages = (client, msg) => {
440
- const base = {
441
- sender: { id: msg.from },
442
- space: { id: msg.from },
443
- timestamp: msg.timestamp
444
- };
445
- if (msg.content.type === "contacts") {
446
- const multi = msg.content.contacts.length > 1;
447
- return msg.content.contacts.map((card, index) => ({
448
- ...base,
449
- id: multi ? `${msg.id}:${index}` : msg.id,
450
- content: waContactToSpectrum(card)
451
- }));
452
- }
453
- return [
454
- {
455
- ...base,
456
- id: msg.id,
457
- content: mapContent(client, msg)
458
- }
459
- ];
460
- };
461
- var mapContent = (client, msg) => {
462
- const { content } = msg;
463
- switch (content.type) {
464
- case "text":
465
- return asText(content.body);
466
- case "image":
467
- case "video":
468
- case "audio":
469
- case "document":
470
- return lazyMedia(client, content.media);
471
- case "sticker":
472
- return asCustom({ whatsapp_type: "sticker", ...content.sticker });
473
- case "location":
474
- return asCustom({ whatsapp_type: "location", ...content.location });
475
- case "reaction": {
476
- const stubTarget = {
477
- id: content.reaction.messageId,
478
- content: asCustom({ whatsapp_type: "reaction-target", stub: true })
479
- };
480
- return asReaction({
481
- emoji: content.reaction.emoji,
482
- target: stubTarget
483
- });
484
- }
485
- case "interactive": {
486
- const inter = content.interactive;
487
- if (inter.type === "button_reply" || inter.type === "list_reply") {
488
- const poll = msg.context?.id === void 0 ? void 0 : getPollCache(client).get(msg.context.id);
489
- const optionIndex = optionIndexFromId(inter.reply.id);
490
- const option = optionIndex === void 0 ? void 0 : poll?.options[optionIndex];
491
- if (poll && option) {
492
- return asPollOption({ poll, option, selected: true });
493
- }
494
- }
495
- return asCustom({ whatsapp_type: "interactive", ...inter });
496
- }
497
- case "button":
498
- return asCustom({ whatsapp_type: "button", ...content.button });
499
- case "order":
500
- return asCustom({ whatsapp_type: "order", ...content.order });
501
- case "system":
502
- return asCustom({ whatsapp_type: "system", ...content.system });
503
- default:
504
- return asCustom({ whatsapp_type: "unknown" });
505
- }
506
- };
507
- var fetchMedia = async (client, mediaId) => {
508
- const { url } = await client.media.getUrl(mediaId);
509
- const response = await fetch(url);
510
- if (!response.ok) {
511
- throw new Error(`Media download failed: ${response.status}`);
512
- }
513
- return response;
514
- };
515
- var lazyMedia = (client, media) => asAttachment({
516
- id: media.id,
517
- name: media.filename ?? `media-${media.id}`,
518
- mimeType: media.mimeType,
519
- read: async () => Buffer.from(await (await fetchMedia(client, media.id)).arrayBuffer()),
520
- stream: async () => {
521
- const response = await fetchMedia(client, media.id);
522
- if (!response.body) {
523
- throw new Error("Media response missing body");
524
- }
525
- return response.body;
526
- }
527
- });
528
- var mimeToMediaType = (mimeType) => {
529
- if (mimeType.startsWith("image/")) {
530
- return "image";
531
- }
532
- if (mimeType.startsWith("video/")) {
533
- return "video";
534
- }
535
- if (mimeType.startsWith("audio/")) {
536
- return "audio";
537
- }
538
- return "document";
539
- };
540
- var voiceFilename = (content) => {
541
- if (content.name) {
542
- return content.name;
543
- }
544
- const ext = mimeExtension(content.mimeType);
545
- return ext ? `voice.${ext}` : "voice";
546
- };
547
- var spectrumPhoneTypeToWa = (type) => {
548
- if (type === "mobile") {
549
- return "CELL";
550
- }
551
- if (type === "home" || type === "work" || type === "other") {
552
- return type.toUpperCase();
553
- }
554
- return;
555
- };
556
- var spectrumSimpleTypeToWa = (type) => type ? type.toUpperCase() : void 0;
557
- var spectrumNameToWa = (name) => ({
558
- formattedName: name?.formatted ?? ([name?.first, name?.middle, name?.last].filter((p) => Boolean(p)).join(" ") || "Unknown"),
559
- firstName: name?.first,
560
- lastName: name?.last,
561
- middleName: name?.middle,
562
- prefix: name?.prefix,
563
- suffix: name?.suffix
564
- });
565
- var isWhatsAppContactCard = (value) => {
566
- if (!value || typeof value !== "object") {
567
- return false;
568
- }
569
- const raw = value;
570
- const name = raw.name;
571
- if (!name || typeof name !== "object" || typeof name.formattedName !== "string") {
572
- return false;
573
- }
574
- return Array.isArray(raw.phones) && Array.isArray(raw.emails) && Array.isArray(raw.addresses) && Array.isArray(raw.urls);
575
- };
576
- var contactToWa = (contact) => {
577
- if (isWhatsAppContactCard(contact.raw)) {
578
- return contact.raw;
579
- }
580
- const card = {
581
- name: spectrumNameToWa(contact.name),
582
- phones: (contact.phones ?? []).map((p) => ({
583
- phone: p.value,
584
- type: spectrumPhoneTypeToWa(p.type)
585
- })),
586
- emails: (contact.emails ?? []).map((e) => ({
587
- email: e.value,
588
- type: spectrumSimpleTypeToWa(e.type)
589
- })),
590
- addresses: (contact.addresses ?? []).map((a) => ({
591
- street: a.street,
592
- city: a.city,
593
- state: a.region,
594
- zip: a.postalCode,
595
- country: a.country,
596
- type: spectrumSimpleTypeToWa(a.type)
597
- })),
598
- urls: (contact.urls ?? []).map((url) => ({ url })),
599
- org: contact.org?.name || contact.org?.department || contact.org?.title ? {
600
- company: contact.org.name,
601
- department: contact.org.department,
602
- title: contact.org.title
603
- } : void 0,
604
- birthday: contact.birthday
605
- };
606
- return card;
607
- };
608
- var clientStream = (client) => {
609
- const eventStream = client.events.subscribe().filter(
610
- (e) => e.type === "message"
611
- );
612
- return stream((emit, end) => {
613
- const pump = (async () => {
614
- try {
615
- for await (const event of eventStream) {
616
- for (const m of toMessages(client, event.message)) {
617
- await emit(m);
618
- }
619
- }
620
- end();
621
- } catch (e) {
622
- end(e);
623
- }
624
- })();
625
- return async () => {
626
- await eventStream.close();
627
- await pump;
628
- };
629
- });
630
- };
631
- var messages = (clients) => mergeStreams(clients.map(clientStream));
632
- var send = async (clients, spaceId, content) => {
633
- if (content.type === "reply") {
634
- return await replyToMessage(
635
- clients,
636
- spaceId,
637
- content.target.id,
638
- content.content
639
- );
640
- }
641
- if (content.type === "reaction") {
642
- return await reactToMessage(clients, spaceId, content);
643
- }
644
- if (content.type === "typing") {
645
- return;
646
- }
647
- if (content.type === "read") {
648
- await primary(clients).messages.markRead(content.target.id);
649
- return;
650
- }
651
- const client = primary(clients);
652
- switch (content.type) {
653
- case "text":
654
- return toRecord(
655
- await client.messages.send({ to: spaceId, text: content.text }),
656
- spaceId,
657
- content
658
- );
659
- case "attachment": {
660
- const { mediaId } = await client.media.upload({
661
- file: await content.read(),
662
- mimeType: content.mimeType,
663
- filename: content.name
664
- });
665
- const mediaType = mimeToMediaType(content.mimeType);
666
- const mediaPayload = mediaType === "document" ? { id: mediaId, filename: content.name } : { id: mediaId };
667
- return toRecord(
668
- await client.messages.send({
669
- to: spaceId,
670
- [mediaType]: mediaPayload
671
- }),
672
- spaceId,
673
- content
674
- );
675
- }
676
- case "contact":
677
- return toRecord(
678
- await client.messages.send({
679
- to: spaceId,
680
- contacts: [contactToWa(content)]
681
- }),
682
- spaceId,
683
- content
684
- );
685
- case "voice": {
686
- const { mediaId } = await client.media.upload({
687
- file: await content.read(),
688
- mimeType: content.mimeType,
689
- filename: voiceFilename(content)
690
- });
691
- return toRecord(
692
- await client.messages.send({
693
- to: spaceId,
694
- audio: { id: mediaId }
695
- }),
696
- spaceId,
697
- content
698
- );
699
- }
700
- case "poll": {
701
- const result = await client.messages.send({
702
- to: spaceId,
703
- interactive: pollToInteractive(content)
704
- });
705
- cachePoll(client, result.messageId, content);
706
- return toRecord(result, spaceId, content);
707
- }
708
- default:
709
- throw UnsupportedError.content(content.type);
710
- }
711
- };
712
- var reactToMessage = async (clients, spaceId, content) => {
713
- const result = await primary(clients).messages.send({
714
- to: spaceId,
715
- reaction: { messageId: content.target.id, emoji: content.emoji }
716
- });
717
- return toRecord(result, spaceId, content);
718
- };
719
- var replyToMessage = async (clients, spaceId, messageId, content) => {
720
- const client = primary(clients);
721
- switch (content.type) {
722
- case "text":
723
- return toRecord(
724
- await client.messages.send({
725
- to: spaceId,
726
- replyTo: messageId,
727
- text: content.text
728
- }),
729
- spaceId,
730
- content
731
- );
732
- case "attachment": {
733
- const { mediaId } = await client.media.upload({
734
- file: await content.read(),
735
- mimeType: content.mimeType,
736
- filename: content.name
737
- });
738
- const mediaType = mimeToMediaType(content.mimeType);
739
- const mediaPayload = mediaType === "document" ? { id: mediaId, filename: content.name } : { id: mediaId };
740
- return toRecord(
741
- await client.messages.send({
742
- to: spaceId,
743
- replyTo: messageId,
744
- [mediaType]: mediaPayload
745
- }),
746
- spaceId,
747
- content
748
- );
749
- }
750
- case "contact":
751
- return toRecord(
752
- await client.messages.send({
753
- to: spaceId,
754
- replyTo: messageId,
755
- contacts: [contactToWa(content)]
756
- }),
757
- spaceId,
758
- content
759
- );
760
- case "voice": {
761
- const { mediaId } = await client.media.upload({
762
- file: await content.read(),
763
- mimeType: content.mimeType,
764
- filename: voiceFilename(content)
765
- });
766
- return toRecord(
767
- await client.messages.send({
768
- to: spaceId,
769
- replyTo: messageId,
770
- audio: { id: mediaId }
771
- }),
772
- spaceId,
773
- content
774
- );
775
- }
776
- case "poll": {
777
- const result = await client.messages.send({
778
- to: spaceId,
779
- replyTo: messageId,
780
- interactive: pollToInteractive(content)
781
- });
782
- cachePoll(client, result.messageId, content);
783
- return toRecord(result, spaceId, content);
784
- }
785
- default:
786
- throw UnsupportedError.content(content.type);
787
- }
788
- };
789
-
790
- // src/providers/whatsapp-business/types.ts
791
- import z from "zod";
792
- var directConfig = z.object({
793
- accessToken: z.string().min(1),
794
- appSecret: z.string().optional(),
795
- phoneNumberId: z.string().min(1)
796
- });
797
- var cloudConfig = z.object({}).strict();
798
- var configSchema = z.union([directConfig, cloudConfig]);
799
- var isCloudConfig = (config) => !("accessToken" in config);
800
- var userSchema = z.object({});
801
- var spaceSchema = z.object({
802
- id: z.string()
803
- });
804
-
805
- // src/providers/whatsapp-business/index.ts
806
- var whatsappBusiness = definePlatform("WhatsApp Business", {
807
- config: configSchema,
808
- lifecycle: {
809
- createClient: async ({
810
- config,
811
- projectId,
812
- projectSecret
813
- }) => {
814
- if (!isCloudConfig(config)) {
815
- return [
816
- createClient2({
817
- accessToken: config.accessToken,
818
- appSecret: config.appSecret ?? "",
819
- phoneNumberId: config.phoneNumberId
820
- })
821
- ];
822
- }
823
- if (!(projectId && projectSecret)) {
824
- throw new Error(
825
- "WhatsApp Business cloud mode requires projectId and projectSecret. Either pass credentials to Spectrum(), or provide direct credentials: whatsappBusiness.config({ accessToken, phoneNumberId })"
826
- );
827
- }
828
- return await createCloudClients(projectId, projectSecret);
829
- },
830
- destroyClient: async ({ client }) => {
831
- await disposeCloudAuth(client);
832
- await Promise.all(client.map((c) => c.close()));
833
- }
834
- },
835
- user: {
836
- resolve: async ({ input }) => ({ id: input.userID })
837
- },
838
- space: {
839
- schema: spaceSchema,
840
- create: async ({ input }) => {
841
- if (input.users.length === 0) {
842
- throw new Error("WhatsApp space creation requires at least one user");
843
- }
844
- if (input.users.length > 1) {
845
- throw UnsupportedError.action(
846
- "space.create",
847
- "WhatsApp Business",
848
- "only 1:1 conversations are supported"
849
- );
850
- }
851
- const user = input.users[0];
852
- if (!user) {
853
- throw new Error("WhatsApp space creation requires a user");
854
- }
855
- return { id: user.id };
856
- }
857
- },
858
- messages: ({ client }) => messages(client),
859
- send: async ({ space, content, client }) => await send(client, space.id, content)
860
- });
861
-
862
- export {
863
- whatsappBusiness
864
- };