@sinch/functions-runtime 0.4.0 → 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 +104 -29
- package/dist/bin/sinch-runtime.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +81 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -160,6 +160,31 @@ function setupJsonParsing(app, options = {}) {
|
|
|
160
160
|
return app;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// ../runtime-shared/dist/auth/basic-auth.js
|
|
164
|
+
import { timingSafeEqual } from "crypto";
|
|
165
|
+
function validateBasicAuth(authHeader, expectedKey, expectedSecret) {
|
|
166
|
+
if (!authHeader) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (!authHeader.toLowerCase().startsWith("basic ")) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
const decoded = Buffer.from(authHeader.slice(6), "base64").toString("utf-8");
|
|
173
|
+
const colonIndex = decoded.indexOf(":");
|
|
174
|
+
if (colonIndex === -1) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const providedKey = decoded.slice(0, colonIndex);
|
|
178
|
+
const providedSecret = decoded.slice(colonIndex + 1);
|
|
179
|
+
const expectedKeyBuf = Buffer.from(expectedKey);
|
|
180
|
+
const providedKeyBuf = Buffer.from(providedKey);
|
|
181
|
+
const expectedSecretBuf = Buffer.from(expectedSecret);
|
|
182
|
+
const providedSecretBuf = Buffer.from(providedSecret);
|
|
183
|
+
const keyMatch = expectedKeyBuf.length === providedKeyBuf.length && timingSafeEqual(expectedKeyBuf, providedKeyBuf);
|
|
184
|
+
const secretMatch = expectedSecretBuf.length === providedSecretBuf.length && timingSafeEqual(expectedSecretBuf, providedSecretBuf);
|
|
185
|
+
return keyMatch && secretMatch;
|
|
186
|
+
}
|
|
187
|
+
|
|
163
188
|
// ../runtime-shared/dist/host/app.js
|
|
164
189
|
import { createRequire as createRequire2 } from "module";
|
|
165
190
|
import { pathToFileURL } from "url";
|
|
@@ -462,7 +487,7 @@ function setupRequestHandler(app, options = {}) {
|
|
|
462
487
|
const functionUrl = pathToFileURL(functionPath).href;
|
|
463
488
|
const module = await import(functionUrl);
|
|
464
489
|
return module.default || module;
|
|
465
|
-
}, buildContext = buildBaseContext, logger = console.log, landingPageEnabled = true, onRequestStart = () => {
|
|
490
|
+
}, buildContext = buildBaseContext, logger = console.log, landingPageEnabled = true, authConfig, authKey, authSecret, onRequestStart = () => {
|
|
466
491
|
}, onRequestEnd = () => {
|
|
467
492
|
} } = options;
|
|
468
493
|
app.use("/{*splat}", async (req, res) => {
|
|
@@ -490,6 +515,17 @@ function setupRequestHandler(app, options = {}) {
|
|
|
490
515
|
try {
|
|
491
516
|
const functionName = extractFunctionName(req.originalUrl, req.body);
|
|
492
517
|
logger(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${req.method} ${req.path} -> ${functionName}`);
|
|
518
|
+
if (authConfig && authKey && authSecret) {
|
|
519
|
+
const needsAuth = authConfig === "*" || Array.isArray(authConfig) && authConfig.includes(functionName);
|
|
520
|
+
if (needsAuth) {
|
|
521
|
+
const isValid = validateBasicAuth(req.headers.authorization, authKey, authSecret);
|
|
522
|
+
if (!isValid) {
|
|
523
|
+
logger(`[AUTH] Rejected unauthorized request to ${functionName}`);
|
|
524
|
+
res.status(401).set("WWW-Authenticate", 'Basic realm="sinch-function"').json({ error: "Unauthorized" });
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
493
529
|
onRequestStart({ functionName, req });
|
|
494
530
|
const context = buildContext(req);
|
|
495
531
|
const userFunction = await Promise.resolve(loadUserFunction());
|
|
@@ -780,7 +816,7 @@ import * as path4 from "path";
|
|
|
780
816
|
var LocalStorage = class {
|
|
781
817
|
baseDir;
|
|
782
818
|
constructor(baseDir) {
|
|
783
|
-
this.baseDir = baseDir ?? path4.join(process.cwd(), "storage");
|
|
819
|
+
this.baseDir = baseDir ?? path4.join(process.cwd(), ".sinch", "storage");
|
|
784
820
|
}
|
|
785
821
|
resolvePath(key) {
|
|
786
822
|
const sanitized = key.replace(/^\/+/, "").replace(/\.\./g, "_");
|
|
@@ -1232,6 +1268,13 @@ import axios from "axios";
|
|
|
1232
1268
|
|
|
1233
1269
|
// src/tunnel/webhook-config.ts
|
|
1234
1270
|
import { SinchClient as SinchClient2 } from "@sinch/sdk-core";
|
|
1271
|
+
var SINCH_FN_URL_PATTERN = /\.fn(-\w+)?\.sinch\.com/;
|
|
1272
|
+
function isOurWebhook(target) {
|
|
1273
|
+
return !!target && SINCH_FN_URL_PATTERN.test(target);
|
|
1274
|
+
}
|
|
1275
|
+
function isTunnelUrl(target) {
|
|
1276
|
+
return !!target && target.includes("tunnel.fn");
|
|
1277
|
+
}
|
|
1235
1278
|
async function configureConversationWebhooks(tunnelUrl, config) {
|
|
1236
1279
|
try {
|
|
1237
1280
|
const conversationAppId = process.env.CONVERSATION_APP_ID;
|
|
@@ -1253,24 +1296,23 @@ async function configureConversationWebhooks(tunnelUrl, config) {
|
|
|
1253
1296
|
app_id: conversationAppId
|
|
1254
1297
|
});
|
|
1255
1298
|
const existingWebhooks = webhooksResult.webhooks || [];
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1299
|
+
const deployedWebhook = existingWebhooks.find(
|
|
1300
|
+
(w) => isOurWebhook(w.target)
|
|
1301
|
+
);
|
|
1302
|
+
if (!deployedWebhook || !deployedWebhook.id) {
|
|
1303
|
+
console.log("\u26A0\uFE0F No deployed webhook found \u2014 deploy first");
|
|
1304
|
+
return;
|
|
1263
1305
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1306
|
+
config.conversationWebhookId = deployedWebhook.id;
|
|
1307
|
+
config.originalTarget = deployedWebhook.target;
|
|
1308
|
+
await sinchClient.conversation.webhooks.update({
|
|
1309
|
+
webhook_id: deployedWebhook.id,
|
|
1310
|
+
webhookUpdateRequestBody: {
|
|
1311
|
+
target: webhookUrl
|
|
1312
|
+
},
|
|
1313
|
+
update_mask: ["target"]
|
|
1271
1314
|
});
|
|
1272
|
-
|
|
1273
|
-
console.log(`\u2705 Created Conversation webhook: ${webhookUrl}`);
|
|
1315
|
+
console.log(`\u2705 Updated Conversation webhook to tunnel: ${webhookUrl}`);
|
|
1274
1316
|
console.log("\u{1F4AC} Send a message to your Conversation app to test!");
|
|
1275
1317
|
} catch (error) {
|
|
1276
1318
|
console.log("\u26A0\uFE0F Could not configure Conversation webhooks:", error.message);
|
|
@@ -1289,10 +1331,29 @@ async function cleanupConversationWebhook(config) {
|
|
|
1289
1331
|
keyId,
|
|
1290
1332
|
keySecret
|
|
1291
1333
|
});
|
|
1292
|
-
|
|
1293
|
-
|
|
1334
|
+
let restoreTarget = config.originalTarget;
|
|
1335
|
+
if (!restoreTarget || isTunnelUrl(restoreTarget)) {
|
|
1336
|
+
const functionName = process.env.FUNCTION_NAME || process.env.FUNCTION_ID;
|
|
1337
|
+
if (functionName) {
|
|
1338
|
+
restoreTarget = `https://${functionName}.fn-dev.sinch.com/webhook/conversation`;
|
|
1339
|
+
console.log(`\u{1F527} Derived restore target from env: ${restoreTarget}`);
|
|
1340
|
+
} else {
|
|
1341
|
+
console.log("\u26A0\uFE0F Cannot restore webhook \u2014 no FUNCTION_NAME or FUNCTION_ID available");
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
await sinchClient.conversation.webhooks.update({
|
|
1346
|
+
webhook_id: config.conversationWebhookId,
|
|
1347
|
+
webhookUpdateRequestBody: {
|
|
1348
|
+
target: restoreTarget
|
|
1349
|
+
},
|
|
1350
|
+
update_mask: ["target"]
|
|
1351
|
+
});
|
|
1352
|
+
console.log(`\u{1F504} Restored webhook target to: ${restoreTarget}`);
|
|
1294
1353
|
config.conversationWebhookId = void 0;
|
|
1354
|
+
config.originalTarget = void 0;
|
|
1295
1355
|
} catch (error) {
|
|
1356
|
+
console.log("\u26A0\uFE0F Could not restore Conversation webhook:", error.message);
|
|
1296
1357
|
}
|
|
1297
1358
|
}
|
|
1298
1359
|
async function configureElevenLabs() {
|
|
@@ -1627,7 +1688,7 @@ function loadRuntimeConfig() {
|
|
|
1627
1688
|
};
|
|
1628
1689
|
}
|
|
1629
1690
|
var storage = createStorageClient();
|
|
1630
|
-
var databasePath = path6.join(process.cwd(), "data", "app.db");
|
|
1691
|
+
var databasePath = path6.join(process.cwd(), ".sinch", "data", "app.db");
|
|
1631
1692
|
function buildLocalContext(req, runtimeConfig) {
|
|
1632
1693
|
const baseContext = buildBaseContext(req);
|
|
1633
1694
|
const cache = createCacheClient();
|
|
@@ -1755,20 +1816,34 @@ async function main() {
|
|
|
1755
1816
|
} catch {
|
|
1756
1817
|
}
|
|
1757
1818
|
await secretsLoader.loadFromKeychain();
|
|
1758
|
-
fs5.mkdirSync(path6.join(process.cwd(), "storage"), { recursive: true });
|
|
1759
|
-
fs5.mkdirSync(path6.join(process.cwd(), "data"), { recursive: true });
|
|
1819
|
+
fs5.mkdirSync(path6.join(process.cwd(), ".sinch", "storage"), { recursive: true });
|
|
1820
|
+
fs5.mkdirSync(path6.join(process.cwd(), ".sinch", "data"), { recursive: true });
|
|
1760
1821
|
const config = loadRuntimeConfig();
|
|
1761
1822
|
const staticDir = process.env.STATIC_DIR;
|
|
1762
1823
|
const landingPageEnabled = process.env.LANDING_PAGE_ENABLED !== "false";
|
|
1763
1824
|
const app = createApp({ staticDir, landingPageEnabled });
|
|
1825
|
+
const authKey = process.env.PROJECT_ID_API_KEY;
|
|
1826
|
+
const authSecret = process.env.PROJECT_ID_API_SECRET;
|
|
1827
|
+
let userAuthConfig;
|
|
1828
|
+
const loadUserFunction = async () => {
|
|
1829
|
+
const functionPath = findFunctionPath3();
|
|
1830
|
+
const functionUrl = pathToFileURL2(functionPath).href;
|
|
1831
|
+
const module = await import(functionUrl);
|
|
1832
|
+
if (userAuthConfig === void 0) {
|
|
1833
|
+
userAuthConfig = module.auth || module.default?.auth;
|
|
1834
|
+
if (userAuthConfig && verbose) {
|
|
1835
|
+
console.log(`[AUTH] Auth config loaded: ${JSON.stringify(userAuthConfig)}`);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return module.default || module;
|
|
1839
|
+
};
|
|
1840
|
+
await loadUserFunction();
|
|
1764
1841
|
setupRequestHandler(app, {
|
|
1765
1842
|
landingPageEnabled,
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
return module.default || module;
|
|
1771
|
-
},
|
|
1843
|
+
authConfig: userAuthConfig,
|
|
1844
|
+
authKey,
|
|
1845
|
+
authSecret,
|
|
1846
|
+
loadUserFunction,
|
|
1772
1847
|
buildContext: (req) => buildLocalContext(req, config),
|
|
1773
1848
|
logger: console.log,
|
|
1774
1849
|
onRequestStart: ({ req }) => {
|