@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/bin/sinch-runtime.js +446 -73
- package/dist/bin/sinch-runtime.js.map +1 -1
- package/dist/index.d.ts +100 -2
- package/dist/index.js +413 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -4
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(
|
|
1049
|
+
function extractFunctionName(path6, body) {
|
|
1025
1050
|
if (body?.event && isVoiceCallback(body.event)) {
|
|
1026
1051
|
return body.event;
|
|
1027
1052
|
}
|
|
1028
|
-
const pathname =
|
|
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
|
|
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 (
|
|
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
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
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
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2507
|
-
|
|
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
|
|
2835
|
-
import
|
|
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
|
-
|
|
2851
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
2942
|
-
if (
|
|
2943
|
-
const sinchConfig = JSON.parse(
|
|
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 =
|
|
2957
|
-
if (
|
|
2958
|
-
const sinchConfig = JSON.parse(
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|