@sinch/functions-runtime 0.1.0-beta.28

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/index.js ADDED
@@ -0,0 +1,1946 @@
1
+ // ../runtime-shared/dist/builders/svaml.js
2
+ var BaseSvamlBuilder = class {
3
+ instructions = [];
4
+ action = null;
5
+ /**
6
+ * Add a text-to-speech instruction
7
+ *
8
+ * @param text - Text to speak
9
+ * @param locale - Voice locale (e.g., 'en-US')
10
+ */
11
+ say(text, locale = "en-US") {
12
+ const instruction = { name: "say", text, locale };
13
+ this.instructions.push(instruction);
14
+ return this;
15
+ }
16
+ /**
17
+ * Play an audio file
18
+ *
19
+ * @param url - URL of the audio file to play
20
+ */
21
+ play(url) {
22
+ const instruction = { name: "play", url };
23
+ this.instructions.push(instruction);
24
+ return this;
25
+ }
26
+ /**
27
+ * Play multiple audio files
28
+ *
29
+ * @param urls - Array of audio file URLs
30
+ * @param locale - Locale for any TTS prompts
31
+ */
32
+ playFiles(urls, locale = "en-US") {
33
+ const instruction = { name: "playFiles", ids: urls, locale };
34
+ this.instructions.push(instruction);
35
+ return this;
36
+ }
37
+ /**
38
+ * Start recording the call
39
+ *
40
+ * @param options - Recording options
41
+ */
42
+ startRecording(options) {
43
+ const instruction = {
44
+ name: "startRecording",
45
+ options
46
+ };
47
+ this.instructions.push(instruction);
48
+ return this;
49
+ }
50
+ /**
51
+ * Stop recording the call
52
+ */
53
+ stopRecording() {
54
+ const instruction = { name: "stopRecording" };
55
+ this.instructions.push(instruction);
56
+ return this;
57
+ }
58
+ /**
59
+ * Set a cookie (persists data across callbacks)
60
+ *
61
+ * @param key - Cookie key
62
+ * @param value - Cookie value
63
+ */
64
+ setCookie(key, value) {
65
+ const instruction = { name: "setCookie", key, value };
66
+ this.instructions.push(instruction);
67
+ return this;
68
+ }
69
+ /**
70
+ * Send DTMF tones
71
+ *
72
+ * @param digits - DTMF digits to send
73
+ */
74
+ sendDtmf(digits) {
75
+ const instruction = { name: "sendDtmf", value: digits };
76
+ this.instructions.push(instruction);
77
+ return this;
78
+ }
79
+ /**
80
+ * Hang up the call
81
+ */
82
+ hangup() {
83
+ this.action = { name: "hangup" };
84
+ return this;
85
+ }
86
+ /**
87
+ * Continue to next callback
88
+ */
89
+ continue() {
90
+ this.action = { name: "continue" };
91
+ return this;
92
+ }
93
+ /**
94
+ * Build the final SVAML response
95
+ *
96
+ * @throws Error if no action has been set
97
+ */
98
+ build() {
99
+ if (!this.action) {
100
+ throw new Error("SVAML response requires an action. Call hangup(), continue(), connectPstn(), etc.");
101
+ }
102
+ return {
103
+ instructions: this.instructions.length > 0 ? this.instructions : void 0,
104
+ action: this.action
105
+ };
106
+ }
107
+ };
108
+ var IceSvamlBuilder = class extends BaseSvamlBuilder {
109
+ /**
110
+ * Answer the call explicitly
111
+ */
112
+ answer() {
113
+ const instruction = { name: "answer" };
114
+ this.instructions.push(instruction);
115
+ return this;
116
+ }
117
+ /**
118
+ * Connect to a PSTN phone number
119
+ *
120
+ * @param number - E.164 formatted phone number (e.g., '+15551234567')
121
+ * @param options - Connection options
122
+ */
123
+ connectPstn(number, options) {
124
+ this.action = {
125
+ name: "connectPstn",
126
+ number,
127
+ ...options
128
+ };
129
+ return this;
130
+ }
131
+ /**
132
+ * Connect to a SIP endpoint
133
+ *
134
+ * @param destination - SIP URI
135
+ * @param options - Connection options
136
+ */
137
+ connectSip(destination, options) {
138
+ this.action = {
139
+ name: "connectSip",
140
+ destination,
141
+ ...options
142
+ };
143
+ return this;
144
+ }
145
+ /**
146
+ * Connect to an MXP (Sinch SDK) user
147
+ *
148
+ * @param destination - MXP user identifier
149
+ * @param options - Connection options
150
+ */
151
+ connectMxp(destination, options) {
152
+ this.action = {
153
+ name: "connectMxp",
154
+ destination,
155
+ ...options
156
+ };
157
+ return this;
158
+ }
159
+ /**
160
+ * Join a conference
161
+ *
162
+ * @param conferenceId - Conference ID to join
163
+ * @param options - Conference options
164
+ */
165
+ connectConf(conferenceId, options) {
166
+ this.action = {
167
+ name: "connectConf",
168
+ conferenceId,
169
+ ...options
170
+ };
171
+ return this;
172
+ }
173
+ /**
174
+ * Run an IVR menu
175
+ *
176
+ * @param menus - Array of menu definitions or MenuStructure from createMenu().build()
177
+ * @param options - Menu options
178
+ */
179
+ runMenu(menus, options) {
180
+ const menuArray = Array.isArray(menus) ? menus : menus.menus;
181
+ const barge = Array.isArray(menus) ? options?.barge : menus.barge;
182
+ this.action = {
183
+ name: "runMenu",
184
+ menus: menuArray,
185
+ barge,
186
+ ...options
187
+ };
188
+ return this;
189
+ }
190
+ /**
191
+ * Park the call (put on hold)
192
+ *
193
+ * @param holdPrompt - Prompt to play while on hold
194
+ * @param options - Park options
195
+ */
196
+ park(holdPrompt, options) {
197
+ this.action = {
198
+ name: "park",
199
+ holdPrompt,
200
+ ...options
201
+ };
202
+ return this;
203
+ }
204
+ };
205
+ var AceSvamlBuilder = class extends BaseSvamlBuilder {
206
+ };
207
+ var PieSvamlBuilder = class extends BaseSvamlBuilder {
208
+ /**
209
+ * Connect to a PSTN phone number
210
+ *
211
+ * @param number - E.164 formatted phone number
212
+ * @param options - Connection options
213
+ */
214
+ connectPstn(number, options) {
215
+ this.action = {
216
+ name: "connectPstn",
217
+ number,
218
+ ...options
219
+ };
220
+ return this;
221
+ }
222
+ /**
223
+ * Run an IVR menu
224
+ *
225
+ * @param menus - Array of menu definitions or MenuStructure from createMenu().build()
226
+ * @param options - Menu options
227
+ */
228
+ runMenu(menus, options) {
229
+ const menuArray = Array.isArray(menus) ? menus : menus.menus;
230
+ const barge = Array.isArray(menus) ? options?.barge : menus.barge;
231
+ this.action = {
232
+ name: "runMenu",
233
+ menus: menuArray,
234
+ barge,
235
+ ...options
236
+ };
237
+ return this;
238
+ }
239
+ /**
240
+ * Park the call (put on hold)
241
+ *
242
+ * @param holdPrompt - Prompt to play while on hold
243
+ * @param options - Park options
244
+ */
245
+ park(holdPrompt, options) {
246
+ this.action = {
247
+ name: "park",
248
+ holdPrompt,
249
+ ...options
250
+ };
251
+ return this;
252
+ }
253
+ };
254
+ function createIceBuilder() {
255
+ return new IceSvamlBuilder();
256
+ }
257
+ function createAceBuilder() {
258
+ return new AceSvamlBuilder();
259
+ }
260
+ function createPieBuilder() {
261
+ return new PieSvamlBuilder();
262
+ }
263
+
264
+ // ../runtime-shared/dist/builders/menu.js
265
+ var VALID_DTMF_KEYS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"];
266
+ var MenuBuilder = class {
267
+ menuStructure;
268
+ currentMenu;
269
+ /**
270
+ * Create a new MenuBuilder
271
+ *
272
+ * @param id - Menu ID (must be 'main' for root menu)
273
+ * @throws Error if id is not 'main' for root menu
274
+ */
275
+ constructor(id = "main") {
276
+ if (id !== "main") {
277
+ throw new Error("Menu structure must start with 'main' menu as id");
278
+ }
279
+ this.menuStructure = {
280
+ barge: true,
281
+ menus: []
282
+ };
283
+ this.currentMenu = {
284
+ id,
285
+ mainPrompt: "",
286
+ options: [],
287
+ repeats: 2,
288
+ maxDigits: 1,
289
+ timeoutMills: 8e3
290
+ };
291
+ }
292
+ /**
293
+ * Set the main prompt for the menu
294
+ *
295
+ * @param text - The prompt text (will be wrapped in TTS tags)
296
+ * @param locale - Voice locale (default: 'en-US')
297
+ */
298
+ prompt(text, locale = "en-US") {
299
+ this.currentMenu.mainPrompt = `#tts[${text}]`;
300
+ this.currentMenu.locale = locale;
301
+ return this;
302
+ }
303
+ /**
304
+ * Set the repeat prompt (played when user doesn't respond)
305
+ *
306
+ * @param text - The repeat prompt text
307
+ */
308
+ repeatPrompt(text) {
309
+ this.currentMenu.repeatPrompt = `#tts[${text}]`;
310
+ return this;
311
+ }
312
+ /**
313
+ * Add a menu option
314
+ *
315
+ * @param dtmf - The DTMF key (0-9, *, #)
316
+ * @param action - Action: 'return', 'return(value)', or 'menu(menuid)'
317
+ * @throws Error if DTMF key or action is invalid
318
+ */
319
+ option(dtmf, action = "return") {
320
+ if (!this.isValidDtmf(dtmf)) {
321
+ throw new Error(`Invalid DTMF key: ${dtmf}. Must be 0-9, *, or #`);
322
+ }
323
+ if (!this.isValidAction(action)) {
324
+ throw new Error(`Invalid action: ${action}. Must be 'return', 'return(value)', or 'menu(menuid)'`);
325
+ }
326
+ const menuOption = {
327
+ dtmf: dtmf.toString(),
328
+ action
329
+ };
330
+ this.currentMenu.options.push(menuOption);
331
+ return this;
332
+ }
333
+ /**
334
+ * Set maximum number of digits to collect
335
+ *
336
+ * @param digits - Maximum digits (1-20)
337
+ * @throws Error if digits is out of range
338
+ */
339
+ maxDigits(digits) {
340
+ if (digits < 1 || digits > 20) {
341
+ throw new Error("maxDigits must be between 1 and 20");
342
+ }
343
+ this.currentMenu.maxDigits = digits;
344
+ return this;
345
+ }
346
+ /**
347
+ * Set timeout in milliseconds
348
+ *
349
+ * @param ms - Timeout in milliseconds (1000-30000)
350
+ * @throws Error if timeout is out of range
351
+ */
352
+ timeout(ms) {
353
+ if (ms < 1e3 || ms > 3e4) {
354
+ throw new Error("Timeout must be between 1000ms and 30000ms");
355
+ }
356
+ this.currentMenu.timeoutMills = ms;
357
+ return this;
358
+ }
359
+ /**
360
+ * Set number of times to repeat the menu
361
+ *
362
+ * @param count - Number of repeats (0-5)
363
+ * @throws Error if count is out of range
364
+ */
365
+ repeats(count) {
366
+ if (count < 0 || count > 5) {
367
+ throw new Error("Repeats must be between 0 and 5");
368
+ }
369
+ this.currentMenu.repeats = count;
370
+ return this;
371
+ }
372
+ /**
373
+ * Enable/disable barge-in (allow interrupting prompts with input)
374
+ *
375
+ * @param enabled - Whether to allow barge-in (default: true)
376
+ */
377
+ barge(enabled = true) {
378
+ this.menuStructure.barge = enabled;
379
+ return this;
380
+ }
381
+ /**
382
+ * Add the current menu and start a new submenu
383
+ *
384
+ * @param id - Menu ID for the submenu
385
+ * @throws Error if submenu ID is invalid
386
+ */
387
+ addSubmenu(id) {
388
+ if (!id || typeof id !== "string") {
389
+ throw new Error("Submenu ID must be a non-empty string");
390
+ }
391
+ this.menuStructure.menus.push({ ...this.currentMenu });
392
+ this.currentMenu = {
393
+ id,
394
+ mainPrompt: "",
395
+ options: [],
396
+ repeats: 2,
397
+ maxDigits: 1,
398
+ timeoutMills: 8e3
399
+ };
400
+ return this;
401
+ }
402
+ /**
403
+ * Build and return the complete menu structure
404
+ *
405
+ * @throws Error if menu structure is invalid
406
+ */
407
+ build() {
408
+ if (this.currentMenu.options.length > 0) {
409
+ this.menuStructure.menus.push({ ...this.currentMenu });
410
+ }
411
+ if (this.menuStructure.menus.length === 0) {
412
+ throw new Error("Menu must have at least one menu with options");
413
+ }
414
+ if (this.menuStructure.menus[0].id !== "main") {
415
+ throw new Error("First menu must have id 'main'");
416
+ }
417
+ return this.menuStructure;
418
+ }
419
+ /**
420
+ * Get just the menus array (for use with runMenu action)
421
+ */
422
+ buildMenus() {
423
+ return this.build().menus;
424
+ }
425
+ /**
426
+ * Validate DTMF key format
427
+ */
428
+ isValidDtmf(dtmf) {
429
+ return VALID_DTMF_KEYS.includes(dtmf.toString());
430
+ }
431
+ /**
432
+ * Validate action format
433
+ */
434
+ isValidAction(action) {
435
+ if (action === "return")
436
+ return true;
437
+ if (action.startsWith("return(") && action.endsWith(")"))
438
+ return true;
439
+ if (action.startsWith("menu(") && action.endsWith(")"))
440
+ return true;
441
+ return false;
442
+ }
443
+ };
444
+ function createMenu(id = "main") {
445
+ return new MenuBuilder(id);
446
+ }
447
+ function createSimpleMenu(prompt, options = []) {
448
+ const builder = createMenu().prompt(prompt);
449
+ for (const { dtmf, action } of options) {
450
+ builder.option(dtmf, action);
451
+ }
452
+ return builder.build();
453
+ }
454
+ var MenuTemplates = {
455
+ /**
456
+ * Standard business menu (Sales, Support, Operator)
457
+ *
458
+ * @param companyName - Company name for greeting
459
+ */
460
+ business(companyName = "Our Company") {
461
+ return createMenu().prompt(`Thank you for calling ${companyName}! Press 1 for sales, 2 for support, or 0 for an operator.`).repeatPrompt("Press 1 for sales, 2 for support, or 0 for operator.").option("1", "return(sales)").option("2", "return(support)").option("0", "return(operator)").build();
462
+ },
463
+ /**
464
+ * Yes/No confirmation menu
465
+ *
466
+ * @param question - Question to ask
467
+ */
468
+ yesNo(question = "Please make your selection") {
469
+ return createMenu().prompt(`${question} Press 1 for Yes, or 2 for No.`).repeatPrompt("Press 1 for Yes, or 2 for No.").option("1", "return(yes)").option("2", "return(no)").build();
470
+ },
471
+ /**
472
+ * Language selection menu
473
+ *
474
+ * @param languages - Language options
475
+ */
476
+ language(languages = [
477
+ { dtmf: "1", name: "English", value: "en-US" },
478
+ { dtmf: "2", name: "Spanish", value: "es-ES" }
479
+ ]) {
480
+ const promptText = languages.map((lang) => `Press ${lang.dtmf} for ${lang.name}`).join(", ") + ".";
481
+ const builder = createMenu().prompt(promptText).repeatPrompt(promptText);
482
+ for (const lang of languages) {
483
+ builder.option(lang.dtmf, `return(${lang.value})`);
484
+ }
485
+ return builder.build();
486
+ },
487
+ /**
488
+ * After hours menu
489
+ *
490
+ * @param companyName - Company name
491
+ * @param businessHours - Business hours text
492
+ */
493
+ afterHours(companyName = "Our Company", businessHours = "9 AM to 5 PM, Monday through Friday") {
494
+ return createMenu().prompt(`Thank you for calling ${companyName}. Our office hours are ${businessHours}. Press 1 to leave a voicemail, 2 for our website, or 9 for emergency support.`).repeatPrompt("Press 1 for voicemail, 2 for website, or 9 for emergency support.").option("1", "return(voicemail)").option("2", "return(website)").option("9", "return(emergency)").timeout(1e4).build();
495
+ },
496
+ /**
497
+ * Recording consent menu
498
+ */
499
+ recordingConsent() {
500
+ return createMenu().prompt("For quality and training purposes, this call may be recorded. Press 1 to continue with recording, or press 2 to opt out.").repeatPrompt("Press 1 to allow recording, or press 2 to opt out.").option("1", "return(consent)").option("2", "return(no_consent)").timeout(1e4).build();
501
+ },
502
+ /**
503
+ * Numeric input menu (for collecting multi-digit input like account numbers)
504
+ *
505
+ * @param prompt - Instructions for user
506
+ * @param digits - Number of digits to collect (default: 4)
507
+ */
508
+ numericInput(prompt, digits = 4) {
509
+ return createMenu().prompt(`${prompt} Then press pound.`).repeatPrompt(`Please enter your ${digits} digit number, then press pound.`).maxDigits(digits).option("#", "return").timeout(15e3).build();
510
+ }
511
+ };
512
+
513
+ // ../runtime-shared/dist/config/universal.js
514
+ var UniversalConfig = class {
515
+ context;
516
+ env;
517
+ /**
518
+ * Create a new UniversalConfig instance
519
+ *
520
+ * @param context - Function execution context
521
+ */
522
+ constructor(context) {
523
+ this.context = context;
524
+ this.env = context.env || process.env;
525
+ }
526
+ /**
527
+ * Get a secret value (e.g., API keys, passwords, tokens)
528
+ *
529
+ * @param name - Secret name (e.g., 'PROJECT_ID_API_SECRET')
530
+ * @param defaultValue - Optional default value if secret not found
531
+ */
532
+ getSecret(name, defaultValue = null) {
533
+ return this.env[name] ?? defaultValue;
534
+ }
535
+ /**
536
+ * Get an environment variable (non-secret configuration)
537
+ *
538
+ * @param name - Variable name (e.g., 'COMPANY_NAME', 'API_URL')
539
+ * @param defaultValue - Optional default value
540
+ */
541
+ getVariable(name, defaultValue = null) {
542
+ return this.env[name] ?? defaultValue;
543
+ }
544
+ /**
545
+ * Check if a secret exists (has a value)
546
+ *
547
+ * @param name - Secret name
548
+ */
549
+ hasSecret(name) {
550
+ return !!this.env[name];
551
+ }
552
+ /**
553
+ * Check if a variable exists (has a value)
554
+ *
555
+ * @param name - Variable name
556
+ */
557
+ hasVariable(name) {
558
+ return !!this.env[name];
559
+ }
560
+ /**
561
+ * Get required secret (throws error if not found)
562
+ *
563
+ * @param name - Secret name
564
+ * @throws Error if secret is not found
565
+ */
566
+ requireSecret(name) {
567
+ const value = this.getSecret(name);
568
+ if (!value) {
569
+ throw new Error(`Required secret '${name}' not found in environment`);
570
+ }
571
+ return value;
572
+ }
573
+ /**
574
+ * Get required variable (throws error if not found)
575
+ *
576
+ * @param name - Variable name
577
+ * @throws Error if variable is not found
578
+ */
579
+ requireVariable(name) {
580
+ const value = this.getVariable(name);
581
+ if (!value) {
582
+ throw new Error(`Required variable '${name}' not found in environment`);
583
+ }
584
+ return value;
585
+ }
586
+ /**
587
+ * Get Sinch application credentials
588
+ */
589
+ getApplicationCredentials() {
590
+ const key = this.env.VOICE_APPLICATION_KEY;
591
+ const secret = this.env.VOICE_APPLICATION_SECRET;
592
+ if (key && secret) {
593
+ return { applicationKey: key, applicationSecret: secret };
594
+ }
595
+ return null;
596
+ }
597
+ /**
598
+ * Check if running in development mode
599
+ */
600
+ isDevelopment() {
601
+ return this.env.NODE_ENV !== "production";
602
+ }
603
+ /**
604
+ * Check if running in production mode
605
+ */
606
+ isProduction() {
607
+ return this.env.NODE_ENV === "production";
608
+ }
609
+ /**
610
+ * Get cache client instance
611
+ */
612
+ getCache() {
613
+ return this.context.cache;
614
+ }
615
+ /**
616
+ * Get comprehensive configuration summary (useful for debugging)
617
+ * Excludes actual secret values for security
618
+ */
619
+ getConfigSummary() {
620
+ const secretKeys = [
621
+ "PROJECT_ID_API_SECRET",
622
+ "VOICE_APPLICATION_SECRET",
623
+ "DATABASE_PASSWORD",
624
+ "API_KEY"
625
+ ];
626
+ const summary = {
627
+ environment: this.isProduction() ? "production" : "development",
628
+ variables: {},
629
+ secrets: {},
630
+ hasApplicationCredentials: !!this.getApplicationCredentials()
631
+ };
632
+ for (const [key, value] of Object.entries(this.env)) {
633
+ if (secretKeys.some((secretKey) => key.includes(secretKey))) {
634
+ summary.secrets[key] = value ? "[REDACTED]" : "[NOT SET]";
635
+ } else if (["NODE_ENV", "PORT", "SINCH_REGION"].includes(key)) {
636
+ summary.variables[key] = value;
637
+ }
638
+ }
639
+ return summary;
640
+ }
641
+ };
642
+ function createConfig(context) {
643
+ return new UniversalConfig(context);
644
+ }
645
+ var createUniversalConfig = createConfig;
646
+
647
+ // ../runtime-shared/dist/host/middleware.js
648
+ import { createRequire } from "module";
649
+ var requireCjs = createRequire(import.meta.url);
650
+ var defaultOptions = {
651
+ limit: "10mb",
652
+ allowJson5: true,
653
+ camelCase: {
654
+ deep: true,
655
+ stopPaths: ["custom", "applicationkey"],
656
+ exclude: []
657
+ }
658
+ };
659
+ var SPECIAL_CASES = {
660
+ callid: "callId",
661
+ applicationkey: "applicationKey",
662
+ callbackurl: "callbackUrl",
663
+ clinumber: "cliNumber",
664
+ menuResult: "menuResult",
665
+ userid: "userId",
666
+ username: "userName",
667
+ callleg: "callLeg",
668
+ conferenceid: "conferenceId",
669
+ participantid: "participantId"
670
+ };
671
+ function toCamelCase(str) {
672
+ if (!str)
673
+ return str;
674
+ if (str.includes("_") || str.includes("-")) {
675
+ return str.replace(/[-_]([a-z])/g, (_, char) => char.toUpperCase());
676
+ }
677
+ if (/[a-z][A-Z]/.test(str)) {
678
+ return str;
679
+ }
680
+ const lower = str.toLowerCase();
681
+ if (SPECIAL_CASES[lower]) {
682
+ return SPECIAL_CASES[lower];
683
+ }
684
+ return str;
685
+ }
686
+ function transformKeys(obj, options = {}) {
687
+ const { stopPaths = [], exclude = [], deep = true } = options;
688
+ if (obj === null || obj === void 0) {
689
+ return obj;
690
+ }
691
+ if (Array.isArray(obj)) {
692
+ return deep ? obj.map((item) => transformKeys(item, options)) : obj;
693
+ }
694
+ if (typeof obj !== "object") {
695
+ return obj;
696
+ }
697
+ const result = {};
698
+ const DANGEROUS_KEYS = ["__proto__", "constructor", "prototype"];
699
+ for (const [key, value] of Object.entries(obj)) {
700
+ if (DANGEROUS_KEYS.includes(key)) {
701
+ continue;
702
+ }
703
+ let newKey = key;
704
+ let shouldTransformValue = deep;
705
+ const isExcluded = exclude.some((pattern) => {
706
+ if (pattern instanceof RegExp) {
707
+ return pattern.test(key);
708
+ }
709
+ return pattern === key;
710
+ });
711
+ if (!isExcluded) {
712
+ newKey = toCamelCase(key);
713
+ }
714
+ if (DANGEROUS_KEYS.includes(newKey)) {
715
+ continue;
716
+ }
717
+ if (stopPaths.includes(newKey)) {
718
+ shouldTransformValue = false;
719
+ }
720
+ result[newKey] = shouldTransformValue ? transformKeys(value, options) : value;
721
+ }
722
+ return result;
723
+ }
724
+ function parseJson(text, allowJson5 = true) {
725
+ if (!text || typeof text !== "string") {
726
+ return {};
727
+ }
728
+ const trimmed = text.trim();
729
+ if (!trimmed) {
730
+ return {};
731
+ }
732
+ try {
733
+ return JSON.parse(trimmed);
734
+ } catch (e) {
735
+ if (!allowJson5) {
736
+ throw new Error(`Invalid JSON: ${e.message}`);
737
+ }
738
+ try {
739
+ const cleaned = trimmed.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
740
+ return JSON.parse(cleaned);
741
+ } catch (e2) {
742
+ throw new Error(`Invalid JSON/JSON5: ${e.message}`);
743
+ }
744
+ }
745
+ }
746
+ function createLenientJsonParser(options = {}) {
747
+ const opts = { ...defaultOptions, ...options };
748
+ return function lenientJsonParser(req, res, next) {
749
+ if (req.body && typeof req.body === "object" && !Buffer.isBuffer(req.body)) {
750
+ if (!req._keysTransformed) {
751
+ req.body = transformKeys(req.body, opts.camelCase);
752
+ req._keysTransformed = true;
753
+ }
754
+ return next();
755
+ }
756
+ let raw = "";
757
+ if (typeof req.body === "string") {
758
+ raw = req.body;
759
+ req.rawBody = raw;
760
+ } else if (Buffer.isBuffer(req.body)) {
761
+ raw = req.body.toString("utf8");
762
+ req.rawBody = raw;
763
+ } else {
764
+ req.body = {};
765
+ return next();
766
+ }
767
+ try {
768
+ let parsed = parseJson(raw, opts.allowJson5);
769
+ parsed = transformKeys(parsed, opts.camelCase);
770
+ req._keysTransformed = true;
771
+ req.body = parsed;
772
+ next();
773
+ } catch (error) {
774
+ res.status(400).json({
775
+ error: "Invalid JSON in request body",
776
+ message: error.message,
777
+ hint: opts.allowJson5 ? "This endpoint accepts JSON and JSON5 (comments and trailing commas allowed)" : "This endpoint requires strict JSON format"
778
+ });
779
+ }
780
+ };
781
+ }
782
+ function setupJsonParsing(app, options = {}) {
783
+ const express = requireCjs("express");
784
+ app.use(express.text({
785
+ type: ["application/json", "application/*+json", "text/json"],
786
+ limit: options.limit || "10mb"
787
+ }));
788
+ app.use(createLenientJsonParser(options));
789
+ return app;
790
+ }
791
+
792
+ // ../runtime-shared/dist/host/app.js
793
+ import { createRequire as createRequire2 } from "module";
794
+ import { pathToFileURL } from "url";
795
+ import * as nodePath from "path";
796
+ var requireCjs2 = createRequire2(import.meta.url);
797
+ var LANDING_PAGE_HTML = `<!DOCTYPE html>
798
+ <html lang="en">
799
+ <head>
800
+ <meta charset="UTF-8">
801
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
802
+ <title>Sinch Function</title>
803
+ <link rel="icon" type="image/svg+xml" href="/favicon.ico">
804
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
805
+ <style>
806
+ * { margin: 0; padding: 0; box-sizing: border-box; }
807
+ body { font-family: "DM Sans", Arial, sans-serif; min-height: 100vh; overflow: hidden; background: #ffffff; }
808
+ #gradient-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; }
809
+ .content { position: relative; z-index: 10; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem; mix-blend-mode: difference; }
810
+ .icon-wrapper { width: 120px; height: 120px; margin-bottom: 2rem; animation: float 6s ease-in-out infinite; }
811
+ @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
812
+ .icon-wrapper svg { width: 100%; height: 100%; fill: white; }
813
+ .title { font-size: clamp(32px, 6vw, 56px); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; color: white; text-align: center; max-width: 600px; margin-bottom: 1.5rem; }
814
+ .status { display: inline-flex; align-items: center; gap: 0.75rem; font-size: 18px; font-weight: 500; color: white; opacity: 0.9; }
815
+ .status-dot { width: 10px; height: 10px; background: white; border-radius: 50%; position: relative; }
816
+ .status-dot::before { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid white; animation: ripple 2s ease-out infinite; }
817
+ @keyframes ripple { 0% { transform: scale(1); opacity: 0.6; } 100% { transform: scale(2); opacity: 0; } }
818
+ footer { position: fixed; bottom: 0; left: 0; right: 0; padding: 1.5rem 2rem; text-align: center; z-index: 10; mix-blend-mode: difference; }
819
+ .footer-content { display: flex; align-items: center; justify-content: center; gap: 0.5rem; font-size: 13px; color: white; opacity: 0.7; }
820
+ .footer-content svg { height: 16px; width: auto; fill: white; }
821
+ @media (prefers-reduced-motion: reduce) { .icon-wrapper { animation: none; } .status-dot::before { animation: none; display: none; } }
822
+ </style>
823
+ </head>
824
+ <body>
825
+ <canvas id="gradient-canvas"></canvas>
826
+ <div class="content">
827
+ <div class="icon-wrapper">
828
+ <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
829
+ <path d="M0 0 C20.93144435 -0.58142901 39.09496106 1.50604372 55.875 15.5625 C66.82790249 26.17665293 72.53226475 40.66646351 74.60546875 55.56640625 C74.87588186 58.00035983 74.87588186 58.00035983 76 60 C76.96454894 67.72509952 77.18355452 75.52548686 77.39453125 83.30078125 C77.46339082 85.43557289 77.53307981 87.57033791 77.60351562 89.70507812 C77.70976411 92.99051459 77.81152568 96.27576582 77.89868164 99.56176758 C78.52393733 129.23731142 78.52393733 129.23731142 88 157 C88.35578125 157.67804688 88.7115625 158.35609375 89.078125 159.0546875 C94.76089061 168.37922543 104.94901494 170.48725373 115 173 C115 182.57 115 192.14 115 202 C112.69 202.37125 110.38 202.7425 108 203.125 C99.905737 204.67003153 95.0680161 206.8924082 90.109375 213.53125 C83.71376381 223.52775152 81.23188184 234.44421869 79.9609375 246.09765625 C79.87667007 246.86579147 79.79240265 247.6339267 79.70558167 248.42533875 C78.67925596 258.36255872 78.32712705 268.3341428 77.9375 278.3125 C76.56010435 312.28921394 76.56010435 312.28921394 74.25 321.5 C74.0744458 322.21430176 73.8988916 322.92860352 73.71801758 323.66455078 C69.36621237 340.7251508 62.22279512 355.52722574 46.7734375 364.97265625 C31.08266768 373.31391199 17.46036093 374.45648002 0 374 C0 362.78 0 351.56 0 340 C4.95 339.67 9.9 339.34 15 339 C21.41425661 337.54221441 27.40557289 335.94604143 32 331 C41.96653782 315.14896379 41.37732492 296.30175799 42 278.25 C44.29042973 212.3198731 44.29042973 212.3198731 64.91015625 190 C66.29780265 187.98880883 66.29780265 187.98880883 65.37890625 185.48046875 C64.05564488 183.10009772 62.69232678 181.32846483 60.875 179.3125 C43.91950219 157.82609672 43.67363702 127.5168667 42.25390625 101.43359375 C42.19595886 100.36962875 42.13801147 99.30566376 42.07830811 98.2094574 C41.84407978 93.84466467 41.61402719 89.47975805 41.39819336 85.11401367 C40.99015593 64.27717646 40.99015593 64.27717646 32.98828125 45.5234375 C32 44 32 44 32 42 C31.42121094 41.71253906 30.84242188 41.42507813 30.24609375 41.12890625 C28.83602338 40.42019262 27.43139326 39.70043581 26.03515625 38.96484375 C25.38417969 38.62582031 24.73320313 38.28679687 24.0625 37.9375 C23.41410156 37.59589844 22.76570312 37.25429688 22.09765625 36.90234375 C15.27203813 33.966184 7.11372705 34.47424847 0 34 C0 22.78 0 11.56 0 0 Z" transform="translate(654,325)"/>
830
+ <path d="M0 0 C3.444375 0.04125 6.88875 0.0825 10.4375 0.125 C10.4375 11.345 10.4375 22.565 10.4375 34.125 C7.83875 34.2075 5.24 34.29 2.5625 34.375 C-7.18272292 34.85729903 -15.63749007 36.71684985 -22.5625 44.125 C-31.49595906 57.5251886 -31.12984766 74.21365918 -31.625 89.6875 C-34.18432686 164.24320576 -34.18432686 164.24320576 -54.375 183.4765625 C-55.74969183 185.03433577 -55.74969183 185.03433577 -55.515625 186.95703125 C-54.17099734 190.0155081 -52.28807488 192.49561568 -50.3125 195.1875 C-32.54496946 220.15391355 -32.63824135 251.51444506 -31.51293945 280.92919922 C-31.08045868 292.11365858 -30.51897291 303.08447592 -28.5625 314.125 C-28.43262695 314.88192139 -28.30275391 315.63884277 -28.16894531 316.41870117 C-26.61388783 323.91215705 -23.50529382 330.13896804 -17.1875 334.5625 C-8.59143743 340.04835084 0.27828442 339.49004903 10.4375 340.125 C10.4375 351.345 10.4375 362.565 10.4375 374.125 C-11.03507606 374.69006779 -28.20349315 372.92712593 -45.375 358.5625 C-68.63112564 336.25560398 -67.21470802 299.23895394 -68.0625 269.5625 C-68.59361144 237.84268541 -68.59361144 237.84268541 -82.5625 210.125 C-89.22872268 204.50037462 -95.95018781 203.27807805 -104.5625 201.125 C-104.5625 191.885 -104.5625 182.645 -104.5625 173.125 C-102.0875 172.444375 -99.6125 171.76375 -97.0625 171.0625 C-88.61410179 168.53529591 -83.39407037 165.21504609 -78.3125 157.875 C-77.69451944 156.64176126 -77.11369297 155.38950152 -76.5625 154.125 C-76.05783203 152.97451172 -76.05783203 152.97451172 -75.54296875 151.80078125 C-70.10324087 138.17090127 -68.58488302 123.17691663 -68.1328125 108.6171875 C-68.0590342 106.55597377 -67.98479985 104.49477633 -67.91015625 102.43359375 C-67.79799647 99.2401961 -67.68897443 96.04681123 -67.58837891 92.85302734 C-67.22538737 81.52946225 -66.65494051 70.32073083 -65.13671875 59.0859375 C-64.95793686 57.75983963 -64.95793686 57.75983963 -64.77554321 56.4069519 C-62.24372342 39.06207822 -55.12155189 23.04594995 -40.9609375 12.1171875 C-28.07509054 3.53900119 -15.33988115 -0.26186367 0 0 Z" transform="translate(359.5625,324.875)"/>
831
+ <path d="M0 0 C9.89334431 8.68950817 14.30034619 23.01452361 15.24373245 35.79419899 C15.49099471 41.77148785 15.42334062 47.74978768 15.37109375 53.73046875 C15.36548489 55.63859724 15.36122091 57.54673011 15.35823059 59.4548645 C15.34688325 64.42950786 15.31750548 69.40388024 15.28411865 74.37841797 C15.25319735 79.47359555 15.23967854 84.56881775 15.22460938 89.6640625 C15.19262409 99.62643911 15.13994121 109.58854505 15.078125 119.55078125 C3.528125 119.55078125 -8.021875 119.55078125 -19.921875 119.55078125 C-19.97085938 114.4203125 -20.01984375 109.28984375 -20.0703125 104.00390625 C-20.10574492 100.73175245 -20.14173112 97.45962313 -20.1796875 94.1875 C-20.23973228 89.00671097 -20.29785091 83.82595572 -20.34375 78.64501953 C-20.38093086 74.46275871 -20.42737419 70.28068941 -20.47993851 66.09859467 C-20.49830992 64.51204578 -20.51347972 62.92545612 -20.52521896 61.3388443 C-20.22141003 42.05064114 -20.22141003 42.05064114 -28.921875 25.55078125 C-33.2097739 21.98892101 -37.29807009 19.87523153 -42.921875 19.55078125 C-43.79714844 19.48890625 -44.67242188 19.42703125 -45.57421875 19.36328125 C-54.66067243 18.98906437 -62.9621186 20.4280254 -69.859375 26.73828125 C-76.14726535 33.73487866 -78.0525657 41.92333342 -78.12719727 51.07324219 C-78.13709686 51.82485123 -78.14699646 52.57646027 -78.15719604 53.35084534 C-78.18871966 55.82891721 -78.21353223 58.30700823 -78.23828125 60.78515625 C-78.25885867 62.50580334 -78.27985536 64.22644545 -78.30125427 65.94708252 C-78.35639351 70.47125217 -78.40583867 74.99546682 -78.45410156 79.51971436 C-78.51321858 84.95049451 -78.57940167 90.38119259 -78.64440155 95.81190491 C-78.74230855 104.0581468 -78.83123469 112.30444185 -78.921875 120.55078125 C-90.801875 120.55078125 -102.681875 120.55078125 -114.921875 120.55078125 C-114.921875 77.65078125 -114.921875 34.75078125 -114.921875 -9.44921875 C-103.701875 -9.44921875 -92.481875 -9.44921875 -80.921875 -9.44921875 C-79.96000569 -5.60174152 -79.61528473 -2.14754023 -79.359375 1.80078125 C-79.27558594 3.0640625 -79.19179688 4.32734375 -79.10546875 5.62890625 C-79.04488281 6.593125 -78.98429688 7.55734375 -78.921875 8.55078125 C-78.48875 7.72578125 -78.055625 6.90078125 -77.609375 6.05078125 C-71.50314599 -3.01214812 -59.57220635 -8.76116827 -49.16015625 -11.05859375 C-31.06340614 -13.24155516 -14.8555768 -11.20207112 0 0 Z" transform="translate(613.921875,481.44921875)"/>
832
+ <path d="M0 0 C1.53906801 1.53906801 1.16955456 2.91263628 1.1875 5.0625 C1.1451715 9.92322255 0.65815533 14.68227908 0.0625 19.5 C-0.02427002 20.20648682 -0.11104004 20.91297363 -0.20043945 21.64086914 C-0.86920125 26.7384025 -0.86920125 26.7384025 -2 29 C-2.96808594 28.84144531 -3.93617188 28.68289063 -4.93359375 28.51953125 C-9.31081865 27.89956204 -13.64601408 27.74275212 -18.0625 27.6875 C-18.83916016 27.65849609 -19.61582031 27.62949219 -20.41601562 27.59960938 C-25.75566236 27.54083257 -28.83098013 28.36372264 -33 32 C-37.31404494 38.3211768 -37.31404494 38.3211768 -37 53 C-25.78 53 -14.56 53 -3 53 C-3 62.57 -3 72.14 -3 82 C-14.55 82 -26.1 82 -38 82 C-38 115 -38 148 -38 182 C-49.55 182 -61.1 182 -73 182 C-73 149 -73 116 -73 82 C-79.93 82 -86.86 82 -94 82 C-94 72.43 -94 62.86 -94 53 C-87.07 53 -80.14 53 -73 53 C-73.04125 50.6075 -73.0825 48.215 -73.125 45.75 C-73.17827085 31.60875021 -70.96095372 19.48048613 -61.0078125 8.81640625 C-54.5981324 3.0177657 -47.30019677 0.02408307 -39 -2 C-38.37625488 -2.17200928 -37.75250977 -2.34401855 -37.10986328 -2.52124023 C-25.33848182 -5.19234404 -11.53065338 -2.96722147 0 0 Z" transform="translate(486,419)"/>
833
+ </svg>
834
+ </div>
835
+ <h1 class="title">Your function is ready for traffic</h1>
836
+ <div class="status"><span class="status-dot"></span>Running</div>
837
+ </div>
838
+ <footer>
839
+ <div class="footer-content">
840
+ Powered by
841
+ <svg viewBox="0 0 93 48" xmlns="http://www.w3.org/2000/svg">
842
+ <path d="M92.298 25.271a17.167 17.167 0 0 1-.814 5.312c-1.51 4.734-5.27 8.678-10.06 10.549-5.64 2.202-12.252 1.416-18.624-2.21l-4.649-2.67a16.424 16.424 0 0 1-3.563 3.064l-14.817 8.679-.027.015v-7.501l.027-.014 22.29-13.057a16.017 16.017 0 0 1-.713 3.206l4.656 2.65c5.95 3.386 10.388 2.85 13.065 1.806 2.991-1.167 5.323-3.59 6.245-6.483.692-2.15.679-4.467-.04-6.609-.955-2.88-3.319-5.275-6.324-6.41-2.688-1.014-7.132-1.494-13.04 1.962L29.7 38.747l-.043.028c-4.017 2.35-8.14 3.556-12.065 3.58a18.162 18.162 0 0 1-6.53-1.145C6.247 39.396 2.44 35.498.874 30.783A17.116 17.116 0 0 1 .81 20.166c1.51-4.733 5.272-8.676 10.063-10.548 5.64-2.202 12.252-1.416 18.623 2.212l4.649 2.67a16.377 16.377 0 0 1 3.563-3.067l.281-.163 1.726-1.011-7.37-4.197A3.238 3.238 0 0 1 35.551.437l10.591 6.06L56.52.457a3.238 3.238 0 0 1 3.27 5.588l-29.528 17.05c.132-1.017.36-2.019.683-2.992l-4.656-2.65c-5.946-3.383-10.384-2.847-13.061-1.803-2.991 1.167-5.325 3.59-6.247 6.481a10.623 10.623 0 0 0 .039 6.608c.956 2.882 3.321 5.277 6.324 6.41 2.689 1.012 7.136 1.495 13.042-1.966l36.256-21.208c4.017-2.349 8.14-3.555 12.067-3.579a18.112 18.112 0 0 1 6.53 1.145c4.812 1.813 8.62 5.712 10.187 10.426a17.23 17.23 0 0 1 .872 5.304Z"/>
843
+ </svg>
844
+ </div>
845
+ </footer>
846
+ <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"}}</script>
847
+ <script type="module">
848
+ import * as THREE from 'three';
849
+ const COLORS={yellow:new THREE.Color(0xFFBE3C),green:new THREE.Color(0x059688),blue:new THREE.Color(0x3AA7EA),red:new THREE.Color(0xEF5858)};
850
+ const vertexShader=\`varying vec2 vUv;void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}\`;
851
+ const fragmentShader=\`uniform float uTime;uniform vec2 uResolution;uniform vec3 uColor1;uniform vec3 uColor2;uniform vec3 uColor3;uniform vec3 uColor4;varying vec2 vUv;vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 permute(vec4 x){return mod289(((x*34.0)+1.0)*x);}vec4 taylorInvSqrt(vec4 r){return 1.79284291400159-0.85373472095314*r;}float snoise(vec3 v){const vec2 C=vec2(1.0/6.0,1.0/3.0);const vec4 D=vec4(0.0,0.5,1.0,2.0);vec3 i=floor(v+dot(v,C.yyy));vec3 x0=v-i+dot(i,C.xxx);vec3 g=step(x0.yzx,x0.xyz);vec3 l=1.0-g;vec3 i1=min(g.xyz,l.zxy);vec3 i2=max(g.xyz,l.zxy);vec3 x1=x0-i1+C.xxx;vec3 x2=x0-i2+C.yyy;vec3 x3=x0-D.yyy;i=mod289(i);vec4 p=permute(permute(permute(i.z+vec4(0.0,i1.z,i2.z,1.0))+i.y+vec4(0.0,i1.y,i2.y,1.0))+i.x+vec4(0.0,i1.x,i2.x,1.0));float n_=0.142857142857;vec3 ns=n_*D.wyz-D.xzx;vec4 j=p-49.0*floor(p*ns.z*ns.z);vec4 x_=floor(j*ns.z);vec4 y_=floor(j-7.0*x_);vec4 x=x_*ns.x+ns.yyyy;vec4 y=y_*ns.x+ns.yyyy;vec4 h=1.0-abs(x)-abs(y);vec4 b0=vec4(x.xy,y.xy);vec4 b1=vec4(x.zw,y.zw);vec4 s0=floor(b0)*2.0+1.0;vec4 s1=floor(b1)*2.0+1.0;vec4 sh=-step(h,vec4(0.0));vec4 a0=b0.xzyw+s0.xzyw*sh.xxyy;vec4 a1=b1.xzyw+s1.xzyw*sh.zzww;vec3 p0=vec3(a0.xy,h.x);vec3 p1=vec3(a0.zw,h.y);vec3 p2=vec3(a1.xy,h.z);vec3 p3=vec3(a1.zw,h.w);vec4 norm=taylorInvSqrt(vec4(dot(p0,p0),dot(p1,p1),dot(p2,p2),dot(p3,p3)));p0*=norm.x;p1*=norm.y;p2*=norm.z;p3*=norm.w;vec4 m=max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.0);m=m*m;return 42.0*dot(m*m,vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3)));}float metaball(vec2 p,vec2 center,float radius){float d=length(p-center);return radius/(d*d+0.0001);}void main(){vec2 uv=vUv;vec2 aspect=vec2(uResolution.x/uResolution.y,1.0);vec2 p=(uv-0.5)*aspect;float time=uTime*0.15;vec2 blob1=vec2(0.6*sin(time*0.7+snoise(vec3(time*0.2,0.0,0.0))),0.5*cos(time*0.5+snoise(vec3(0.0,time*0.2,0.0))));vec2 blob2=vec2(0.55*cos(time*0.6+1.5+snoise(vec3(time*0.15,1.0,0.0))),0.6*sin(time*0.8+2.0+snoise(vec3(1.0,time*0.15,0.0))));vec2 blob3=vec2(0.7*sin(time*0.5+3.0+snoise(vec3(time*0.1,2.0,0.0))),0.5*cos(time*0.7+1.0+snoise(vec3(2.0,time*0.1,0.0))));vec2 blob4=vec2(0.55*cos(time*0.8+4.5+snoise(vec3(time*0.12,3.0,0.0))),0.65*sin(time*0.6+0.5+snoise(vec3(3.0,time*0.12,0.0))));float size1=0.16+0.04*sin(time*1.2);float size2=0.14+0.03*cos(time*1.5);float size3=0.18+0.05*sin(time*0.9);float size4=0.12+0.04*cos(time*1.1);float m1=metaball(p,blob1,size1);float m2=metaball(p,blob2,size2);float m3=metaball(p,blob3,size3);float m4=metaball(p,blob4,size4);float field=m1+m2+m3+m4;float noise=snoise(vec3(p*3.0,time*0.3))*0.15;field+=noise;vec3 color=vec3(0.0);float total=m1+m2+m3+m4+0.0001;color+=uColor1*(m1/total);color+=uColor2*(m2/total);color+=uColor3*(m3/total);color+=uColor4*(m4/total);float gradientNoise=snoise(vec3(uv*2.0,time*0.1));color+=gradientNoise*0.05;float threshold=0.8;float edge=smoothstep(threshold-0.3,threshold+0.5,field);vec3 bg1=mix(uColor1,uColor3,uv.y)*0.6+0.4;vec3 bg2=mix(uColor2,uColor4,uv.x)*0.6+0.4;vec3 background=mix(bg1,bg2,0.5+0.5*sin(time*0.2));vec3 finalColor=mix(background,color,edge);float vignette=1.0-length(uv-0.5)*0.5;finalColor*=vignette;finalColor=pow(finalColor,vec3(0.95));gl_FragColor=vec4(finalColor,1.0);}\`;
852
+ class GradientAnimation{constructor(){this.canvas=document.getElementById('gradient-canvas');this.scene=new THREE.Scene();this.camera=new THREE.OrthographicCamera(-1,1,1,-1,0,1);this.renderer=new THREE.WebGLRenderer({canvas:this.canvas,antialias:true,alpha:false});this.renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));this.renderer.setSize(window.innerWidth,window.innerHeight);this.uniforms={uTime:{value:0},uResolution:{value:new THREE.Vector2(window.innerWidth,window.innerHeight)},uColor1:{value:COLORS.yellow},uColor2:{value:COLORS.green},uColor3:{value:COLORS.blue},uColor4:{value:COLORS.red}};const geometry=new THREE.PlaneGeometry(2,2);const material=new THREE.ShaderMaterial({vertexShader,fragmentShader,uniforms:this.uniforms});this.mesh=new THREE.Mesh(geometry,material);this.scene.add(this.mesh);window.addEventListener('resize',this.onResize.bind(this));this.animate();}onResize(){this.renderer.setSize(window.innerWidth,window.innerHeight);this.uniforms.uResolution.value.set(window.innerWidth,window.innerHeight);}animate(){this.uniforms.uTime.value+=0.016;this.renderer.render(this.scene,this.camera);requestAnimationFrame(this.animate.bind(this));}}
853
+ new GradientAnimation();
854
+ </script>
855
+ </body>
856
+ </html>`;
857
+ function getLandingPageHtml() {
858
+ return LANDING_PAGE_HTML;
859
+ }
860
+ var FAVICON_SVG = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
861
+ <rect width="1024" height="1024" fill="#3AA7EA"/>
862
+ <path fill="white" d="M0 0 C20.93144435 -0.58142901 39.09496106 1.50604372 55.875 15.5625 C66.82790249 26.17665293 72.53226475 40.66646351 74.60546875 55.56640625 C74.87588186 58.00035983 74.87588186 58.00035983 76 60 C76.96454894 67.72509952 77.18355452 75.52548686 77.39453125 83.30078125 C77.46339082 85.43557289 77.53307981 87.57033791 77.60351562 89.70507812 C77.70976411 92.99051459 77.81152568 96.27576582 77.89868164 99.56176758 C78.52393733 129.23731142 78.52393733 129.23731142 88 157 C88.35578125 157.67804688 88.7115625 158.35609375 89.078125 159.0546875 C94.76089061 168.37922543 104.94901494 170.48725373 115 173 C115 182.57 115 192.14 115 202 C112.69 202.37125 110.38 202.7425 108 203.125 C99.905737 204.67003153 95.0680161 206.8924082 90.109375 213.53125 C83.71376381 223.52775152 81.23188184 234.44421869 79.9609375 246.09765625 C79.87667007 246.86579147 79.79240265 247.6339267 79.70558167 248.42533875 C78.67925596 258.36255872 78.32712705 268.3341428 77.9375 278.3125 C76.56010435 312.28921394 76.56010435 312.28921394 74.25 321.5 C74.0744458 322.21430176 73.8988916 322.92860352 73.71801758 323.66455078 C69.36621237 340.7251508 62.22279512 355.52722574 46.7734375 364.97265625 C31.08266768 373.31391199 17.46036093 374.45648002 0 374 C0 362.78 0 351.56 0 340 C4.95 339.67 9.9 339.34 15 339 C21.41425661 337.54221441 27.40557289 335.94604143 32 331 C41.96653782 315.14896379 41.37732492 296.30175799 42 278.25 C44.29042973 212.3198731 44.29042973 212.3198731 64.91015625 190 C66.29780265 187.98880883 66.29780265 187.98880883 65.37890625 185.48046875 C64.05564488 183.10009772 62.69232678 181.32846483 60.875 179.3125 C43.91950219 157.82609672 43.67363702 127.5168667 42.25390625 101.43359375 C42.19595886 100.36962875 42.13801147 99.30566376 42.07830811 98.2094574 C41.84407978 93.84466467 41.61402719 89.47975805 41.39819336 85.11401367 C40.99015593 64.27717646 40.99015593 64.27717646 32.98828125 45.5234375 C32 44 32 44 32 42 C31.42121094 41.71253906 30.84242188 41.42507813 30.24609375 41.12890625 C28.83602338 40.42019262 27.43139326 39.70043581 26.03515625 38.96484375 C25.38417969 38.62582031 24.73320313 38.28679687 24.0625 37.9375 C23.41410156 37.59589844 22.76570312 37.25429688 22.09765625 36.90234375 C15.27203813 33.966184 7.11372705 34.47424847 0 34 C0 22.78 0 11.56 0 0 Z" transform="translate(654,325)"/>
863
+ <path fill="white" d="M0 0 C3.444375 0.04125 6.88875 0.0825 10.4375 0.125 C10.4375 11.345 10.4375 22.565 10.4375 34.125 C7.83875 34.2075 5.24 34.29 2.5625 34.375 C-7.18272292 34.85729903 -15.63749007 36.71684985 -22.5625 44.125 C-31.49595906 57.5251886 -31.12984766 74.21365918 -31.625 89.6875 C-34.18432686 164.24320576 -34.18432686 164.24320576 -54.375 183.4765625 C-55.74969183 185.03433577 -55.74969183 185.03433577 -55.515625 186.95703125 C-54.17099734 190.0155081 -52.28807488 192.49561568 -50.3125 195.1875 C-32.54496946 220.15391355 -32.63824135 251.51444506 -31.51293945 280.92919922 C-31.08045868 292.11365858 -30.51897291 303.08447592 -28.5625 314.125 C-28.43262695 314.88192139 -28.30275391 315.63884277 -28.16894531 316.41870117 C-26.61388783 323.91215705 -23.50529382 330.13896804 -17.1875 334.5625 C-8.59143743 340.04835084 0.27828442 339.49004903 10.4375 340.125 C10.4375 351.345 10.4375 362.565 10.4375 374.125 C-11.03507606 374.69006779 -28.20349315 372.92712593 -45.375 358.5625 C-68.63112564 336.25560398 -67.21470802 299.23895394 -68.0625 269.5625 C-68.59361144 237.84268541 -68.59361144 237.84268541 -82.5625 210.125 C-89.22872268 204.50037462 -95.95018781 203.27807805 -104.5625 201.125 C-104.5625 191.885 -104.5625 182.645 -104.5625 173.125 C-102.0875 172.444375 -99.6125 171.76375 -97.0625 171.0625 C-88.61410179 168.53529591 -83.39407037 165.21504609 -78.3125 157.875 C-77.69451944 156.64176126 -77.11369297 155.38950152 -76.5625 154.125 C-76.05783203 152.97451172 -76.05783203 152.97451172 -75.54296875 151.80078125 C-70.10324087 138.17090127 -68.58488302 123.17691663 -68.1328125 108.6171875 C-68.0590342 106.55597377 -67.98479985 104.49477633 -67.91015625 102.43359375 C-67.79799647 99.2401961 -67.68897443 96.04681123 -67.58837891 92.85302734 C-67.22538737 81.52946225 -66.65494051 70.32073083 -65.13671875 59.0859375 C-64.95793686 57.75983963 -64.95793686 57.75983963 -64.77554321 56.4069519 C-62.24372342 39.06207822 -55.12155189 23.04594995 -40.9609375 12.1171875 C-28.07509054 3.53900119 -15.33988115 -0.26186367 0 0 Z" transform="translate(359.5625,324.875)"/>
864
+ <path fill="white" d="M0 0 C9.89334431 8.68950817 14.30034619 23.01452361 15.24373245 35.79419899 C15.49099471 41.77148785 15.42334062 47.74978768 15.37109375 53.73046875 C15.36548489 55.63859724 15.36122091 57.54673011 15.35823059 59.4548645 C15.34688325 64.42950786 15.31750548 69.40388024 15.28411865 74.37841797 C15.25319735 79.47359555 15.23967854 84.56881775 15.22460938 89.6640625 C15.19262409 99.62643911 15.13994121 109.58854505 15.078125 119.55078125 C3.528125 119.55078125 -8.021875 119.55078125 -19.921875 119.55078125 C-19.97085938 114.4203125 -20.01984375 109.28984375 -20.0703125 104.00390625 C-20.10574492 100.73175245 -20.14173112 97.45962313 -20.1796875 94.1875 C-20.23973228 89.00671097 -20.29785091 83.82595572 -20.34375 78.64501953 C-20.38093086 74.46275871 -20.42737419 70.28068941 -20.47993851 66.09859467 C-20.49830992 64.51204578 -20.51347972 62.92545612 -20.52521896 61.3388443 C-20.22141003 42.05064114 -20.22141003 42.05064114 -28.921875 25.55078125 C-33.2097739 21.98892101 -37.29807009 19.87523153 -42.921875 19.55078125 C-43.79714844 19.48890625 -44.67242188 19.42703125 -45.57421875 19.36328125 C-54.66067243 18.98906437 -62.9621186 20.4280254 -69.859375 26.73828125 C-76.14726535 33.73487866 -78.0525657 41.92333342 -78.12719727 51.07324219 C-78.13709686 51.82485123 -78.14699646 52.57646027 -78.15719604 53.35084534 C-78.18871966 55.82891721 -78.21353223 58.30700823 -78.23828125 60.78515625 C-78.25885867 62.50580334 -78.27985536 64.22644545 -78.30125427 65.94708252 C-78.35639351 70.47125217 -78.40583867 74.99546682 -78.45410156 79.51971436 C-78.51321858 84.95049451 -78.57940167 90.38119259 -78.64440155 95.81190491 C-78.74230855 104.0581468 -78.83123469 112.30444185 -78.921875 120.55078125 C-90.801875 120.55078125 -102.681875 120.55078125 -114.921875 120.55078125 C-114.921875 77.65078125 -114.921875 34.75078125 -114.921875 -9.44921875 C-103.701875 -9.44921875 -92.481875 -9.44921875 -80.921875 -9.44921875 C-79.96000569 -5.60174152 -79.61528473 -2.14754023 -79.359375 1.80078125 C-79.27558594 3.0640625 -79.19179688 4.32734375 -79.10546875 5.62890625 C-79.04488281 6.593125 -78.98429688 7.55734375 -78.921875 8.55078125 C-78.48875 7.72578125 -78.055625 6.90078125 -77.609375 6.05078125 C-71.50314599 -3.01214812 -59.57220635 -8.76116827 -49.16015625 -11.05859375 C-31.06340614 -13.24155516 -14.8555768 -11.20207112 0 0 Z" transform="translate(613.921875,481.44921875)"/>
865
+ <path fill="white" d="M0 0 C1.53906801 1.53906801 1.16955456 2.91263628 1.1875 5.0625 C1.1451715 9.92322255 0.65815533 14.68227908 0.0625 19.5 C-0.02427002 20.20648682 -0.11104004 20.91297363 -0.20043945 21.64086914 C-0.86920125 26.7384025 -0.86920125 26.7384025 -2 29 C-2.96808594 28.84144531 -3.93617188 28.68289063 -4.93359375 28.51953125 C-9.31081865 27.89956204 -13.64601408 27.74275212 -18.0625 27.6875 C-18.83916016 27.65849609 -19.61582031 27.62949219 -20.41601562 27.59960938 C-25.75566236 27.54083257 -28.83098013 28.36372264 -33 32 C-37.31404494 38.3211768 -37.31404494 38.3211768 -37 53 C-25.78 53 -14.56 53 -3 53 C-3 62.57 -3 72.14 -3 82 C-14.55 82 -26.1 82 -38 82 C-38 115 -38 148 -38 182 C-49.55 182 -61.1 182 -73 182 C-73 149 -73 116 -73 82 C-79.93 82 -86.86 82 -94 82 C-94 72.43 -94 62.86 -94 53 C-87.07 53 -80.14 53 -73 53 C-73.04125 50.6075 -73.0825 48.215 -73.125 45.75 C-73.17827085 31.60875021 -70.96095372 19.48048613 -61.0078125 8.81640625 C-54.5981324 3.0177657 -47.30019677 0.02408307 -39 -2 C-38.37625488 -2.17200928 -37.75250977 -2.34401855 -37.10986328 -2.52124023 C-25.33848182 -5.19234404 -11.53065338 -2.96722147 0 0 Z" transform="translate(486,419)"/>
866
+ </svg>`;
867
+ function getFaviconSvg() {
868
+ return FAVICON_SVG;
869
+ }
870
+ var VOICE_CALLBACKS = ["ice", "ace", "pie", "dice", "notify"];
871
+ var NOTIFICATION_EVENTS = ["dice", "notify"];
872
+ function isVoiceCallback(functionName) {
873
+ return VOICE_CALLBACKS.includes(functionName);
874
+ }
875
+ function isNotificationEvent(functionName) {
876
+ return NOTIFICATION_EVENTS.includes(functionName);
877
+ }
878
+ function extractFunctionName(path5, body) {
879
+ if (body?.event && isVoiceCallback(body.event)) {
880
+ return body.event;
881
+ }
882
+ const segments = path5.split("/").filter((s) => s && s !== "*");
883
+ if (segments.length === 1 && isVoiceCallback(segments[0])) {
884
+ return segments[0];
885
+ }
886
+ return segments[segments.length - 1] || "default";
887
+ }
888
+ function generateRequestId() {
889
+ return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
890
+ }
891
+ function findFunctionPath() {
892
+ const fs4 = requireCjs2("fs");
893
+ const distPath = nodePath.join(process.cwd(), "dist", "function.js");
894
+ const rootPath = nodePath.join(process.cwd(), "function.js");
895
+ if (fs4.existsSync(distPath)) {
896
+ return distPath;
897
+ }
898
+ return rootPath;
899
+ }
900
+ function formatSvamlResponse(result, functionName) {
901
+ if (!result || typeof result !== "object") {
902
+ throw new Error(`Voice callback ${functionName} must return a valid SVAML object`);
903
+ }
904
+ const svaml = result;
905
+ if (!svaml.action && !svaml.instructions) {
906
+ throw new Error(`Voice callback ${functionName} must return SVAML with an action or instructions`);
907
+ }
908
+ if (!svaml.instructions) {
909
+ svaml.instructions = [];
910
+ }
911
+ return {
912
+ statusCode: 200,
913
+ headers: { "Content-Type": "application/json" },
914
+ body: svaml
915
+ };
916
+ }
917
+ function formatCustomResponse(result) {
918
+ if (!result || typeof result !== "object") {
919
+ return {
920
+ statusCode: 200,
921
+ headers: { "Content-Type": "application/json" },
922
+ body: result
923
+ };
924
+ }
925
+ const response = result;
926
+ if (!response.statusCode) {
927
+ return {
928
+ statusCode: 200,
929
+ headers: { "Content-Type": "application/json" },
930
+ body: result
931
+ };
932
+ }
933
+ return {
934
+ statusCode: response.statusCode,
935
+ headers: response.headers,
936
+ body: response.body
937
+ };
938
+ }
939
+ function validateVoiceRequest(body) {
940
+ if (!body || typeof body !== "object") {
941
+ return {
942
+ valid: false,
943
+ error: "Missing request body",
944
+ expectedEvents: [...VOICE_CALLBACKS]
945
+ };
946
+ }
947
+ const data = body;
948
+ if (!data.event) {
949
+ return {
950
+ valid: false,
951
+ error: "Missing event type in request body",
952
+ expectedEvents: [...VOICE_CALLBACKS]
953
+ };
954
+ }
955
+ if (!data.callId) {
956
+ return {
957
+ valid: false,
958
+ error: "Missing callId in request body"
959
+ };
960
+ }
961
+ return { valid: true };
962
+ }
963
+ var noOpCache = {
964
+ set: async () => {
965
+ },
966
+ get: async () => null,
967
+ has: async () => false,
968
+ delete: async () => false,
969
+ extend: async () => false,
970
+ keys: async () => [],
971
+ getMany: async () => ({})
972
+ };
973
+ function buildBaseContext(req, config = {}) {
974
+ return {
975
+ requestId: req.headers["x-request-id"] || generateRequestId(),
976
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
977
+ env: process.env,
978
+ config: {
979
+ projectId: config.projectId || "unknown",
980
+ functionName: config.functionName || "unknown",
981
+ environment: config.environment || "development",
982
+ variables: config.variables
983
+ },
984
+ cache: noOpCache
985
+ };
986
+ }
987
+ async function handleVoiceCallback(functionName, userFunction, context, callbackData, logger) {
988
+ const handler = userFunction[functionName];
989
+ if (!handler || typeof handler !== "function") {
990
+ throw new Error(`Function '${functionName}' not found in function.js`);
991
+ }
992
+ let result;
993
+ switch (functionName) {
994
+ case "ice":
995
+ result = await handler?.(context, callbackData);
996
+ break;
997
+ case "ace":
998
+ result = await handler?.(context, callbackData);
999
+ break;
1000
+ case "pie":
1001
+ result = await handler?.(context, callbackData);
1002
+ break;
1003
+ case "dice":
1004
+ result = await handler?.(context, callbackData);
1005
+ break;
1006
+ case "notify":
1007
+ result = await handler?.(context, callbackData);
1008
+ break;
1009
+ default:
1010
+ throw new Error(`Unknown voice callback: ${functionName}`);
1011
+ }
1012
+ if (logger) {
1013
+ logger("Function result:", result);
1014
+ }
1015
+ if (isNotificationEvent(functionName)) {
1016
+ if (logger) {
1017
+ logger(`${functionName.toUpperCase()} callback completed`);
1018
+ }
1019
+ return {
1020
+ statusCode: 200,
1021
+ headers: { "Content-Type": "application/json" },
1022
+ body: {}
1023
+ };
1024
+ }
1025
+ return formatSvamlResponse(result, functionName);
1026
+ }
1027
+ async function handleCustomEndpoint(functionName, userFunction, context, request, logger) {
1028
+ const handler = userFunction[functionName];
1029
+ if (!handler || typeof handler !== "function") {
1030
+ throw new Error(`Function '${functionName}' not found in function.js`);
1031
+ }
1032
+ const result = await handler(context, request);
1033
+ if (logger) {
1034
+ logger("Function result:", result);
1035
+ }
1036
+ return formatCustomResponse(result);
1037
+ }
1038
+ function createApp(options = {}) {
1039
+ const express = requireCjs2("express");
1040
+ const app = express();
1041
+ if (options.staticDir) {
1042
+ app.use(express.static(options.staticDir, {
1043
+ index: false,
1044
+ // Don't serve index.html for /, landing page handles that
1045
+ dotfiles: "ignore"
1046
+ }));
1047
+ }
1048
+ const jsonOptions = options.jsonParsingOptions || {
1049
+ limit: "10mb",
1050
+ allowJson5: true,
1051
+ camelCase: {
1052
+ deep: true,
1053
+ stopPaths: ["custom"],
1054
+ exclude: []
1055
+ }
1056
+ };
1057
+ setupJsonParsing(app, jsonOptions);
1058
+ app.use(express.urlencoded({ extended: true, limit: "10mb" }));
1059
+ return app;
1060
+ }
1061
+ function setupRequestHandler(app, options = {}) {
1062
+ const { loadUserFunction = async () => {
1063
+ const functionPath = findFunctionPath();
1064
+ const functionUrl = pathToFileURL(functionPath).href;
1065
+ const module = await import(functionUrl);
1066
+ return module.default || module;
1067
+ }, buildContext = buildBaseContext, logger = console.log, landingPageEnabled = true, onRequestStart = () => {
1068
+ }, onRequestEnd = () => {
1069
+ } } = options;
1070
+ app.use("/{*splat}", async (req, res) => {
1071
+ const startTime = Date.now();
1072
+ if ((req.method === "GET" || req.method === "HEAD") && req.originalUrl === "/favicon.ico") {
1073
+ const svg = getFaviconSvg();
1074
+ res.type("image/svg+xml").send(svg);
1075
+ return;
1076
+ }
1077
+ if (req.method === "GET" && req.path === "/" && landingPageEnabled) {
1078
+ const acceptHeader = req.headers.accept || "";
1079
+ if (acceptHeader.includes("text/html")) {
1080
+ const html = getLandingPageHtml();
1081
+ res.type("html").send(html);
1082
+ return;
1083
+ }
1084
+ }
1085
+ try {
1086
+ const functionName = extractFunctionName(req.baseUrl, req.body);
1087
+ logger(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${req.method} ${req.path} -> ${functionName}`);
1088
+ onRequestStart({ functionName, req });
1089
+ const context = buildContext(req);
1090
+ const userFunction = await Promise.resolve(loadUserFunction());
1091
+ let response;
1092
+ if (isVoiceCallback(functionName)) {
1093
+ const validation = validateVoiceRequest(req.body);
1094
+ if (!validation.valid) {
1095
+ res.status(400).json({
1096
+ error: validation.error,
1097
+ ...validation.expectedEvents && { expectedEvents: validation.expectedEvents }
1098
+ });
1099
+ return;
1100
+ }
1101
+ response = await handleVoiceCallback(functionName, userFunction, context, req.body, logger);
1102
+ } else {
1103
+ const request = {
1104
+ method: req.method,
1105
+ path: req.path,
1106
+ query: req.query,
1107
+ headers: req.headers,
1108
+ body: req.body,
1109
+ params: req.params
1110
+ };
1111
+ response = await handleCustomEndpoint(functionName, userFunction, context, request, logger);
1112
+ }
1113
+ res.status(response.statusCode);
1114
+ if (response.headers) {
1115
+ for (const [key, value] of Object.entries(response.headers)) {
1116
+ res.setHeader(key, value);
1117
+ }
1118
+ }
1119
+ const contentType = response.headers?.["Content-Type"];
1120
+ const isJsonContent = contentType?.includes("application/json");
1121
+ const isHtmlContent = contentType?.includes("text/html");
1122
+ if (response.body === void 0) {
1123
+ res.end();
1124
+ } else if (isJsonContent) {
1125
+ res.json(response.body);
1126
+ } else if (isHtmlContent) {
1127
+ res.send(String(response.body));
1128
+ } else {
1129
+ res.send(response.body);
1130
+ }
1131
+ const duration = Date.now() - startTime;
1132
+ logger(`[${(/* @__PURE__ */ new Date()).toISOString()}] Response sent: ${response.statusCode} (${duration}ms)`);
1133
+ onRequestEnd({
1134
+ functionName,
1135
+ req,
1136
+ response,
1137
+ duration,
1138
+ statusCode: response.statusCode
1139
+ });
1140
+ } catch (error) {
1141
+ const duration = Date.now() - startTime;
1142
+ logger("Function execution error:", {
1143
+ error: error.message,
1144
+ stack: error.stack,
1145
+ function: extractFunctionName(req.path, req.body)
1146
+ });
1147
+ res.status(500).json({
1148
+ error: "Internal server error",
1149
+ message: error.message,
1150
+ ...process.env.NODE_ENV === "development" && { stack: error.stack }
1151
+ });
1152
+ onRequestEnd({
1153
+ functionName: extractFunctionName(req.path, req.body),
1154
+ req,
1155
+ error,
1156
+ duration,
1157
+ statusCode: 500
1158
+ });
1159
+ }
1160
+ });
1161
+ }
1162
+
1163
+ // ../runtime-shared/dist/sinch/index.js
1164
+ var SinchClient = null;
1165
+ var validateAuthenticationHeader = null;
1166
+ async function loadSinchSdk() {
1167
+ if (SinchClient !== null) {
1168
+ return SinchClient !== false;
1169
+ }
1170
+ try {
1171
+ const sdk = await import("@sinch/sdk-core");
1172
+ SinchClient = sdk.SinchClient;
1173
+ validateAuthenticationHeader = sdk.validateAuthenticationHeader;
1174
+ return true;
1175
+ } catch {
1176
+ SinchClient = false;
1177
+ validateAuthenticationHeader = false;
1178
+ console.debug("[SINCH] SDK not available - Sinch API features disabled");
1179
+ return false;
1180
+ }
1181
+ }
1182
+ async function createSinchClients() {
1183
+ const clients = {};
1184
+ const sdkAvailable = await loadSinchSdk();
1185
+ if (!sdkAvailable) {
1186
+ return clients;
1187
+ }
1188
+ const hasCredentials = process.env.PROJECT_ID && process.env.PROJECT_ID_API_KEY && process.env.PROJECT_ID_API_SECRET;
1189
+ if (!hasCredentials) {
1190
+ return clients;
1191
+ }
1192
+ try {
1193
+ const sinchClient = new SinchClient({
1194
+ projectId: process.env.PROJECT_ID,
1195
+ keyId: process.env.PROJECT_ID_API_KEY,
1196
+ keySecret: process.env.PROJECT_ID_API_SECRET,
1197
+ region: process.env.SINCH_REGION || "us"
1198
+ });
1199
+ if (process.env.CONVERSATION_APP_ID) {
1200
+ clients.conversation = sinchClient.conversation;
1201
+ console.log("[SINCH] Conversation API initialized");
1202
+ }
1203
+ if (process.env.VOICE_APPLICATION_KEY && process.env.VOICE_APPLICATION_SECRET) {
1204
+ const voiceClient = new SinchClient({
1205
+ projectId: process.env.PROJECT_ID,
1206
+ keyId: process.env.PROJECT_ID_API_KEY,
1207
+ keySecret: process.env.PROJECT_ID_API_SECRET,
1208
+ applicationKey: process.env.VOICE_APPLICATION_KEY,
1209
+ applicationSecret: process.env.VOICE_APPLICATION_SECRET,
1210
+ region: process.env.SINCH_REGION || "us"
1211
+ });
1212
+ clients.voice = voiceClient.voice;
1213
+ console.log("[SINCH] Voice API initialized with application credentials");
1214
+ }
1215
+ if (process.env.SMS_SERVICE_PLAN_ID) {
1216
+ clients.sms = sinchClient.sms;
1217
+ console.log("[SINCH] SMS API initialized");
1218
+ }
1219
+ if (process.env.ENABLE_NUMBERS_API === "true") {
1220
+ clients.numbers = sinchClient.numbers;
1221
+ console.log("[SINCH] Numbers API initialized");
1222
+ }
1223
+ } catch (error) {
1224
+ console.error("[SINCH] Failed to initialize Sinch clients:", error.message);
1225
+ return {};
1226
+ }
1227
+ if (process.env.VOICE_APPLICATION_KEY && process.env.VOICE_APPLICATION_SECRET && validateAuthenticationHeader) {
1228
+ clients.validateWebhookSignature = (requestData) => {
1229
+ console.log("[SINCH] Validating Voice webhook signature");
1230
+ try {
1231
+ const result = validateAuthenticationHeader(process.env.VOICE_APPLICATION_KEY, process.env.VOICE_APPLICATION_SECRET, requestData.headers, requestData.body, requestData.path, requestData.method);
1232
+ console.log("[SINCH] Validation result:", result ? "VALID" : "INVALID");
1233
+ return result;
1234
+ } catch (error) {
1235
+ console.error("[SINCH] Validation error:", error.message);
1236
+ return false;
1237
+ }
1238
+ };
1239
+ }
1240
+ return clients;
1241
+ }
1242
+ var cachedClients = null;
1243
+ var initPromise = null;
1244
+ function getSinchClients() {
1245
+ if (cachedClients) {
1246
+ return cachedClients;
1247
+ }
1248
+ if (!initPromise) {
1249
+ initPromise = createSinchClients().then((clients) => {
1250
+ cachedClients = clients;
1251
+ return clients;
1252
+ });
1253
+ }
1254
+ return {};
1255
+ }
1256
+ function resetSinchClients() {
1257
+ cachedClients = null;
1258
+ initPromise = null;
1259
+ }
1260
+
1261
+ // ../runtime-shared/dist/utils/templateRender.js
1262
+ import fs from "fs";
1263
+ import path from "path";
1264
+ var TemplateRender = class {
1265
+ /**
1266
+ * Load and render an HTML template with variables
1267
+ * @param templatePath - Path to the HTML template file
1268
+ * @param variables - Object containing variables to replace in template
1269
+ * @returns Rendered HTML string
1270
+ */
1271
+ static render(templatePath, variables = {}) {
1272
+ try {
1273
+ const template = fs.readFileSync(templatePath, "utf8");
1274
+ let rendered = template;
1275
+ for (const [key, value] of Object.entries(variables)) {
1276
+ const placeholder = `{{${key}}}`;
1277
+ const regex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
1278
+ rendered = rendered.replace(regex, String(value));
1279
+ }
1280
+ return rendered;
1281
+ } catch (error) {
1282
+ console.error("Error rendering template:", error);
1283
+ throw new Error(`Failed to render template: ${templatePath}`);
1284
+ }
1285
+ }
1286
+ /**
1287
+ * Get the absolute path to a template file relative to the current directory
1288
+ * @param templateName - Name of the template file (e.g., 'index.html')
1289
+ * @param baseDir - Base directory to search from (defaults to cwd)
1290
+ * @returns Absolute path to the template file
1291
+ */
1292
+ static getTemplatePath(templateName, baseDir) {
1293
+ const searchDir = baseDir || process.cwd();
1294
+ const pagesPath = path.join(searchDir, "utils", "pages", templateName);
1295
+ if (fs.existsSync(pagesPath)) {
1296
+ return pagesPath;
1297
+ }
1298
+ const directPath = path.join(searchDir, "pages", templateName);
1299
+ if (fs.existsSync(directPath)) {
1300
+ return directPath;
1301
+ }
1302
+ return path.join(searchDir, "utils", "pages", templateName);
1303
+ }
1304
+ };
1305
+
1306
+ // ../runtime-shared/dist/utils/versionExtractor.js
1307
+ import fs2 from "fs";
1308
+ import path2 from "path";
1309
+ var VersionExtractor = class {
1310
+ /**
1311
+ * Extract version from README.md file in the template directory
1312
+ * Looks for "Template Version: X.X.X" pattern in the last few lines
1313
+ * @param templateDir - Path to the template directory containing README.md
1314
+ * @returns Version string or default fallback
1315
+ */
1316
+ static getTemplateVersion(templateDir = process.cwd()) {
1317
+ try {
1318
+ const readmePath = path2.join(templateDir, "README.md");
1319
+ if (!fs2.existsSync(readmePath)) {
1320
+ console.warn("README.md not found, using default version");
1321
+ return "v1.0.0";
1322
+ }
1323
+ const readmeContent = fs2.readFileSync(readmePath, "utf8");
1324
+ const lines = readmeContent.split("\n");
1325
+ const versionPattern = /Template Version:\s*([\d.]+)/i;
1326
+ for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
1327
+ const match = lines[i].match(versionPattern);
1328
+ if (match) {
1329
+ return `v${match[1]}`;
1330
+ }
1331
+ }
1332
+ console.warn("Version pattern not found in README.md, using default");
1333
+ return "v1.0.0";
1334
+ } catch (error) {
1335
+ console.error("Error reading template version:", error.message);
1336
+ return "v1.0.0";
1337
+ }
1338
+ }
1339
+ };
1340
+
1341
+ // ../runtime-shared/dist/utils/defaultEndpointHandler.js
1342
+ var DefaultEndpointHandler = class {
1343
+ /**
1344
+ * Handle the default endpoint with content negotiation
1345
+ * @param context - Function context with config and cache
1346
+ * @param request - HTTP request object with method, path, query, headers, body, params
1347
+ * @param options - Configuration options for the response
1348
+ * @returns HTTP response object with statusCode, headers, and body
1349
+ */
1350
+ static async handle(context, request, options = {}) {
1351
+ const { serviceName = "Sinch Function", companyName = "Your Company", templateVersion = "v1.0.0", availableEndpoints = {} } = options;
1352
+ console.info("Default endpoint called");
1353
+ console.info(`Request: ${request.method} ${request.path}`);
1354
+ const acceptHeader = request.headers?.accept || request.headers?.Accept || "";
1355
+ const wantsHtml = acceptHeader.includes("text/html") || !acceptHeader.includes("application/json") && !acceptHeader.includes("application/*") && !acceptHeader.includes("*/*");
1356
+ if (wantsHtml) {
1357
+ try {
1358
+ const templatePath = TemplateRender.getTemplatePath("index.html");
1359
+ const html = TemplateRender.render(templatePath, {
1360
+ companyName: serviceName,
1361
+ requestId: context.requestId,
1362
+ method: request.method,
1363
+ path: request.path,
1364
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1365
+ templateVersion,
1366
+ runtime: "Node.js"
1367
+ });
1368
+ return {
1369
+ statusCode: 200,
1370
+ headers: {
1371
+ "Content-Type": "text/html; charset=utf-8"
1372
+ },
1373
+ body: html
1374
+ };
1375
+ } catch (error) {
1376
+ console.warn("HTML template not found, returning JSON");
1377
+ }
1378
+ }
1379
+ return {
1380
+ statusCode: 200,
1381
+ headers: {
1382
+ "Content-Type": "application/json"
1383
+ },
1384
+ body: {
1385
+ message: `Welcome to ${serviceName}`,
1386
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1387
+ requestId: context.requestId,
1388
+ method: request.method,
1389
+ path: request.path,
1390
+ availableEndpoints,
1391
+ usage: {
1392
+ html: "Set Accept: text/html header or visit in browser",
1393
+ json: "Set Accept: application/json header or use API client"
1394
+ }
1395
+ }
1396
+ };
1397
+ }
1398
+ };
1399
+
1400
+ // ../runtime-shared/dist/utils/functionLoader.js
1401
+ import path3 from "path";
1402
+
1403
+ // ../runtime-shared/dist/types/api.js
1404
+ function createResponse(statusCode, body, headers) {
1405
+ return { statusCode, body, headers };
1406
+ }
1407
+ function createJsonResponse(body, statusCode = 200) {
1408
+ return {
1409
+ statusCode,
1410
+ headers: { "Content-Type": "application/json" },
1411
+ body
1412
+ };
1413
+ }
1414
+ function createErrorResponse(message, statusCode = 400) {
1415
+ return {
1416
+ statusCode,
1417
+ headers: { "Content-Type": "application/json" },
1418
+ body: { error: message }
1419
+ };
1420
+ }
1421
+
1422
+ // src/cache/local.ts
1423
+ var globalCache = /* @__PURE__ */ new Map();
1424
+ var LocalCache = class {
1425
+ cache;
1426
+ constructor() {
1427
+ this.cache = globalCache;
1428
+ }
1429
+ async set(key, value, ttlSeconds = 3600) {
1430
+ this.cache.set(key, {
1431
+ value,
1432
+ expiresAt: Date.now() + ttlSeconds * 1e3
1433
+ });
1434
+ }
1435
+ async get(key) {
1436
+ const item = this.cache.get(key);
1437
+ if (!item || Date.now() > item.expiresAt) {
1438
+ this.cache.delete(key);
1439
+ return null;
1440
+ }
1441
+ return item.value;
1442
+ }
1443
+ async has(key) {
1444
+ const value = await this.get(key);
1445
+ return value !== null;
1446
+ }
1447
+ async delete(key) {
1448
+ return this.cache.delete(key);
1449
+ }
1450
+ async extend(key, additionalSeconds) {
1451
+ const item = this.cache.get(key);
1452
+ if (!item || Date.now() > item.expiresAt) {
1453
+ return false;
1454
+ }
1455
+ item.expiresAt += additionalSeconds * 1e3;
1456
+ return true;
1457
+ }
1458
+ async keys(pattern = "*") {
1459
+ const allKeys = Array.from(this.cache.keys());
1460
+ const now = Date.now();
1461
+ for (const key of allKeys) {
1462
+ const item = this.cache.get(key);
1463
+ if (item && now > item.expiresAt) {
1464
+ this.cache.delete(key);
1465
+ }
1466
+ }
1467
+ const validKeys = Array.from(this.cache.keys());
1468
+ if (pattern === "*") {
1469
+ return validKeys;
1470
+ }
1471
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
1472
+ return validKeys.filter((key) => regex.test(key));
1473
+ }
1474
+ async getMany(keys) {
1475
+ const results = {};
1476
+ for (const key of keys) {
1477
+ results[key] = await this.get(key);
1478
+ }
1479
+ return results;
1480
+ }
1481
+ /**
1482
+ * Clear all entries from the cache
1483
+ * (Utility method for testing)
1484
+ */
1485
+ async clear() {
1486
+ this.cache.clear();
1487
+ }
1488
+ /**
1489
+ * Get the number of entries in the cache
1490
+ * (Utility method for debugging)
1491
+ */
1492
+ get size() {
1493
+ return this.cache.size;
1494
+ }
1495
+ };
1496
+ function createCacheClient(_projectId, _functionName) {
1497
+ return new LocalCache();
1498
+ }
1499
+
1500
+ // src/tunnel/index.ts
1501
+ import WebSocket from "ws";
1502
+ import axios from "axios";
1503
+ var TunnelClient = class {
1504
+ ws = null;
1505
+ tunnelUrl = null;
1506
+ isConnected = false;
1507
+ reconnectAttempts = 0;
1508
+ maxReconnectAttempts = 10;
1509
+ reconnectDelay = 5e3;
1510
+ heartbeatInterval = null;
1511
+ localPort;
1512
+ constructor(localPort = 3e3) {
1513
+ this.localPort = localPort;
1514
+ }
1515
+ async connect() {
1516
+ if (process.env.SINCH_TUNNEL !== "true") {
1517
+ console.log("Tunnel is disabled (set SINCH_TUNNEL=true to enable)");
1518
+ return;
1519
+ }
1520
+ const projectId = process.env.PROJECT_ID;
1521
+ const apiKey = process.env.PROJECT_ID_API_KEY;
1522
+ if (!projectId || !apiKey) {
1523
+ console.warn("PROJECT_ID or PROJECT_ID_API_KEY not configured, tunnel cannot connect");
1524
+ return;
1525
+ }
1526
+ const sessionId = `${projectId}-local-dev`;
1527
+ const apiUrl = process.env.API_URL || "https://functions.api.sinch.com";
1528
+ const baseUrl = new URL(apiUrl);
1529
+ const wsUrl = new URL(baseUrl.toString());
1530
+ wsUrl.protocol = baseUrl.protocol === "https:" ? "wss:" : "ws:";
1531
+ wsUrl.pathname = `/api/tunnel/${projectId}/${sessionId}`;
1532
+ const tunnelEndpoint = wsUrl.toString();
1533
+ const tunnelUrlObj = new URL(baseUrl.toString());
1534
+ tunnelUrlObj.pathname = `/api/ingress/${sessionId}`;
1535
+ this.tunnelUrl = tunnelUrlObj.toString();
1536
+ console.log("Connecting to tunnel server...");
1537
+ try {
1538
+ this.ws = new WebSocket(tunnelEndpoint, {
1539
+ headers: {
1540
+ "Authorization": `Bearer ${apiKey}`
1541
+ }
1542
+ });
1543
+ this.ws.on("open", async () => {
1544
+ this.isConnected = true;
1545
+ this.reconnectAttempts = 0;
1546
+ console.log("\u2705 Tunnel connected successfully!");
1547
+ console.log(`\u{1F4CD} Tunnel URL: ${this.tunnelUrl}`);
1548
+ console.log(`\u{1F4DE} Voice webhook URL: ${this.tunnelUrl}/`);
1549
+ await this.displayTestPhoneNumbers();
1550
+ });
1551
+ this.ws.on("message", async (data) => {
1552
+ try {
1553
+ const message = JSON.parse(data.toString());
1554
+ await this.handleMessage(message);
1555
+ } catch (error) {
1556
+ console.error("Error processing tunnel message:", error);
1557
+ }
1558
+ });
1559
+ this.ws.on("close", () => {
1560
+ this.isConnected = false;
1561
+ console.log("Tunnel connection closed");
1562
+ this.stopHeartbeat();
1563
+ this.scheduleReconnect();
1564
+ });
1565
+ this.ws.on("error", (error) => {
1566
+ console.error("Tunnel connection error:", error.message);
1567
+ });
1568
+ this.startHeartbeat();
1569
+ } catch (error) {
1570
+ console.error("Failed to establish tunnel connection:", error);
1571
+ this.scheduleReconnect();
1572
+ }
1573
+ }
1574
+ async handleMessage(message) {
1575
+ switch (message.type) {
1576
+ case "request":
1577
+ await this.handleRequest(message);
1578
+ break;
1579
+ case "ping":
1580
+ this.sendPong();
1581
+ break;
1582
+ }
1583
+ }
1584
+ async handleRequest(message) {
1585
+ console.log(`Forwarding ${message.method} request to ${message.path}`);
1586
+ try {
1587
+ const localUrl = `http://localhost:${this.localPort}${message.path}${message.query || ""}`;
1588
+ const axiosConfig = {
1589
+ method: message.method,
1590
+ url: localUrl,
1591
+ headers: {}
1592
+ };
1593
+ if (message.headers) {
1594
+ axiosConfig.headers = { ...message.headers };
1595
+ }
1596
+ if (message.body) {
1597
+ axiosConfig.data = message.body;
1598
+ }
1599
+ const response = await axios(axiosConfig);
1600
+ const responseMessage = {
1601
+ type: "response",
1602
+ id: message.id,
1603
+ statusCode: response.status,
1604
+ headers: response.headers,
1605
+ body: typeof response.data === "string" ? response.data : JSON.stringify(response.data)
1606
+ };
1607
+ this.ws?.send(JSON.stringify(responseMessage));
1608
+ } catch (error) {
1609
+ console.error("Error forwarding request:", error.message);
1610
+ const errorResponse = {
1611
+ type: "response",
1612
+ id: message.id,
1613
+ statusCode: error.response?.status || 502,
1614
+ headers: { "Content-Type": "text/plain" },
1615
+ body: "Error forwarding request to local server"
1616
+ };
1617
+ this.ws?.send(JSON.stringify(errorResponse));
1618
+ }
1619
+ }
1620
+ sendPong() {
1621
+ const pongMessage = { type: "pong" };
1622
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1623
+ this.ws.send(JSON.stringify(pongMessage));
1624
+ }
1625
+ }
1626
+ startHeartbeat() {
1627
+ this.heartbeatInterval = setInterval(() => {
1628
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1629
+ const pingMessage = { type: "ping" };
1630
+ this.ws.send(JSON.stringify(pingMessage));
1631
+ }
1632
+ }, 3e4);
1633
+ }
1634
+ stopHeartbeat() {
1635
+ if (this.heartbeatInterval) {
1636
+ clearInterval(this.heartbeatInterval);
1637
+ this.heartbeatInterval = null;
1638
+ }
1639
+ }
1640
+ async displayTestPhoneNumbers() {
1641
+ try {
1642
+ const appKey = process.env.VOICE_APPLICATION_KEY;
1643
+ const appSecret = process.env.VOICE_APPLICATION_SECRET;
1644
+ if (!appKey || !appSecret) {
1645
+ console.log("\u{1F4A1} Voice API not configured - skipping phone number display");
1646
+ return;
1647
+ }
1648
+ try {
1649
+ const updateUrl = `https://callingapi.sinch.com/v1/configuration/callbacks/applications/${appKey}/`;
1650
+ const auth = Buffer.from(`${appKey}:${appSecret}`).toString("base64");
1651
+ await axios.post(updateUrl, {
1652
+ url: {
1653
+ primary: this.tunnelUrl,
1654
+ fallback: null
1655
+ }
1656
+ }, {
1657
+ headers: {
1658
+ "Authorization": `Basic ${auth}`,
1659
+ "Content-Type": "application/json"
1660
+ }
1661
+ });
1662
+ console.log(`\u2705 Updated voice webhook URL to: ${this.tunnelUrl}`);
1663
+ } catch (error) {
1664
+ console.log("\u26A0\uFE0F Could not update webhook URL:", error.message);
1665
+ }
1666
+ try {
1667
+ const listUrl = `https://callingapi.sinch.com/v1/configuration/numbers/`;
1668
+ const auth = Buffer.from(`${appKey}:${appSecret}`).toString("base64");
1669
+ const response = await axios.get(listUrl, {
1670
+ headers: {
1671
+ "Authorization": `Basic ${auth}`
1672
+ }
1673
+ });
1674
+ const numbers = response.data?.numbers || [];
1675
+ const appNumbers = numbers.filter((n) => n.applicationkey === appKey);
1676
+ if (appNumbers.length > 0) {
1677
+ console.log("\u{1F4F1} Test Phone Numbers:");
1678
+ appNumbers.forEach((num) => {
1679
+ console.log(` \u260E\uFE0F ${num.number}`);
1680
+ });
1681
+ console.log("\u{1F4A1} Call any of these numbers to test your voice function!");
1682
+ } else {
1683
+ console.log("\u26A0\uFE0F No phone numbers assigned to this application yet");
1684
+ console.log("\u{1F4A1} Add numbers at https://dashboard.sinch.com/voice/apps");
1685
+ }
1686
+ } catch (error) {
1687
+ console.log("\u{1F4A1} Could not fetch phone numbers:", error.message);
1688
+ }
1689
+ } catch (error) {
1690
+ console.log("\u{1F4A1} Could not fetch phone numbers (Voice API may not be configured)");
1691
+ }
1692
+ }
1693
+ scheduleReconnect() {
1694
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1695
+ console.error("Max reconnection attempts reached. Giving up.");
1696
+ return;
1697
+ }
1698
+ this.reconnectAttempts++;
1699
+ const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 3e4);
1700
+ console.log(`Attempting to reconnect in ${delay / 1e3} seconds...`);
1701
+ setTimeout(() => this.connect(), delay);
1702
+ }
1703
+ disconnect() {
1704
+ this.stopHeartbeat();
1705
+ if (this.ws) {
1706
+ this.ws.close();
1707
+ this.ws = null;
1708
+ }
1709
+ this.isConnected = false;
1710
+ }
1711
+ getTunnelUrl() {
1712
+ return this.tunnelUrl;
1713
+ }
1714
+ getIsConnected() {
1715
+ return this.isConnected;
1716
+ }
1717
+ };
1718
+ var tunnelInstance = null;
1719
+ function getTunnelClient(localPort = 3e3) {
1720
+ if (!tunnelInstance) {
1721
+ tunnelInstance = new TunnelClient(localPort);
1722
+ }
1723
+ return tunnelInstance;
1724
+ }
1725
+
1726
+ // src/secrets/index.ts
1727
+ import fs3 from "fs";
1728
+ import path4 from "path";
1729
+ import os from "os";
1730
+ var SecretsLoader = class {
1731
+ // Same service name as CLI uses
1732
+ SERVICE_NAME = "sinch-functions-cli";
1733
+ username = os.userInfo().username;
1734
+ /**
1735
+ * Load secrets from OS keychain for variables declared in .env
1736
+ * Only loads secrets that have empty values in .env (security best practice)
1737
+ */
1738
+ async loadFromKeychain() {
1739
+ if (process.env.NODE_ENV === "production") {
1740
+ return false;
1741
+ }
1742
+ try {
1743
+ let keytar;
1744
+ try {
1745
+ keytar = await import("keytar");
1746
+ } catch (error) {
1747
+ if (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_MODULE_NOT_FOUND") {
1748
+ console.debug("[Secrets] Keytar not available - secrets not loaded");
1749
+ return false;
1750
+ } else {
1751
+ console.error("[Secrets] Error loading keytar:", error.message);
1752
+ }
1753
+ return false;
1754
+ }
1755
+ const envPath = path4.join(process.cwd(), ".env");
1756
+ if (!fs3.existsSync(envPath)) {
1757
+ console.debug("[Secrets] No .env file found, skipping keychain load");
1758
+ return false;
1759
+ }
1760
+ const envContent = fs3.readFileSync(envPath, "utf8");
1761
+ const envLines = envContent.replace(/\r\n/g, "\n").split("\n");
1762
+ const secretsToLoad = [];
1763
+ envLines.forEach((line) => {
1764
+ const trimmedLine = line.replace(/\r$/, "").trim();
1765
+ if (trimmedLine && !trimmedLine.startsWith("#")) {
1766
+ const equalIndex = trimmedLine.indexOf("=");
1767
+ if (equalIndex !== -1) {
1768
+ const envKey = trimmedLine.substring(0, equalIndex).trim();
1769
+ const envValue = trimmedLine.substring(equalIndex + 1).trim();
1770
+ if (envKey && envValue === "" && !process.env[envKey]) {
1771
+ secretsToLoad.push(envKey);
1772
+ }
1773
+ }
1774
+ }
1775
+ });
1776
+ if (secretsToLoad.length === 0) {
1777
+ console.debug("[Secrets] No empty variables found in .env");
1778
+ return false;
1779
+ }
1780
+ let secretsLoaded = 0;
1781
+ if (secretsToLoad.includes("PROJECT_ID_API_SECRET")) {
1782
+ const apiSecret = await keytar.getPassword(this.SERVICE_NAME, `${this.username}-keySecret`);
1783
+ if (apiSecret) {
1784
+ process.env.PROJECT_ID_API_SECRET = apiSecret;
1785
+ console.log("\u2705 Loaded PROJECT_ID_API_SECRET from secure storage");
1786
+ secretsLoaded++;
1787
+ }
1788
+ }
1789
+ if (secretsToLoad.includes("VOICE_APPLICATION_SECRET")) {
1790
+ const applicationKey = process.env.VOICE_APPLICATION_KEY || this.getApplicationKeyFromConfig();
1791
+ if (applicationKey) {
1792
+ const appSecret = await keytar.getPassword(this.SERVICE_NAME, applicationKey);
1793
+ if (appSecret) {
1794
+ process.env.VOICE_APPLICATION_SECRET = appSecret;
1795
+ console.log("\u2705 Loaded VOICE_APPLICATION_SECRET from secure storage");
1796
+ secretsLoaded++;
1797
+ }
1798
+ }
1799
+ }
1800
+ const functionName = this.getFunctionNameFromConfig();
1801
+ for (const secretName of secretsToLoad) {
1802
+ if (secretName === "PROJECT_ID_API_SECRET" || secretName === "VOICE_APPLICATION_SECRET") {
1803
+ continue;
1804
+ }
1805
+ if (functionName) {
1806
+ const value = await keytar.getPassword(this.SERVICE_NAME, `${functionName}-${secretName}`);
1807
+ if (value) {
1808
+ process.env[secretName] = value;
1809
+ console.log(`\u2705 Loaded ${secretName} from secure storage`);
1810
+ secretsLoaded++;
1811
+ }
1812
+ }
1813
+ }
1814
+ if (secretsLoaded === 0) {
1815
+ console.log("\u2139\uFE0F No secrets found in secure storage for declared variables");
1816
+ console.log("\u{1F4A1} To configure Sinch auth: sinch auth login");
1817
+ console.log("\u{1F4A1} To add custom secrets: sinch functions secrets add <KEY> <VALUE>");
1818
+ }
1819
+ return secretsLoaded > 0;
1820
+ } catch (error) {
1821
+ console.error("[Secrets] Unexpected error:", error.message);
1822
+ console.log("\u{1F4A1} To manage secrets manually, use: sinch functions secrets");
1823
+ return false;
1824
+ }
1825
+ }
1826
+ /**
1827
+ * Helper to get application key from sinch.json
1828
+ */
1829
+ getApplicationKeyFromConfig() {
1830
+ try {
1831
+ const sinchJsonPath = path4.join(process.cwd(), "sinch.json");
1832
+ if (fs3.existsSync(sinchJsonPath)) {
1833
+ const sinchConfig = JSON.parse(fs3.readFileSync(sinchJsonPath, "utf8"));
1834
+ return sinchConfig.voiceAppId || sinchConfig.applicationKey || null;
1835
+ }
1836
+ } catch (error) {
1837
+ console.debug("[Secrets] Could not read sinch.json:", error.message);
1838
+ }
1839
+ return null;
1840
+ }
1841
+ /**
1842
+ * Helper to get function name from sinch.json
1843
+ */
1844
+ getFunctionNameFromConfig() {
1845
+ try {
1846
+ const sinchJsonPath = path4.join(process.cwd(), "sinch.json");
1847
+ if (fs3.existsSync(sinchJsonPath)) {
1848
+ const sinchConfig = JSON.parse(fs3.readFileSync(sinchJsonPath, "utf8"));
1849
+ return sinchConfig.name || null;
1850
+ }
1851
+ } catch (error) {
1852
+ console.debug("[Secrets] Could not read sinch.json:", error.message);
1853
+ }
1854
+ return null;
1855
+ }
1856
+ /**
1857
+ * Load custom secrets added via 'sinch functions secrets' command
1858
+ */
1859
+ async loadCustomSecrets(secretNames = []) {
1860
+ const secrets = {};
1861
+ try {
1862
+ const keytar = await import("keytar");
1863
+ const functionName = this.getFunctionNameFromConfig();
1864
+ if (!functionName) {
1865
+ console.debug("[Secrets] Could not determine function name for custom secrets");
1866
+ return secrets;
1867
+ }
1868
+ for (const secretName of secretNames) {
1869
+ const value = await keytar.getPassword(this.SERVICE_NAME, `${functionName}-${secretName}`);
1870
+ if (value) {
1871
+ secrets[secretName] = value;
1872
+ process.env[secretName] = value;
1873
+ }
1874
+ }
1875
+ } catch (error) {
1876
+ console.debug("[Secrets] Could not load custom secrets:", error.message);
1877
+ }
1878
+ return secrets;
1879
+ }
1880
+ /**
1881
+ * Check if keytar is available
1882
+ */
1883
+ async isAvailable() {
1884
+ try {
1885
+ await import("keytar");
1886
+ return true;
1887
+ } catch {
1888
+ return false;
1889
+ }
1890
+ }
1891
+ };
1892
+ var secretsLoader = new SecretsLoader();
1893
+ export {
1894
+ AceSvamlBuilder,
1895
+ DefaultEndpointHandler,
1896
+ IceSvamlBuilder,
1897
+ LocalCache,
1898
+ MenuBuilder,
1899
+ MenuTemplates,
1900
+ NOTIFICATION_EVENTS,
1901
+ PieSvamlBuilder,
1902
+ SecretsLoader,
1903
+ TemplateRender,
1904
+ TunnelClient,
1905
+ UniversalConfig,
1906
+ VOICE_CALLBACKS,
1907
+ VersionExtractor,
1908
+ buildBaseContext,
1909
+ toCamelCase as convertToCamelCase,
1910
+ createAceBuilder,
1911
+ createApp,
1912
+ createCacheClient,
1913
+ createConfig,
1914
+ createErrorResponse,
1915
+ createIceBuilder,
1916
+ createLenientJsonParser as createJsonParser,
1917
+ createJsonResponse,
1918
+ createLenientJsonParser,
1919
+ createMenu,
1920
+ createPieBuilder,
1921
+ createResponse,
1922
+ createSimpleMenu,
1923
+ createUniversalConfig,
1924
+ extractFunctionName,
1925
+ formatCustomResponse,
1926
+ formatSvamlResponse,
1927
+ generateRequestId,
1928
+ getSinchClients,
1929
+ getTunnelClient,
1930
+ handleCustomEndpoint,
1931
+ handleVoiceCallback,
1932
+ isNotificationEvent,
1933
+ isVoiceCallback,
1934
+ parseJson,
1935
+ parseJson as parseJsonLenient,
1936
+ resetSinchClients,
1937
+ secretsLoader,
1938
+ setupJsonParsing as setupJsonMiddleware,
1939
+ setupJsonParsing,
1940
+ setupRequestHandler,
1941
+ toCamelCase,
1942
+ transformKeys,
1943
+ transformKeys as transformKeysToCamelCase,
1944
+ validateVoiceRequest
1945
+ };
1946
+ //# sourceMappingURL=index.js.map