@sinch/functions-runtime 0.3.9 → 0.4.1

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 CHANGED
@@ -934,6 +934,31 @@ function setupJsonParsing(app, options = {}) {
934
934
  return app;
935
935
  }
936
936
 
937
+ // ../runtime-shared/dist/auth/basic-auth.js
938
+ import { timingSafeEqual } from "crypto";
939
+ function validateBasicAuth(authHeader, expectedKey, expectedSecret) {
940
+ if (!authHeader) {
941
+ return false;
942
+ }
943
+ if (!authHeader.toLowerCase().startsWith("basic ")) {
944
+ return false;
945
+ }
946
+ const decoded = Buffer.from(authHeader.slice(6), "base64").toString("utf-8");
947
+ const colonIndex = decoded.indexOf(":");
948
+ if (colonIndex === -1) {
949
+ return false;
950
+ }
951
+ const providedKey = decoded.slice(0, colonIndex);
952
+ const providedSecret = decoded.slice(colonIndex + 1);
953
+ const expectedKeyBuf = Buffer.from(expectedKey);
954
+ const providedKeyBuf = Buffer.from(providedKey);
955
+ const expectedSecretBuf = Buffer.from(expectedSecret);
956
+ const providedSecretBuf = Buffer.from(providedSecret);
957
+ const keyMatch = expectedKeyBuf.length === providedKeyBuf.length && timingSafeEqual(expectedKeyBuf, providedKeyBuf);
958
+ const secretMatch = expectedSecretBuf.length === providedSecretBuf.length && timingSafeEqual(expectedSecretBuf, providedSecretBuf);
959
+ return keyMatch && secretMatch;
960
+ }
961
+
937
962
  // ../runtime-shared/dist/host/app.js
938
963
  import { createRequire as createRequire2 } from "module";
939
964
  import { pathToFileURL } from "url";
@@ -1021,11 +1046,11 @@ function isVoiceCallback(functionName) {
1021
1046
  function isNotificationEvent(functionName) {
1022
1047
  return NOTIFICATION_EVENTS.includes(functionName);
1023
1048
  }
1024
- function extractFunctionName(path5, body) {
1049
+ function extractFunctionName(path6, body) {
1025
1050
  if (body?.event && isVoiceCallback(body.event)) {
1026
1051
  return body.event;
1027
1052
  }
1028
- const pathname = path5.split("?")[0];
1053
+ const pathname = path6.split("?")[0];
1029
1054
  const segments = pathname.split("/").filter((s) => s && s !== "*");
1030
1055
  if (segments.length === 1 && isVoiceCallback(segments[0])) {
1031
1056
  return segments[0];
@@ -1042,10 +1067,10 @@ function generateRequestId() {
1042
1067
  return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
1043
1068
  }
1044
1069
  function findFunctionPath() {
1045
- const fs4 = requireCjs2("fs");
1070
+ const fs5 = requireCjs2("fs");
1046
1071
  const distPath = nodePath.join(process.cwd(), "dist", "function.js");
1047
1072
  const rootPath = nodePath.join(process.cwd(), "function.js");
1048
- if (fs4.existsSync(distPath)) {
1073
+ if (fs5.existsSync(distPath)) {
1049
1074
  return distPath;
1050
1075
  }
1051
1076
  return rootPath;
@@ -1123,6 +1148,15 @@ var noOpCache = {
1123
1148
  keys: async () => [],
1124
1149
  getMany: async () => ({})
1125
1150
  };
1151
+ var noOpStorage = {
1152
+ write: async () => {
1153
+ },
1154
+ read: async () => Buffer.alloc(0),
1155
+ list: async () => [],
1156
+ exists: async () => false,
1157
+ delete: async () => {
1158
+ }
1159
+ };
1126
1160
  function buildBaseContext(req, config = {}) {
1127
1161
  return {
1128
1162
  requestId: req.headers["x-request-id"] || generateRequestId(),
@@ -1135,6 +1169,8 @@ function buildBaseContext(req, config = {}) {
1135
1169
  variables: config.variables
1136
1170
  },
1137
1171
  cache: noOpCache,
1172
+ storage: noOpStorage,
1173
+ database: "",
1138
1174
  assets: (filename) => {
1139
1175
  const filePath = nodePath.join(process.cwd(), "assets", filename);
1140
1176
  return nodeFs.promises.readFile(filePath, "utf-8");
@@ -1225,7 +1261,7 @@ function setupRequestHandler(app, options = {}) {
1225
1261
  const functionUrl = pathToFileURL(functionPath).href;
1226
1262
  const module = await import(functionUrl);
1227
1263
  return module.default || module;
1228
- }, buildContext = buildBaseContext, logger = console.log, landingPageEnabled = true, onRequestStart = () => {
1264
+ }, buildContext = buildBaseContext, logger = console.log, landingPageEnabled = true, authConfig, authKey, authSecret, onRequestStart = () => {
1229
1265
  }, onRequestEnd = () => {
1230
1266
  } } = options;
1231
1267
  app.use("/{*splat}", async (req, res) => {
@@ -1253,6 +1289,17 @@ function setupRequestHandler(app, options = {}) {
1253
1289
  try {
1254
1290
  const functionName = extractFunctionName(req.originalUrl, req.body);
1255
1291
  logger(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${req.method} ${req.path} -> ${functionName}`);
1292
+ if (authConfig && authKey && authSecret) {
1293
+ const needsAuth = authConfig === "*" || Array.isArray(authConfig) && authConfig.includes(functionName);
1294
+ if (needsAuth) {
1295
+ const isValid = validateBasicAuth(req.headers.authorization, authKey, authSecret);
1296
+ if (!isValid) {
1297
+ logger(`[AUTH] Rejected unauthorized request to ${functionName}`);
1298
+ res.status(401).set("WWW-Authenticate", 'Basic realm="sinch-function"').json({ error: "Unauthorized" });
1299
+ return;
1300
+ }
1301
+ }
1302
+ }
1256
1303
  onRequestStart({ functionName, req });
1257
1304
  const context = buildContext(req);
1258
1305
  const userFunction = await Promise.resolve(loadUserFunction());
@@ -2440,12 +2487,82 @@ function createCacheClient(_projectId, _functionName) {
2440
2487
  return new LocalCache();
2441
2488
  }
2442
2489
 
2490
+ // src/storage/local.ts
2491
+ import * as fs3 from "fs/promises";
2492
+ import * as path4 from "path";
2493
+ var LocalStorage = class {
2494
+ baseDir;
2495
+ constructor(baseDir) {
2496
+ this.baseDir = baseDir ?? path4.join(process.cwd(), ".sinch", "storage");
2497
+ }
2498
+ resolvePath(key) {
2499
+ const sanitized = key.replace(/^\/+/, "").replace(/\.\./g, "_");
2500
+ return path4.join(this.baseDir, sanitized);
2501
+ }
2502
+ async write(key, data) {
2503
+ const filePath = this.resolvePath(key);
2504
+ await fs3.mkdir(path4.dirname(filePath), { recursive: true });
2505
+ await fs3.writeFile(filePath, data);
2506
+ }
2507
+ async read(key) {
2508
+ const filePath = this.resolvePath(key);
2509
+ return fs3.readFile(filePath);
2510
+ }
2511
+ async list(prefix) {
2512
+ const results = [];
2513
+ await this.walkDir(this.baseDir, "", results);
2514
+ if (prefix) {
2515
+ return results.filter((f) => f.startsWith(prefix));
2516
+ }
2517
+ return results;
2518
+ }
2519
+ async exists(key) {
2520
+ const filePath = this.resolvePath(key);
2521
+ try {
2522
+ await fs3.access(filePath);
2523
+ return true;
2524
+ } catch {
2525
+ return false;
2526
+ }
2527
+ }
2528
+ async delete(key) {
2529
+ const filePath = this.resolvePath(key);
2530
+ await fs3.rm(filePath, { force: true });
2531
+ }
2532
+ async walkDir(dir, relative, results) {
2533
+ let entries;
2534
+ try {
2535
+ entries = await fs3.readdir(dir, { withFileTypes: true });
2536
+ } catch {
2537
+ return;
2538
+ }
2539
+ for (const entry of entries) {
2540
+ const rel = relative ? `${relative}/${entry.name}` : entry.name;
2541
+ if (entry.isDirectory()) {
2542
+ await this.walkDir(path4.join(dir, entry.name), rel, results);
2543
+ } else {
2544
+ results.push(rel);
2545
+ }
2546
+ }
2547
+ }
2548
+ };
2549
+ function createStorageClient(baseDir) {
2550
+ return new LocalStorage(baseDir);
2551
+ }
2552
+
2443
2553
  // src/tunnel/index.ts
2444
2554
  import WebSocket from "ws";
2445
2555
  import axios from "axios";
2446
2556
 
2447
2557
  // src/tunnel/webhook-config.ts
2448
2558
  import { SinchClient as SinchClient2 } from "@sinch/sdk-core";
2559
+ var SINCH_FN_URL_PATTERN = /\.fn(-\w+)?\.sinch\.com/;
2560
+ function isOurWebhook(target) {
2561
+ return !!target && SINCH_FN_URL_PATTERN.test(target);
2562
+ }
2563
+ function isTunnelUrl(target) {
2564
+ return !!target && target.includes("tunnel.fn");
2565
+ }
2449
2566
  async function configureConversationWebhooks(tunnelUrl, config) {
2450
2567
  try {
2451
2568
  const conversationAppId = process.env.CONVERSATION_APP_ID;
@@ -2467,24 +2584,23 @@ async function configureConversationWebhooks(tunnelUrl, config) {
2467
2584
  app_id: conversationAppId
2468
2585
  });
2469
2586
  const existingWebhooks = webhooksResult.webhooks || [];
2470
- const tunnelWebhooks = existingWebhooks.filter((w) => w.target?.includes("/api/ingress/"));
2471
- for (const staleWebhook of tunnelWebhooks) {
2472
- try {
2473
- await sinchClient.conversation.webhooks.delete({ webhook_id: staleWebhook.id });
2474
- console.log(`\u{1F9F9} Cleaned up stale tunnel webhook: ${staleWebhook.id}`);
2475
- } catch (err) {
2476
- }
2587
+ const deployedWebhook = existingWebhooks.find(
2588
+ (w) => isOurWebhook(w.target)
2589
+ );
2590
+ if (!deployedWebhook || !deployedWebhook.id) {
2591
+ console.log("\u26A0\uFE0F No deployed webhook found \u2014 deploy first");
2592
+ return;
2477
2593
  }
2478
- const createResult = await sinchClient.conversation.webhooks.create({
2479
- webhookCreateRequestBody: {
2480
- app_id: conversationAppId,
2481
- target: webhookUrl,
2482
- target_type: "HTTP",
2483
- triggers: ["MESSAGE_INBOUND"]
2484
- }
2594
+ config.conversationWebhookId = deployedWebhook.id;
2595
+ config.originalTarget = deployedWebhook.target;
2596
+ await sinchClient.conversation.webhooks.update({
2597
+ webhook_id: deployedWebhook.id,
2598
+ webhookUpdateRequestBody: {
2599
+ target: webhookUrl
2600
+ },
2601
+ update_mask: ["target"]
2485
2602
  });
2486
- config.conversationWebhookId = createResult.id;
2487
- console.log(`\u2705 Created Conversation webhook: ${webhookUrl}`);
2603
+ console.log(`\u2705 Updated Conversation webhook to tunnel: ${webhookUrl}`);
2488
2604
  console.log("\u{1F4AC} Send a message to your Conversation app to test!");
2489
2605
  } catch (error) {
2490
2606
  console.log("\u26A0\uFE0F Could not configure Conversation webhooks:", error.message);
@@ -2503,10 +2619,29 @@ async function cleanupConversationWebhook(config) {
2503
2619
  keyId,
2504
2620
  keySecret
2505
2621
  });
2506
- await sinchClient.conversation.webhooks.delete({ webhook_id: config.conversationWebhookId });
2507
- console.log("\u{1F9F9} Cleaned up tunnel webhook");
2622
+ let restoreTarget = config.originalTarget;
2623
+ if (!restoreTarget || isTunnelUrl(restoreTarget)) {
2624
+ const functionName = process.env.FUNCTION_NAME || process.env.FUNCTION_ID;
2625
+ if (functionName) {
2626
+ restoreTarget = `https://${functionName}.fn-dev.sinch.com/webhook/conversation`;
2627
+ console.log(`\u{1F527} Derived restore target from env: ${restoreTarget}`);
2628
+ } else {
2629
+ console.log("\u26A0\uFE0F Cannot restore webhook \u2014 no FUNCTION_NAME or FUNCTION_ID available");
2630
+ return;
2631
+ }
2632
+ }
2633
+ await sinchClient.conversation.webhooks.update({
2634
+ webhook_id: config.conversationWebhookId,
2635
+ webhookUpdateRequestBody: {
2636
+ target: restoreTarget
2637
+ },
2638
+ update_mask: ["target"]
2639
+ });
2640
+ console.log(`\u{1F504} Restored webhook target to: ${restoreTarget}`);
2508
2641
  config.conversationWebhookId = void 0;
2642
+ config.originalTarget = void 0;
2509
2643
  } catch (error) {
2644
+ console.log("\u26A0\uFE0F Could not restore Conversation webhook:", error.message);
2510
2645
  }
2511
2646
  }
2512
2647
  async function configureElevenLabs() {
@@ -2831,9 +2966,242 @@ function getTunnelClient(localPort = 3e3) {
2831
2966
  }
2832
2967
 
2833
2968
  // src/secrets/index.ts
2834
- import fs3 from "fs";
2835
- import path4 from "path";
2969
+ import fs4 from "fs";
2970
+ import path5 from "path";
2836
2971
  import os from "os";
2972
+
2973
+ // src/secrets/keychain.ts
2974
+ import { execFile, spawn } from "child_process";
2975
+ import { promisify } from "util";
2976
+ var execFileAsync = promisify(execFile);
2977
+ function b64(value) {
2978
+ return Buffer.from(value, "utf8").toString("base64");
2979
+ }
2980
+ function psParam(name, value) {
2981
+ return `$${name} = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${b64(value)}'));`;
2982
+ }
2983
+ var WIN32_CRED_READ_SCRIPT = `
2984
+ Add-Type -TypeDefinition @'
2985
+ using System;
2986
+ using System.Runtime.InteropServices;
2987
+ public class CredManager {
2988
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
2989
+ private struct CREDENTIAL {
2990
+ public int Flags;
2991
+ public int Type;
2992
+ public IntPtr TargetName;
2993
+ public IntPtr Comment;
2994
+ public long LastWritten;
2995
+ public int CredentialBlobSize;
2996
+ public IntPtr CredentialBlob;
2997
+ public int Persist;
2998
+ public int AttributeCount;
2999
+ public IntPtr Attributes;
3000
+ public IntPtr TargetAlias;
3001
+ public IntPtr UserName;
3002
+ }
3003
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
3004
+ private static extern bool CredReadW(string target, int type, int flags, out IntPtr cred);
3005
+ [DllImport("advapi32.dll")]
3006
+ private static extern void CredFree(IntPtr cred);
3007
+ public static string Read(string target) {
3008
+ IntPtr credPtr;
3009
+ if (!CredReadW(target, 1, 0, out credPtr)) return null;
3010
+ try {
3011
+ CREDENTIAL c = (CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(CREDENTIAL));
3012
+ if (c.CredentialBlobSize > 0 && c.CredentialBlob != IntPtr.Zero)
3013
+ return Marshal.PtrToStringUni(c.CredentialBlob, c.CredentialBlobSize / 2);
3014
+ return "";
3015
+ } finally { CredFree(credPtr); }
3016
+ }
3017
+ }
3018
+ '@
3019
+ $r = [CredManager]::Read($target)
3020
+ if ($r -ne $null) { [Console]::Write($r) }
3021
+ else { exit 1 }
3022
+ `;
3023
+ var WIN32_CRED_WRITE_SCRIPT = `
3024
+ Add-Type -TypeDefinition @'
3025
+ using System;
3026
+ using System.Runtime.InteropServices;
3027
+ using System.Text;
3028
+ public class CredWriter {
3029
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
3030
+ private struct CREDENTIAL {
3031
+ public int Flags;
3032
+ public int Type;
3033
+ public string TargetName;
3034
+ public string Comment;
3035
+ public long LastWritten;
3036
+ public int CredentialBlobSize;
3037
+ public IntPtr CredentialBlob;
3038
+ public int Persist;
3039
+ public int AttributeCount;
3040
+ public IntPtr Attributes;
3041
+ public string TargetAlias;
3042
+ public string UserName;
3043
+ }
3044
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
3045
+ private static extern bool CredWriteW(ref CREDENTIAL cred, int flags);
3046
+ public static bool Write(string target, string password) {
3047
+ byte[] blob = Encoding.Unicode.GetBytes(password);
3048
+ CREDENTIAL c = new CREDENTIAL();
3049
+ c.Type = 1;
3050
+ c.TargetName = target;
3051
+ c.CredentialBlobSize = blob.Length;
3052
+ c.CredentialBlob = Marshal.AllocHGlobal(blob.Length);
3053
+ Marshal.Copy(blob, 0, c.CredentialBlob, blob.Length);
3054
+ c.Persist = 2;
3055
+ try { return CredWriteW(ref c, 0); }
3056
+ finally { Marshal.FreeHGlobal(c.CredentialBlob); }
3057
+ }
3058
+ }
3059
+ '@
3060
+ if (-not [CredWriter]::Write($target, $password)) { exit 1 }
3061
+ `;
3062
+ var WIN32_CRED_DELETE_SCRIPT = `
3063
+ Add-Type -TypeDefinition @'
3064
+ using System;
3065
+ using System.Runtime.InteropServices;
3066
+ public class CredDeleter {
3067
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
3068
+ private static extern bool CredDeleteW(string target, int type, int flags);
3069
+ public static bool Delete(string target) { return CredDeleteW(target, 1, 0); }
3070
+ }
3071
+ '@
3072
+ if (-not [CredDeleter]::Delete($target)) { exit 1 }
3073
+ `;
3074
+ function winTarget(service, account) {
3075
+ return `${service}/${account}`;
3076
+ }
3077
+ var windowsKeychain = {
3078
+ async getPassword(service, account) {
3079
+ try {
3080
+ const params = psParam("target", winTarget(service, account));
3081
+ const { stdout } = await execFileAsync(
3082
+ "powershell.exe",
3083
+ ["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_READ_SCRIPT],
3084
+ { timeout: 15e3, windowsHide: true }
3085
+ );
3086
+ return stdout;
3087
+ } catch {
3088
+ return null;
3089
+ }
3090
+ },
3091
+ async setPassword(service, account, password) {
3092
+ const params = psParam("target", winTarget(service, account)) + psParam("password", password);
3093
+ await execFileAsync(
3094
+ "powershell.exe",
3095
+ ["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_WRITE_SCRIPT],
3096
+ { timeout: 15e3, windowsHide: true }
3097
+ );
3098
+ },
3099
+ async deletePassword(service, account) {
3100
+ try {
3101
+ const params = psParam("target", winTarget(service, account));
3102
+ await execFileAsync(
3103
+ "powershell.exe",
3104
+ ["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_DELETE_SCRIPT],
3105
+ { timeout: 15e3, windowsHide: true }
3106
+ );
3107
+ return true;
3108
+ } catch {
3109
+ return false;
3110
+ }
3111
+ }
3112
+ };
3113
+ var macKeychain = {
3114
+ async getPassword(service, account) {
3115
+ try {
3116
+ const { stdout } = await execFileAsync(
3117
+ "security",
3118
+ ["find-generic-password", "-s", service, "-a", account, "-w"],
3119
+ { timeout: 15e3 }
3120
+ );
3121
+ return stdout.trimEnd();
3122
+ } catch {
3123
+ return null;
3124
+ }
3125
+ },
3126
+ async setPassword(service, account, password) {
3127
+ await execFileAsync(
3128
+ "security",
3129
+ ["add-generic-password", "-U", "-s", service, "-a", account, "-w", password],
3130
+ { timeout: 15e3 }
3131
+ );
3132
+ },
3133
+ async deletePassword(service, account) {
3134
+ try {
3135
+ await execFileAsync(
3136
+ "security",
3137
+ ["delete-generic-password", "-s", service, "-a", account],
3138
+ { timeout: 15e3 }
3139
+ );
3140
+ return true;
3141
+ } catch {
3142
+ return false;
3143
+ }
3144
+ }
3145
+ };
3146
+ var linuxKeychain = {
3147
+ async getPassword(service, account) {
3148
+ try {
3149
+ const { stdout } = await execFileAsync(
3150
+ "secret-tool",
3151
+ ["lookup", "service", service, "account", account],
3152
+ { timeout: 15e3 }
3153
+ );
3154
+ return stdout.trimEnd();
3155
+ } catch {
3156
+ return null;
3157
+ }
3158
+ },
3159
+ // secret-tool reads password from stdin (avoids exposing it in process args)
3160
+ async setPassword(service, account, password) {
3161
+ const child = spawn(
3162
+ "secret-tool",
3163
+ ["store", "--label", `${service}/${account}`, "service", service, "account", account],
3164
+ { stdio: ["pipe", "pipe", "pipe"] }
3165
+ );
3166
+ child.stdin.write(password);
3167
+ child.stdin.end();
3168
+ await new Promise((resolve, reject) => {
3169
+ child.on(
3170
+ "close",
3171
+ (code) => code === 0 ? resolve() : reject(new Error("secret-tool store failed"))
3172
+ );
3173
+ child.on("error", reject);
3174
+ });
3175
+ },
3176
+ async deletePassword(service, account) {
3177
+ try {
3178
+ await execFileAsync(
3179
+ "secret-tool",
3180
+ ["clear", "service", service, "account", account],
3181
+ { timeout: 15e3 }
3182
+ );
3183
+ return true;
3184
+ } catch {
3185
+ return false;
3186
+ }
3187
+ }
3188
+ };
3189
+ function getBackend() {
3190
+ switch (process.platform) {
3191
+ case "win32":
3192
+ return windowsKeychain;
3193
+ case "darwin":
3194
+ return macKeychain;
3195
+ default:
3196
+ return linuxKeychain;
3197
+ }
3198
+ }
3199
+ var backend = getBackend();
3200
+ async function getPassword(service, account) {
3201
+ return backend.getPassword(service, account);
3202
+ }
3203
+
3204
+ // src/secrets/index.ts
2837
3205
  var SecretsLoader = class {
2838
3206
  // Same service name as CLI uses
2839
3207
  SERVICE_NAME = "sinch-functions-cli";
@@ -2847,24 +3215,12 @@ var SecretsLoader = class {
2847
3215
  return false;
2848
3216
  }
2849
3217
  try {
2850
- let keytar;
2851
- try {
2852
- keytar = await import("keytar");
2853
- } catch (error) {
2854
- if (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_MODULE_NOT_FOUND") {
2855
- console.debug("[Secrets] Keytar not available - secrets not loaded");
2856
- return false;
2857
- } else {
2858
- console.error("[Secrets] Error loading keytar:", error.message);
2859
- }
2860
- return false;
2861
- }
2862
- const envPath = path4.join(process.cwd(), ".env");
2863
- if (!fs3.existsSync(envPath)) {
3218
+ const envPath = path5.join(process.cwd(), ".env");
3219
+ if (!fs4.existsSync(envPath)) {
2864
3220
  console.debug("[Secrets] No .env file found, skipping keychain load");
2865
3221
  return false;
2866
3222
  }
2867
- const envContent = fs3.readFileSync(envPath, "utf8");
3223
+ const envContent = fs4.readFileSync(envPath, "utf8");
2868
3224
  const envLines = envContent.replace(/\r\n/g, "\n").split("\n");
2869
3225
  const secretsToLoad = [];
2870
3226
  envLines.forEach((line) => {
@@ -2886,7 +3242,7 @@ var SecretsLoader = class {
2886
3242
  }
2887
3243
  let secretsLoaded = 0;
2888
3244
  if (secretsToLoad.includes("PROJECT_ID_API_SECRET")) {
2889
- const apiSecret = await keytar.getPassword(this.SERVICE_NAME, `${this.username}-keySecret`);
3245
+ const apiSecret = await getPassword(this.SERVICE_NAME, `${this.username}-keySecret`);
2890
3246
  if (apiSecret) {
2891
3247
  process.env.PROJECT_ID_API_SECRET = apiSecret;
2892
3248
  console.log("\u2705 Loaded PROJECT_ID_API_SECRET from secure storage");
@@ -2896,7 +3252,7 @@ var SecretsLoader = class {
2896
3252
  if (secretsToLoad.includes("VOICE_APPLICATION_SECRET")) {
2897
3253
  const applicationKey = process.env.VOICE_APPLICATION_KEY || this.getApplicationKeyFromConfig();
2898
3254
  if (applicationKey) {
2899
- const appSecret = await keytar.getPassword(this.SERVICE_NAME, applicationKey);
3255
+ const appSecret = await getPassword(this.SERVICE_NAME, applicationKey);
2900
3256
  if (appSecret) {
2901
3257
  process.env.VOICE_APPLICATION_SECRET = appSecret;
2902
3258
  console.log("\u2705 Loaded VOICE_APPLICATION_SECRET from secure storage");
@@ -2910,7 +3266,7 @@ var SecretsLoader = class {
2910
3266
  continue;
2911
3267
  }
2912
3268
  if (functionName) {
2913
- const value = await keytar.getPassword(
3269
+ const value = await getPassword(
2914
3270
  this.SERVICE_NAME,
2915
3271
  `${functionName}-${secretName}`
2916
3272
  );
@@ -2938,9 +3294,9 @@ var SecretsLoader = class {
2938
3294
  */
2939
3295
  getApplicationKeyFromConfig() {
2940
3296
  try {
2941
- const sinchJsonPath = path4.join(process.cwd(), "sinch.json");
2942
- if (fs3.existsSync(sinchJsonPath)) {
2943
- const sinchConfig = JSON.parse(fs3.readFileSync(sinchJsonPath, "utf8"));
3297
+ const sinchJsonPath = path5.join(process.cwd(), "sinch.json");
3298
+ if (fs4.existsSync(sinchJsonPath)) {
3299
+ const sinchConfig = JSON.parse(fs4.readFileSync(sinchJsonPath, "utf8"));
2944
3300
  return sinchConfig.voiceAppId || sinchConfig.applicationKey || null;
2945
3301
  }
2946
3302
  } catch (error) {
@@ -2953,9 +3309,9 @@ var SecretsLoader = class {
2953
3309
  */
2954
3310
  getFunctionNameFromConfig() {
2955
3311
  try {
2956
- const sinchJsonPath = path4.join(process.cwd(), "sinch.json");
2957
- if (fs3.existsSync(sinchJsonPath)) {
2958
- const sinchConfig = JSON.parse(fs3.readFileSync(sinchJsonPath, "utf8"));
3312
+ const sinchJsonPath = path5.join(process.cwd(), "sinch.json");
3313
+ if (fs4.existsSync(sinchJsonPath)) {
3314
+ const sinchConfig = JSON.parse(fs4.readFileSync(sinchJsonPath, "utf8"));
2959
3315
  return sinchConfig.name || null;
2960
3316
  }
2961
3317
  } catch (error) {
@@ -2969,14 +3325,13 @@ var SecretsLoader = class {
2969
3325
  async loadCustomSecrets(secretNames = []) {
2970
3326
  const secrets = {};
2971
3327
  try {
2972
- const keytar = await import("keytar");
2973
3328
  const functionName = this.getFunctionNameFromConfig();
2974
3329
  if (!functionName) {
2975
3330
  console.debug("[Secrets] Could not determine function name for custom secrets");
2976
3331
  return secrets;
2977
3332
  }
2978
3333
  for (const secretName of secretNames) {
2979
- const value = await keytar.getPassword(this.SERVICE_NAME, `${functionName}-${secretName}`);
3334
+ const value = await getPassword(this.SERVICE_NAME, `${functionName}-${secretName}`);
2980
3335
  if (value) {
2981
3336
  secrets[secretName] = value;
2982
3337
  process.env[secretName] = value;
@@ -2988,15 +3343,13 @@ var SecretsLoader = class {
2988
3343
  return secrets;
2989
3344
  }
2990
3345
  /**
2991
- * Check if keytar is available
3346
+ * Check if the native keychain is available.
3347
+ * Always true — no native npm deps required. The underlying OS tool
3348
+ * (secret-tool, security, CredManager) may still be absent, in which
3349
+ * case getPassword() silently returns null per credential.
2992
3350
  */
2993
3351
  async isAvailable() {
2994
- try {
2995
- await import("keytar");
2996
- return true;
2997
- } catch {
2998
- return false;
2999
- }
3352
+ return true;
3000
3353
  }
3001
3354
  };
3002
3355
  var secretsLoader = new SecretsLoader();
@@ -3012,6 +3365,7 @@ export {
3012
3365
  ElevenLabsState,
3013
3366
  IceSvamlBuilder,
3014
3367
  LocalCache,
3368
+ LocalStorage,
3015
3369
  MenuBuilder,
3016
3370
  MenuTemplates,
3017
3371
  NOTIFICATION_EVENTS,
@@ -3042,6 +3396,7 @@ export {
3042
3396
  createResponse,
3043
3397
  createSimpleMenu,
3044
3398
  createSms,
3399
+ createStorageClient,
3045
3400
  createUniversalConfig,
3046
3401
  createWhatsApp,
3047
3402
  extractCallerNumber,