@sinch/functions-runtime 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sinch-runtime.js +346 -48
- package/dist/bin/sinch-runtime.js.map +1 -1
- package/dist/index.d.ts +88 -2
- package/dist/index.js +333 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -4
package/dist/index.d.ts
CHANGED
|
@@ -760,7 +760,7 @@ export declare function createPieBuilder(): PieSvamlBuilder;
|
|
|
760
760
|
/**
|
|
761
761
|
* Cache interface for Sinch Functions
|
|
762
762
|
*
|
|
763
|
-
* Both dev (LocalCache) and prod (
|
|
763
|
+
* Both dev (LocalCache) and prod (ApiBackedCache) implement this interface,
|
|
764
764
|
* allowing seamless package swap during deployment.
|
|
765
765
|
*/
|
|
766
766
|
/**
|
|
@@ -865,6 +865,46 @@ export interface IFunctionCache {
|
|
|
865
865
|
*/
|
|
866
866
|
getMany<T = unknown>(keys: string[]): Promise<Record<string, T | null>>;
|
|
867
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Storage interface for Sinch Functions
|
|
870
|
+
*
|
|
871
|
+
* Both dev (LocalStorage) and prod (S3Storage) implement this interface,
|
|
872
|
+
* allowing seamless package swap during deployment.
|
|
873
|
+
*/
|
|
874
|
+
/**
|
|
875
|
+
* Function storage interface
|
|
876
|
+
*
|
|
877
|
+
* Provides file/blob storage for persistent data.
|
|
878
|
+
* In development, uses the local filesystem (./storage/ directory).
|
|
879
|
+
* In production, uses S3 with local disk caching for reads.
|
|
880
|
+
*
|
|
881
|
+
* Access via `context.storage` — do not construct directly.
|
|
882
|
+
*
|
|
883
|
+
* @example
|
|
884
|
+
* ```typescript
|
|
885
|
+
* // Write a file
|
|
886
|
+
* await context.storage.write('reports/daily.json', JSON.stringify(data));
|
|
887
|
+
*
|
|
888
|
+
* // Read it back
|
|
889
|
+
* const buf = await context.storage.read('reports/daily.json');
|
|
890
|
+
* const data = JSON.parse(buf.toString());
|
|
891
|
+
*
|
|
892
|
+
* // List files
|
|
893
|
+
* const files = await context.storage.list('reports/');
|
|
894
|
+
*
|
|
895
|
+
* // Check existence and delete
|
|
896
|
+
* if (await context.storage.exists('reports/old.json')) {
|
|
897
|
+
* await context.storage.delete('reports/old.json');
|
|
898
|
+
* }
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
export interface IFunctionStorage {
|
|
902
|
+
write(key: string, data: string | Buffer): Promise<void>;
|
|
903
|
+
read(key: string): Promise<Buffer>;
|
|
904
|
+
list(prefix?: string): Promise<string[]>;
|
|
905
|
+
exists(key: string): Promise<boolean>;
|
|
906
|
+
delete(key: string): Promise<void>;
|
|
907
|
+
}
|
|
868
908
|
/**
|
|
869
909
|
* Function configuration with environment variables
|
|
870
910
|
*/
|
|
@@ -932,6 +972,37 @@ export interface FunctionContext {
|
|
|
932
972
|
* Available when `ENABLE_NUMBERS_API=true` is set.
|
|
933
973
|
*/
|
|
934
974
|
numbers?: NumbersService;
|
|
975
|
+
/**
|
|
976
|
+
* Persistent file/blob storage.
|
|
977
|
+
* Local filesystem during development, S3-backed in production.
|
|
978
|
+
* @see {@link IFunctionStorage} for available methods
|
|
979
|
+
*/
|
|
980
|
+
storage: IFunctionStorage;
|
|
981
|
+
/**
|
|
982
|
+
* File path to a SQLite database for persistent structured data.
|
|
983
|
+
* The database file is managed by a Litestream sidecar in production
|
|
984
|
+
* (continuous WAL replication to S3). In development, it's a local file.
|
|
985
|
+
*
|
|
986
|
+
* Use any SQLite library — `sql.js` (pure WASM, no native deps) or
|
|
987
|
+
* `better-sqlite3` (native C++, fastest but requires build tooling).
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* ```typescript
|
|
991
|
+
* // sql.js (recommended — works everywhere)
|
|
992
|
+
* import initSqlJs from 'sql.js';
|
|
993
|
+
* const SQL = await initSqlJs();
|
|
994
|
+
* const buf = existsSync(context.database) ? readFileSync(context.database) : undefined;
|
|
995
|
+
* const db = new SQL.Database(buf);
|
|
996
|
+
* db.run('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)');
|
|
997
|
+
* writeFileSync(context.database, Buffer.from(db.export()));
|
|
998
|
+
*
|
|
999
|
+
* // better-sqlite3 (fastest — needs python3/make/g++)
|
|
1000
|
+
* import Database from 'better-sqlite3';
|
|
1001
|
+
* const db = new Database(context.database);
|
|
1002
|
+
* db.exec('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)');
|
|
1003
|
+
* ```
|
|
1004
|
+
*/
|
|
1005
|
+
database: string;
|
|
935
1006
|
/** Read a file from the assets/ directory (private, not served over HTTP) */
|
|
936
1007
|
assets(filename: string): Promise<string>;
|
|
937
1008
|
}
|
|
@@ -2292,6 +2363,18 @@ export declare class LocalCache implements IFunctionCache {
|
|
|
2292
2363
|
* @internal Dev-only factory — access cache via `context.cache`
|
|
2293
2364
|
*/
|
|
2294
2365
|
export declare function createCacheClient(_projectId?: string, _functionName?: string): IFunctionCache;
|
|
2366
|
+
export declare class LocalStorage implements IFunctionStorage {
|
|
2367
|
+
private baseDir;
|
|
2368
|
+
constructor(baseDir?: string);
|
|
2369
|
+
private resolvePath;
|
|
2370
|
+
write(key: string, data: string | Buffer): Promise<void>;
|
|
2371
|
+
read(key: string): Promise<Buffer>;
|
|
2372
|
+
list(prefix?: string): Promise<string[]>;
|
|
2373
|
+
exists(key: string): Promise<boolean>;
|
|
2374
|
+
delete(key: string): Promise<void>;
|
|
2375
|
+
private walkDir;
|
|
2376
|
+
}
|
|
2377
|
+
export declare function createStorageClient(baseDir?: string): LocalStorage;
|
|
2295
2378
|
/**
|
|
2296
2379
|
* Tunnel Client for Sinch Functions
|
|
2297
2380
|
*
|
|
@@ -2380,7 +2463,10 @@ export declare class SecretsLoader {
|
|
|
2380
2463
|
*/
|
|
2381
2464
|
loadCustomSecrets(secretNames?: string[]): Promise<Record<string, string>>;
|
|
2382
2465
|
/**
|
|
2383
|
-
* Check if
|
|
2466
|
+
* Check if the native keychain is available.
|
|
2467
|
+
* Always true — no native npm deps required. The underlying OS tool
|
|
2468
|
+
* (secret-tool, security, CredManager) may still be absent, in which
|
|
2469
|
+
* case getPassword() silently returns null per credential.
|
|
2384
2470
|
*/
|
|
2385
2471
|
isAvailable(): Promise<boolean>;
|
|
2386
2472
|
}
|
package/dist/index.js
CHANGED
|
@@ -1021,11 +1021,11 @@ function isVoiceCallback(functionName) {
|
|
|
1021
1021
|
function isNotificationEvent(functionName) {
|
|
1022
1022
|
return NOTIFICATION_EVENTS.includes(functionName);
|
|
1023
1023
|
}
|
|
1024
|
-
function extractFunctionName(
|
|
1024
|
+
function extractFunctionName(path6, body) {
|
|
1025
1025
|
if (body?.event && isVoiceCallback(body.event)) {
|
|
1026
1026
|
return body.event;
|
|
1027
1027
|
}
|
|
1028
|
-
const pathname =
|
|
1028
|
+
const pathname = path6.split("?")[0];
|
|
1029
1029
|
const segments = pathname.split("/").filter((s) => s && s !== "*");
|
|
1030
1030
|
if (segments.length === 1 && isVoiceCallback(segments[0])) {
|
|
1031
1031
|
return segments[0];
|
|
@@ -1042,10 +1042,10 @@ function generateRequestId() {
|
|
|
1042
1042
|
return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
1043
1043
|
}
|
|
1044
1044
|
function findFunctionPath() {
|
|
1045
|
-
const
|
|
1045
|
+
const fs5 = requireCjs2("fs");
|
|
1046
1046
|
const distPath = nodePath.join(process.cwd(), "dist", "function.js");
|
|
1047
1047
|
const rootPath = nodePath.join(process.cwd(), "function.js");
|
|
1048
|
-
if (
|
|
1048
|
+
if (fs5.existsSync(distPath)) {
|
|
1049
1049
|
return distPath;
|
|
1050
1050
|
}
|
|
1051
1051
|
return rootPath;
|
|
@@ -1123,6 +1123,15 @@ var noOpCache = {
|
|
|
1123
1123
|
keys: async () => [],
|
|
1124
1124
|
getMany: async () => ({})
|
|
1125
1125
|
};
|
|
1126
|
+
var noOpStorage = {
|
|
1127
|
+
write: async () => {
|
|
1128
|
+
},
|
|
1129
|
+
read: async () => Buffer.alloc(0),
|
|
1130
|
+
list: async () => [],
|
|
1131
|
+
exists: async () => false,
|
|
1132
|
+
delete: async () => {
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1126
1135
|
function buildBaseContext(req, config = {}) {
|
|
1127
1136
|
return {
|
|
1128
1137
|
requestId: req.headers["x-request-id"] || generateRequestId(),
|
|
@@ -1135,6 +1144,8 @@ function buildBaseContext(req, config = {}) {
|
|
|
1135
1144
|
variables: config.variables
|
|
1136
1145
|
},
|
|
1137
1146
|
cache: noOpCache,
|
|
1147
|
+
storage: noOpStorage,
|
|
1148
|
+
database: "",
|
|
1138
1149
|
assets: (filename) => {
|
|
1139
1150
|
const filePath = nodePath.join(process.cwd(), "assets", filename);
|
|
1140
1151
|
return nodeFs.promises.readFile(filePath, "utf-8");
|
|
@@ -2440,6 +2451,69 @@ function createCacheClient(_projectId, _functionName) {
|
|
|
2440
2451
|
return new LocalCache();
|
|
2441
2452
|
}
|
|
2442
2453
|
|
|
2454
|
+
// src/storage/local.ts
|
|
2455
|
+
import * as fs3 from "fs/promises";
|
|
2456
|
+
import * as path4 from "path";
|
|
2457
|
+
var LocalStorage = class {
|
|
2458
|
+
baseDir;
|
|
2459
|
+
constructor(baseDir) {
|
|
2460
|
+
this.baseDir = baseDir ?? path4.join(process.cwd(), "storage");
|
|
2461
|
+
}
|
|
2462
|
+
resolvePath(key) {
|
|
2463
|
+
const sanitized = key.replace(/^\/+/, "").replace(/\.\./g, "_");
|
|
2464
|
+
return path4.join(this.baseDir, sanitized);
|
|
2465
|
+
}
|
|
2466
|
+
async write(key, data) {
|
|
2467
|
+
const filePath = this.resolvePath(key);
|
|
2468
|
+
await fs3.mkdir(path4.dirname(filePath), { recursive: true });
|
|
2469
|
+
await fs3.writeFile(filePath, data);
|
|
2470
|
+
}
|
|
2471
|
+
async read(key) {
|
|
2472
|
+
const filePath = this.resolvePath(key);
|
|
2473
|
+
return fs3.readFile(filePath);
|
|
2474
|
+
}
|
|
2475
|
+
async list(prefix) {
|
|
2476
|
+
const results = [];
|
|
2477
|
+
await this.walkDir(this.baseDir, "", results);
|
|
2478
|
+
if (prefix) {
|
|
2479
|
+
return results.filter((f) => f.startsWith(prefix));
|
|
2480
|
+
}
|
|
2481
|
+
return results;
|
|
2482
|
+
}
|
|
2483
|
+
async exists(key) {
|
|
2484
|
+
const filePath = this.resolvePath(key);
|
|
2485
|
+
try {
|
|
2486
|
+
await fs3.access(filePath);
|
|
2487
|
+
return true;
|
|
2488
|
+
} catch {
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
async delete(key) {
|
|
2493
|
+
const filePath = this.resolvePath(key);
|
|
2494
|
+
await fs3.rm(filePath, { force: true });
|
|
2495
|
+
}
|
|
2496
|
+
async walkDir(dir, relative, results) {
|
|
2497
|
+
let entries;
|
|
2498
|
+
try {
|
|
2499
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
2500
|
+
} catch {
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
for (const entry of entries) {
|
|
2504
|
+
const rel = relative ? `${relative}/${entry.name}` : entry.name;
|
|
2505
|
+
if (entry.isDirectory()) {
|
|
2506
|
+
await this.walkDir(path4.join(dir, entry.name), rel, results);
|
|
2507
|
+
} else {
|
|
2508
|
+
results.push(rel);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
};
|
|
2513
|
+
function createStorageClient(baseDir) {
|
|
2514
|
+
return new LocalStorage(baseDir);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2443
2517
|
// src/tunnel/index.ts
|
|
2444
2518
|
import WebSocket from "ws";
|
|
2445
2519
|
import axios from "axios";
|
|
@@ -2831,9 +2905,242 @@ function getTunnelClient(localPort = 3e3) {
|
|
|
2831
2905
|
}
|
|
2832
2906
|
|
|
2833
2907
|
// src/secrets/index.ts
|
|
2834
|
-
import
|
|
2835
|
-
import
|
|
2908
|
+
import fs4 from "fs";
|
|
2909
|
+
import path5 from "path";
|
|
2836
2910
|
import os from "os";
|
|
2911
|
+
|
|
2912
|
+
// src/secrets/keychain.ts
|
|
2913
|
+
import { execFile, spawn } from "child_process";
|
|
2914
|
+
import { promisify } from "util";
|
|
2915
|
+
var execFileAsync = promisify(execFile);
|
|
2916
|
+
function b64(value) {
|
|
2917
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
2918
|
+
}
|
|
2919
|
+
function psParam(name, value) {
|
|
2920
|
+
return `$${name} = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${b64(value)}'));`;
|
|
2921
|
+
}
|
|
2922
|
+
var WIN32_CRED_READ_SCRIPT = `
|
|
2923
|
+
Add-Type -TypeDefinition @'
|
|
2924
|
+
using System;
|
|
2925
|
+
using System.Runtime.InteropServices;
|
|
2926
|
+
public class CredManager {
|
|
2927
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
2928
|
+
private struct CREDENTIAL {
|
|
2929
|
+
public int Flags;
|
|
2930
|
+
public int Type;
|
|
2931
|
+
public IntPtr TargetName;
|
|
2932
|
+
public IntPtr Comment;
|
|
2933
|
+
public long LastWritten;
|
|
2934
|
+
public int CredentialBlobSize;
|
|
2935
|
+
public IntPtr CredentialBlob;
|
|
2936
|
+
public int Persist;
|
|
2937
|
+
public int AttributeCount;
|
|
2938
|
+
public IntPtr Attributes;
|
|
2939
|
+
public IntPtr TargetAlias;
|
|
2940
|
+
public IntPtr UserName;
|
|
2941
|
+
}
|
|
2942
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
2943
|
+
private static extern bool CredReadW(string target, int type, int flags, out IntPtr cred);
|
|
2944
|
+
[DllImport("advapi32.dll")]
|
|
2945
|
+
private static extern void CredFree(IntPtr cred);
|
|
2946
|
+
public static string Read(string target) {
|
|
2947
|
+
IntPtr credPtr;
|
|
2948
|
+
if (!CredReadW(target, 1, 0, out credPtr)) return null;
|
|
2949
|
+
try {
|
|
2950
|
+
CREDENTIAL c = (CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(CREDENTIAL));
|
|
2951
|
+
if (c.CredentialBlobSize > 0 && c.CredentialBlob != IntPtr.Zero)
|
|
2952
|
+
return Marshal.PtrToStringUni(c.CredentialBlob, c.CredentialBlobSize / 2);
|
|
2953
|
+
return "";
|
|
2954
|
+
} finally { CredFree(credPtr); }
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
'@
|
|
2958
|
+
$r = [CredManager]::Read($target)
|
|
2959
|
+
if ($r -ne $null) { [Console]::Write($r) }
|
|
2960
|
+
else { exit 1 }
|
|
2961
|
+
`;
|
|
2962
|
+
var WIN32_CRED_WRITE_SCRIPT = `
|
|
2963
|
+
Add-Type -TypeDefinition @'
|
|
2964
|
+
using System;
|
|
2965
|
+
using System.Runtime.InteropServices;
|
|
2966
|
+
using System.Text;
|
|
2967
|
+
public class CredWriter {
|
|
2968
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
2969
|
+
private struct CREDENTIAL {
|
|
2970
|
+
public int Flags;
|
|
2971
|
+
public int Type;
|
|
2972
|
+
public string TargetName;
|
|
2973
|
+
public string Comment;
|
|
2974
|
+
public long LastWritten;
|
|
2975
|
+
public int CredentialBlobSize;
|
|
2976
|
+
public IntPtr CredentialBlob;
|
|
2977
|
+
public int Persist;
|
|
2978
|
+
public int AttributeCount;
|
|
2979
|
+
public IntPtr Attributes;
|
|
2980
|
+
public string TargetAlias;
|
|
2981
|
+
public string UserName;
|
|
2982
|
+
}
|
|
2983
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
2984
|
+
private static extern bool CredWriteW(ref CREDENTIAL cred, int flags);
|
|
2985
|
+
public static bool Write(string target, string password) {
|
|
2986
|
+
byte[] blob = Encoding.Unicode.GetBytes(password);
|
|
2987
|
+
CREDENTIAL c = new CREDENTIAL();
|
|
2988
|
+
c.Type = 1;
|
|
2989
|
+
c.TargetName = target;
|
|
2990
|
+
c.CredentialBlobSize = blob.Length;
|
|
2991
|
+
c.CredentialBlob = Marshal.AllocHGlobal(blob.Length);
|
|
2992
|
+
Marshal.Copy(blob, 0, c.CredentialBlob, blob.Length);
|
|
2993
|
+
c.Persist = 2;
|
|
2994
|
+
try { return CredWriteW(ref c, 0); }
|
|
2995
|
+
finally { Marshal.FreeHGlobal(c.CredentialBlob); }
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
'@
|
|
2999
|
+
if (-not [CredWriter]::Write($target, $password)) { exit 1 }
|
|
3000
|
+
`;
|
|
3001
|
+
var WIN32_CRED_DELETE_SCRIPT = `
|
|
3002
|
+
Add-Type -TypeDefinition @'
|
|
3003
|
+
using System;
|
|
3004
|
+
using System.Runtime.InteropServices;
|
|
3005
|
+
public class CredDeleter {
|
|
3006
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
3007
|
+
private static extern bool CredDeleteW(string target, int type, int flags);
|
|
3008
|
+
public static bool Delete(string target) { return CredDeleteW(target, 1, 0); }
|
|
3009
|
+
}
|
|
3010
|
+
'@
|
|
3011
|
+
if (-not [CredDeleter]::Delete($target)) { exit 1 }
|
|
3012
|
+
`;
|
|
3013
|
+
function winTarget(service, account) {
|
|
3014
|
+
return `${service}/${account}`;
|
|
3015
|
+
}
|
|
3016
|
+
var windowsKeychain = {
|
|
3017
|
+
async getPassword(service, account) {
|
|
3018
|
+
try {
|
|
3019
|
+
const params = psParam("target", winTarget(service, account));
|
|
3020
|
+
const { stdout } = await execFileAsync(
|
|
3021
|
+
"powershell.exe",
|
|
3022
|
+
["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_READ_SCRIPT],
|
|
3023
|
+
{ timeout: 15e3, windowsHide: true }
|
|
3024
|
+
);
|
|
3025
|
+
return stdout;
|
|
3026
|
+
} catch {
|
|
3027
|
+
return null;
|
|
3028
|
+
}
|
|
3029
|
+
},
|
|
3030
|
+
async setPassword(service, account, password) {
|
|
3031
|
+
const params = psParam("target", winTarget(service, account)) + psParam("password", password);
|
|
3032
|
+
await execFileAsync(
|
|
3033
|
+
"powershell.exe",
|
|
3034
|
+
["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_WRITE_SCRIPT],
|
|
3035
|
+
{ timeout: 15e3, windowsHide: true }
|
|
3036
|
+
);
|
|
3037
|
+
},
|
|
3038
|
+
async deletePassword(service, account) {
|
|
3039
|
+
try {
|
|
3040
|
+
const params = psParam("target", winTarget(service, account));
|
|
3041
|
+
await execFileAsync(
|
|
3042
|
+
"powershell.exe",
|
|
3043
|
+
["-NoProfile", "-NonInteractive", "-Command", params + WIN32_CRED_DELETE_SCRIPT],
|
|
3044
|
+
{ timeout: 15e3, windowsHide: true }
|
|
3045
|
+
);
|
|
3046
|
+
return true;
|
|
3047
|
+
} catch {
|
|
3048
|
+
return false;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
var macKeychain = {
|
|
3053
|
+
async getPassword(service, account) {
|
|
3054
|
+
try {
|
|
3055
|
+
const { stdout } = await execFileAsync(
|
|
3056
|
+
"security",
|
|
3057
|
+
["find-generic-password", "-s", service, "-a", account, "-w"],
|
|
3058
|
+
{ timeout: 15e3 }
|
|
3059
|
+
);
|
|
3060
|
+
return stdout.trimEnd();
|
|
3061
|
+
} catch {
|
|
3062
|
+
return null;
|
|
3063
|
+
}
|
|
3064
|
+
},
|
|
3065
|
+
async setPassword(service, account, password) {
|
|
3066
|
+
await execFileAsync(
|
|
3067
|
+
"security",
|
|
3068
|
+
["add-generic-password", "-U", "-s", service, "-a", account, "-w", password],
|
|
3069
|
+
{ timeout: 15e3 }
|
|
3070
|
+
);
|
|
3071
|
+
},
|
|
3072
|
+
async deletePassword(service, account) {
|
|
3073
|
+
try {
|
|
3074
|
+
await execFileAsync(
|
|
3075
|
+
"security",
|
|
3076
|
+
["delete-generic-password", "-s", service, "-a", account],
|
|
3077
|
+
{ timeout: 15e3 }
|
|
3078
|
+
);
|
|
3079
|
+
return true;
|
|
3080
|
+
} catch {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
var linuxKeychain = {
|
|
3086
|
+
async getPassword(service, account) {
|
|
3087
|
+
try {
|
|
3088
|
+
const { stdout } = await execFileAsync(
|
|
3089
|
+
"secret-tool",
|
|
3090
|
+
["lookup", "service", service, "account", account],
|
|
3091
|
+
{ timeout: 15e3 }
|
|
3092
|
+
);
|
|
3093
|
+
return stdout.trimEnd();
|
|
3094
|
+
} catch {
|
|
3095
|
+
return null;
|
|
3096
|
+
}
|
|
3097
|
+
},
|
|
3098
|
+
// secret-tool reads password from stdin (avoids exposing it in process args)
|
|
3099
|
+
async setPassword(service, account, password) {
|
|
3100
|
+
const child = spawn(
|
|
3101
|
+
"secret-tool",
|
|
3102
|
+
["store", "--label", `${service}/${account}`, "service", service, "account", account],
|
|
3103
|
+
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
3104
|
+
);
|
|
3105
|
+
child.stdin.write(password);
|
|
3106
|
+
child.stdin.end();
|
|
3107
|
+
await new Promise((resolve, reject) => {
|
|
3108
|
+
child.on(
|
|
3109
|
+
"close",
|
|
3110
|
+
(code) => code === 0 ? resolve() : reject(new Error("secret-tool store failed"))
|
|
3111
|
+
);
|
|
3112
|
+
child.on("error", reject);
|
|
3113
|
+
});
|
|
3114
|
+
},
|
|
3115
|
+
async deletePassword(service, account) {
|
|
3116
|
+
try {
|
|
3117
|
+
await execFileAsync(
|
|
3118
|
+
"secret-tool",
|
|
3119
|
+
["clear", "service", service, "account", account],
|
|
3120
|
+
{ timeout: 15e3 }
|
|
3121
|
+
);
|
|
3122
|
+
return true;
|
|
3123
|
+
} catch {
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
function getBackend() {
|
|
3129
|
+
switch (process.platform) {
|
|
3130
|
+
case "win32":
|
|
3131
|
+
return windowsKeychain;
|
|
3132
|
+
case "darwin":
|
|
3133
|
+
return macKeychain;
|
|
3134
|
+
default:
|
|
3135
|
+
return linuxKeychain;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
var backend = getBackend();
|
|
3139
|
+
async function getPassword(service, account) {
|
|
3140
|
+
return backend.getPassword(service, account);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// src/secrets/index.ts
|
|
2837
3144
|
var SecretsLoader = class {
|
|
2838
3145
|
// Same service name as CLI uses
|
|
2839
3146
|
SERVICE_NAME = "sinch-functions-cli";
|
|
@@ -2847,24 +3154,12 @@ var SecretsLoader = class {
|
|
|
2847
3154
|
return false;
|
|
2848
3155
|
}
|
|
2849
3156
|
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)) {
|
|
3157
|
+
const envPath = path5.join(process.cwd(), ".env");
|
|
3158
|
+
if (!fs4.existsSync(envPath)) {
|
|
2864
3159
|
console.debug("[Secrets] No .env file found, skipping keychain load");
|
|
2865
3160
|
return false;
|
|
2866
3161
|
}
|
|
2867
|
-
const envContent =
|
|
3162
|
+
const envContent = fs4.readFileSync(envPath, "utf8");
|
|
2868
3163
|
const envLines = envContent.replace(/\r\n/g, "\n").split("\n");
|
|
2869
3164
|
const secretsToLoad = [];
|
|
2870
3165
|
envLines.forEach((line) => {
|
|
@@ -2886,7 +3181,7 @@ var SecretsLoader = class {
|
|
|
2886
3181
|
}
|
|
2887
3182
|
let secretsLoaded = 0;
|
|
2888
3183
|
if (secretsToLoad.includes("PROJECT_ID_API_SECRET")) {
|
|
2889
|
-
const apiSecret = await
|
|
3184
|
+
const apiSecret = await getPassword(this.SERVICE_NAME, `${this.username}-keySecret`);
|
|
2890
3185
|
if (apiSecret) {
|
|
2891
3186
|
process.env.PROJECT_ID_API_SECRET = apiSecret;
|
|
2892
3187
|
console.log("\u2705 Loaded PROJECT_ID_API_SECRET from secure storage");
|
|
@@ -2896,7 +3191,7 @@ var SecretsLoader = class {
|
|
|
2896
3191
|
if (secretsToLoad.includes("VOICE_APPLICATION_SECRET")) {
|
|
2897
3192
|
const applicationKey = process.env.VOICE_APPLICATION_KEY || this.getApplicationKeyFromConfig();
|
|
2898
3193
|
if (applicationKey) {
|
|
2899
|
-
const appSecret = await
|
|
3194
|
+
const appSecret = await getPassword(this.SERVICE_NAME, applicationKey);
|
|
2900
3195
|
if (appSecret) {
|
|
2901
3196
|
process.env.VOICE_APPLICATION_SECRET = appSecret;
|
|
2902
3197
|
console.log("\u2705 Loaded VOICE_APPLICATION_SECRET from secure storage");
|
|
@@ -2910,7 +3205,7 @@ var SecretsLoader = class {
|
|
|
2910
3205
|
continue;
|
|
2911
3206
|
}
|
|
2912
3207
|
if (functionName) {
|
|
2913
|
-
const value = await
|
|
3208
|
+
const value = await getPassword(
|
|
2914
3209
|
this.SERVICE_NAME,
|
|
2915
3210
|
`${functionName}-${secretName}`
|
|
2916
3211
|
);
|
|
@@ -2938,9 +3233,9 @@ var SecretsLoader = class {
|
|
|
2938
3233
|
*/
|
|
2939
3234
|
getApplicationKeyFromConfig() {
|
|
2940
3235
|
try {
|
|
2941
|
-
const sinchJsonPath =
|
|
2942
|
-
if (
|
|
2943
|
-
const sinchConfig = JSON.parse(
|
|
3236
|
+
const sinchJsonPath = path5.join(process.cwd(), "sinch.json");
|
|
3237
|
+
if (fs4.existsSync(sinchJsonPath)) {
|
|
3238
|
+
const sinchConfig = JSON.parse(fs4.readFileSync(sinchJsonPath, "utf8"));
|
|
2944
3239
|
return sinchConfig.voiceAppId || sinchConfig.applicationKey || null;
|
|
2945
3240
|
}
|
|
2946
3241
|
} catch (error) {
|
|
@@ -2953,9 +3248,9 @@ var SecretsLoader = class {
|
|
|
2953
3248
|
*/
|
|
2954
3249
|
getFunctionNameFromConfig() {
|
|
2955
3250
|
try {
|
|
2956
|
-
const sinchJsonPath =
|
|
2957
|
-
if (
|
|
2958
|
-
const sinchConfig = JSON.parse(
|
|
3251
|
+
const sinchJsonPath = path5.join(process.cwd(), "sinch.json");
|
|
3252
|
+
if (fs4.existsSync(sinchJsonPath)) {
|
|
3253
|
+
const sinchConfig = JSON.parse(fs4.readFileSync(sinchJsonPath, "utf8"));
|
|
2959
3254
|
return sinchConfig.name || null;
|
|
2960
3255
|
}
|
|
2961
3256
|
} catch (error) {
|
|
@@ -2969,14 +3264,13 @@ var SecretsLoader = class {
|
|
|
2969
3264
|
async loadCustomSecrets(secretNames = []) {
|
|
2970
3265
|
const secrets = {};
|
|
2971
3266
|
try {
|
|
2972
|
-
const keytar = await import("keytar");
|
|
2973
3267
|
const functionName = this.getFunctionNameFromConfig();
|
|
2974
3268
|
if (!functionName) {
|
|
2975
3269
|
console.debug("[Secrets] Could not determine function name for custom secrets");
|
|
2976
3270
|
return secrets;
|
|
2977
3271
|
}
|
|
2978
3272
|
for (const secretName of secretNames) {
|
|
2979
|
-
const value = await
|
|
3273
|
+
const value = await getPassword(this.SERVICE_NAME, `${functionName}-${secretName}`);
|
|
2980
3274
|
if (value) {
|
|
2981
3275
|
secrets[secretName] = value;
|
|
2982
3276
|
process.env[secretName] = value;
|
|
@@ -2988,15 +3282,13 @@ var SecretsLoader = class {
|
|
|
2988
3282
|
return secrets;
|
|
2989
3283
|
}
|
|
2990
3284
|
/**
|
|
2991
|
-
* Check if
|
|
3285
|
+
* Check if the native keychain is available.
|
|
3286
|
+
* Always true — no native npm deps required. The underlying OS tool
|
|
3287
|
+
* (secret-tool, security, CredManager) may still be absent, in which
|
|
3288
|
+
* case getPassword() silently returns null per credential.
|
|
2992
3289
|
*/
|
|
2993
3290
|
async isAvailable() {
|
|
2994
|
-
|
|
2995
|
-
await import("keytar");
|
|
2996
|
-
return true;
|
|
2997
|
-
} catch {
|
|
2998
|
-
return false;
|
|
2999
|
-
}
|
|
3291
|
+
return true;
|
|
3000
3292
|
}
|
|
3001
3293
|
};
|
|
3002
3294
|
var secretsLoader = new SecretsLoader();
|
|
@@ -3012,6 +3304,7 @@ export {
|
|
|
3012
3304
|
ElevenLabsState,
|
|
3013
3305
|
IceSvamlBuilder,
|
|
3014
3306
|
LocalCache,
|
|
3307
|
+
LocalStorage,
|
|
3015
3308
|
MenuBuilder,
|
|
3016
3309
|
MenuTemplates,
|
|
3017
3310
|
NOTIFICATION_EVENTS,
|
|
@@ -3042,6 +3335,7 @@ export {
|
|
|
3042
3335
|
createResponse,
|
|
3043
3336
|
createSimpleMenu,
|
|
3044
3337
|
createSms,
|
|
3338
|
+
createStorageClient,
|
|
3045
3339
|
createUniversalConfig,
|
|
3046
3340
|
createWhatsApp,
|
|
3047
3341
|
extractCallerNumber,
|