skedyul 1.2.21 → 1.2.24
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/cli/index.js +464 -631
- package/dist/config/types/env.d.ts +8 -2
- package/dist/config/types/form.d.ts +10 -6
- package/dist/config/types/index.d.ts +0 -1
- package/dist/config/types/model.d.ts +1 -2
- package/dist/config/types/page.d.ts +2 -2
- package/dist/config/types/webhook.d.ts +2 -1
- package/dist/dedicated/server.js +464 -679
- package/dist/esm/index.mjs +464 -631
- package/dist/index.d.ts +1 -2
- package/dist/index.js +464 -631
- package/dist/server/dedicated.d.ts +1 -1
- package/dist/server/handlers/index.d.ts +12 -0
- package/dist/server/handlers/install-handler.d.ts +9 -0
- package/dist/server/handlers/oauth-callback-handler.d.ts +9 -0
- package/dist/server/handlers/provision-handler.d.ts +9 -0
- package/dist/server/handlers/types.d.ts +101 -0
- package/dist/server/handlers/uninstall-handler.d.ts +9 -0
- package/dist/server/handlers/webhook-handler.d.ts +28 -0
- package/dist/server/serverless.d.ts +1 -1
- package/dist/server/tool-handler.d.ts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +464 -679
- package/dist/serverless/server.mjs +464 -657
- package/dist/types/handlers.d.ts +6 -24
- package/dist/types/index.d.ts +3 -3
- package/dist/types/server.d.ts +1 -1
- package/dist/types/shared.d.ts +5 -0
- package/dist/types/tool.d.ts +5 -7
- package/dist/types/webhook.d.ts +14 -6
- package/package.json +1 -1
- package/dist/config/types/compute.d.ts +0 -9
package/dist/esm/index.mjs
CHANGED
|
@@ -2078,8 +2078,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
2078
2078
|
};
|
|
2079
2079
|
}
|
|
2080
2080
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
2081
|
-
return async function callTool(
|
|
2082
|
-
const toolName = String(
|
|
2081
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
2082
|
+
const toolName = String(toolNameInput);
|
|
2083
2083
|
const tool = registry[toolName];
|
|
2084
2084
|
if (!tool) {
|
|
2085
2085
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -2088,7 +2088,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
2088
2088
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
2089
2089
|
}
|
|
2090
2090
|
const fn = tool.handler;
|
|
2091
|
-
const args =
|
|
2091
|
+
const args = toolArgsInput ?? {};
|
|
2092
2092
|
const estimateMode = args.estimate === true;
|
|
2093
2093
|
if (!estimateMode) {
|
|
2094
2094
|
state.incrementRequestCount();
|
|
@@ -2343,51 +2343,6 @@ async function handleCoreMethod(method, params) {
|
|
|
2343
2343
|
};
|
|
2344
2344
|
}
|
|
2345
2345
|
|
|
2346
|
-
// src/server/handler-helpers.ts
|
|
2347
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
2348
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2349
|
-
return null;
|
|
2350
|
-
}
|
|
2351
|
-
const envelope = parsedBody;
|
|
2352
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2353
|
-
return null;
|
|
2354
|
-
}
|
|
2355
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2356
|
-
return null;
|
|
2357
|
-
}
|
|
2358
|
-
return {
|
|
2359
|
-
env: envelope.env,
|
|
2360
|
-
request: envelope.request,
|
|
2361
|
-
context: envelope.context
|
|
2362
|
-
};
|
|
2363
|
-
}
|
|
2364
|
-
function buildRequestFromRaw(raw) {
|
|
2365
|
-
let parsedBody = raw.body;
|
|
2366
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
2367
|
-
if (contentType.includes("application/json")) {
|
|
2368
|
-
try {
|
|
2369
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2370
|
-
} catch {
|
|
2371
|
-
parsedBody = raw.body;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return {
|
|
2375
|
-
method: raw.method,
|
|
2376
|
-
url: raw.url,
|
|
2377
|
-
path: raw.path,
|
|
2378
|
-
headers: raw.headers,
|
|
2379
|
-
query: raw.query,
|
|
2380
|
-
body: parsedBody,
|
|
2381
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2382
|
-
};
|
|
2383
|
-
}
|
|
2384
|
-
function buildRequestScopedConfig(env) {
|
|
2385
|
-
return {
|
|
2386
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2387
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2388
|
-
};
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
2346
|
// src/server/startup-logger.ts
|
|
2392
2347
|
function padEnd(str, length) {
|
|
2393
2348
|
if (str.length >= length) {
|
|
@@ -2485,6 +2440,405 @@ function isProvisionConfig(value) {
|
|
|
2485
2440
|
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
2486
2441
|
}
|
|
2487
2442
|
|
|
2443
|
+
// src/server/handlers/install-handler.ts
|
|
2444
|
+
async function handleInstall(body, hooks) {
|
|
2445
|
+
if (!hooks?.install) {
|
|
2446
|
+
return {
|
|
2447
|
+
status: 404,
|
|
2448
|
+
body: { error: "Install handler not configured" }
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
2452
|
+
return {
|
|
2453
|
+
status: 400,
|
|
2454
|
+
body: {
|
|
2455
|
+
error: {
|
|
2456
|
+
code: -32602,
|
|
2457
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
const installContext = {
|
|
2463
|
+
env: body.env ?? {},
|
|
2464
|
+
workplace: body.context.workplace,
|
|
2465
|
+
appInstallationId: body.context.appInstallationId,
|
|
2466
|
+
app: body.context.app,
|
|
2467
|
+
invocation: body.invocation,
|
|
2468
|
+
log: createContextLogger()
|
|
2469
|
+
};
|
|
2470
|
+
const requestConfig = {
|
|
2471
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2472
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2473
|
+
};
|
|
2474
|
+
try {
|
|
2475
|
+
const installHook = hooks.install;
|
|
2476
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2477
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2478
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2479
|
+
return await installHandler(installContext);
|
|
2480
|
+
});
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
status: 200,
|
|
2484
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
2485
|
+
};
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
if (err instanceof InstallError) {
|
|
2488
|
+
return {
|
|
2489
|
+
status: 400,
|
|
2490
|
+
body: {
|
|
2491
|
+
error: {
|
|
2492
|
+
code: err.code,
|
|
2493
|
+
message: err.message,
|
|
2494
|
+
field: err.field
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
return {
|
|
2500
|
+
status: 500,
|
|
2501
|
+
body: {
|
|
2502
|
+
error: {
|
|
2503
|
+
code: -32603,
|
|
2504
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/server/handlers/uninstall-handler.ts
|
|
2512
|
+
async function handleUninstall(body, hooks) {
|
|
2513
|
+
if (!hooks?.uninstall) {
|
|
2514
|
+
return {
|
|
2515
|
+
status: 404,
|
|
2516
|
+
body: { error: "Uninstall handler not configured" }
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
2520
|
+
return {
|
|
2521
|
+
status: 400,
|
|
2522
|
+
body: {
|
|
2523
|
+
error: {
|
|
2524
|
+
code: -32602,
|
|
2525
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
const uninstallContext = {
|
|
2531
|
+
env: body.env ?? {},
|
|
2532
|
+
workplace: body.context.workplace,
|
|
2533
|
+
appInstallationId: body.context.appInstallationId,
|
|
2534
|
+
app: body.context.app,
|
|
2535
|
+
invocation: body.invocation,
|
|
2536
|
+
log: createContextLogger()
|
|
2537
|
+
};
|
|
2538
|
+
const requestConfig = {
|
|
2539
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2540
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2541
|
+
};
|
|
2542
|
+
try {
|
|
2543
|
+
const uninstallHook = hooks.uninstall;
|
|
2544
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2545
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2546
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2547
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
2548
|
+
});
|
|
2549
|
+
});
|
|
2550
|
+
return {
|
|
2551
|
+
status: 200,
|
|
2552
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
2553
|
+
};
|
|
2554
|
+
} catch (err) {
|
|
2555
|
+
return {
|
|
2556
|
+
status: 500,
|
|
2557
|
+
body: {
|
|
2558
|
+
error: {
|
|
2559
|
+
code: -32603,
|
|
2560
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// src/server/handlers/provision-handler.ts
|
|
2568
|
+
async function handleProvision(body, hooks) {
|
|
2569
|
+
if (!hooks?.provision) {
|
|
2570
|
+
return {
|
|
2571
|
+
status: 404,
|
|
2572
|
+
body: { error: "Provision handler not configured" }
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
if (!body.context?.app) {
|
|
2576
|
+
return {
|
|
2577
|
+
status: 400,
|
|
2578
|
+
body: {
|
|
2579
|
+
error: {
|
|
2580
|
+
code: -32602,
|
|
2581
|
+
message: "Missing context (app required)"
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
const mergedEnv = {};
|
|
2587
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2588
|
+
if (value !== void 0) {
|
|
2589
|
+
mergedEnv[key] = value;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
2593
|
+
const provisionContext = {
|
|
2594
|
+
env: mergedEnv,
|
|
2595
|
+
app: body.context.app,
|
|
2596
|
+
invocation: body.invocation,
|
|
2597
|
+
log: createContextLogger()
|
|
2598
|
+
};
|
|
2599
|
+
const requestConfig = {
|
|
2600
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
2601
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
2602
|
+
};
|
|
2603
|
+
try {
|
|
2604
|
+
const provisionHook = hooks.provision;
|
|
2605
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
2606
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2607
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2608
|
+
return await provisionHandler(provisionContext);
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
2611
|
+
return {
|
|
2612
|
+
status: 200,
|
|
2613
|
+
body: result
|
|
2614
|
+
};
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
return {
|
|
2617
|
+
status: 500,
|
|
2618
|
+
body: {
|
|
2619
|
+
error: {
|
|
2620
|
+
code: -32603,
|
|
2621
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/server/handler-helpers.ts
|
|
2629
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
2630
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
const envelope = parsedBody;
|
|
2634
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2635
|
+
return null;
|
|
2636
|
+
}
|
|
2637
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
return {
|
|
2641
|
+
env: envelope.env,
|
|
2642
|
+
request: envelope.request,
|
|
2643
|
+
context: envelope.context
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
function buildRequestFromRaw(raw) {
|
|
2647
|
+
let parsedBody = raw.body;
|
|
2648
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
2649
|
+
if (contentType.includes("application/json")) {
|
|
2650
|
+
try {
|
|
2651
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2652
|
+
} catch {
|
|
2653
|
+
parsedBody = raw.body;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
return {
|
|
2657
|
+
method: raw.method,
|
|
2658
|
+
url: raw.url,
|
|
2659
|
+
path: raw.path,
|
|
2660
|
+
headers: raw.headers,
|
|
2661
|
+
query: raw.query,
|
|
2662
|
+
body: parsedBody,
|
|
2663
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
function buildRequestScopedConfig(env) {
|
|
2667
|
+
return {
|
|
2668
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2669
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
2674
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
2675
|
+
if (!hooks?.oauth_callback) {
|
|
2676
|
+
return {
|
|
2677
|
+
status: 404,
|
|
2678
|
+
body: { error: "OAuth callback handler not configured" }
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
2682
|
+
if (!envelope) {
|
|
2683
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2684
|
+
return {
|
|
2685
|
+
status: 400,
|
|
2686
|
+
body: {
|
|
2687
|
+
error: {
|
|
2688
|
+
code: -32602,
|
|
2689
|
+
message: "Missing envelope format: expected { env, request }"
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
const invocation = parsedBody.invocation;
|
|
2695
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2696
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
2697
|
+
const oauthCallbackContext = {
|
|
2698
|
+
request: oauthRequest,
|
|
2699
|
+
invocation,
|
|
2700
|
+
log: createContextLogger()
|
|
2701
|
+
};
|
|
2702
|
+
try {
|
|
2703
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
2704
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2705
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
2706
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2707
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2708
|
+
});
|
|
2709
|
+
});
|
|
2710
|
+
return {
|
|
2711
|
+
status: 200,
|
|
2712
|
+
body: {
|
|
2713
|
+
appInstallationId: result.appInstallationId,
|
|
2714
|
+
env: result.env ?? {}
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
} catch (err) {
|
|
2718
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2719
|
+
return {
|
|
2720
|
+
status: 500,
|
|
2721
|
+
body: {
|
|
2722
|
+
error: {
|
|
2723
|
+
code: -32603,
|
|
2724
|
+
message: errorMessage
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// src/server/handlers/webhook-handler.ts
|
|
2732
|
+
function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
2733
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
2734
|
+
if (isEnvelope) {
|
|
2735
|
+
const envelope = parsedBody;
|
|
2736
|
+
const requestEnv = envelope.env ?? {};
|
|
2737
|
+
const invocation = envelope.invocation;
|
|
2738
|
+
let originalParsedBody = envelope.request.body;
|
|
2739
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
2740
|
+
if (originalContentType.includes("application/json")) {
|
|
2741
|
+
try {
|
|
2742
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
2743
|
+
} catch {
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
const webhookRequest2 = {
|
|
2747
|
+
method: envelope.request.method,
|
|
2748
|
+
url: envelope.request.url,
|
|
2749
|
+
path: envelope.request.path,
|
|
2750
|
+
headers: envelope.request.headers,
|
|
2751
|
+
query: envelope.request.query,
|
|
2752
|
+
body: originalParsedBody,
|
|
2753
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
2754
|
+
};
|
|
2755
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
2756
|
+
const app = envelope.context.app;
|
|
2757
|
+
let webhookContext2;
|
|
2758
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
2759
|
+
webhookContext2 = {
|
|
2760
|
+
env: envVars,
|
|
2761
|
+
app,
|
|
2762
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
2763
|
+
workplace: envelope.context.workplace,
|
|
2764
|
+
registration: envelope.context.registration ?? {},
|
|
2765
|
+
invocation,
|
|
2766
|
+
log: createContextLogger()
|
|
2767
|
+
};
|
|
2768
|
+
} else {
|
|
2769
|
+
webhookContext2 = {
|
|
2770
|
+
env: envVars,
|
|
2771
|
+
app,
|
|
2772
|
+
invocation,
|
|
2773
|
+
log: createContextLogger()
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
2777
|
+
}
|
|
2778
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
2779
|
+
return {
|
|
2780
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
const webhookRequest = {
|
|
2784
|
+
method,
|
|
2785
|
+
url,
|
|
2786
|
+
path: path2,
|
|
2787
|
+
headers,
|
|
2788
|
+
query,
|
|
2789
|
+
body: parsedBody,
|
|
2790
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2791
|
+
};
|
|
2792
|
+
const webhookContext = {
|
|
2793
|
+
env: process.env,
|
|
2794
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
2795
|
+
log: createContextLogger()
|
|
2796
|
+
};
|
|
2797
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
2798
|
+
}
|
|
2799
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
2800
|
+
const webhookDef = webhookRegistry[handle];
|
|
2801
|
+
if (!webhookDef) {
|
|
2802
|
+
return {
|
|
2803
|
+
status: 404,
|
|
2804
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
const originalEnv = { ...process.env };
|
|
2808
|
+
Object.assign(process.env, data.requestEnv);
|
|
2809
|
+
const requestConfig = {
|
|
2810
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2811
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2812
|
+
};
|
|
2813
|
+
let webhookResponse;
|
|
2814
|
+
try {
|
|
2815
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
2816
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2817
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
2818
|
+
});
|
|
2819
|
+
});
|
|
2820
|
+
} catch (err) {
|
|
2821
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2822
|
+
return {
|
|
2823
|
+
status: 500,
|
|
2824
|
+
body: { error: "Webhook handler error" }
|
|
2825
|
+
};
|
|
2826
|
+
} finally {
|
|
2827
|
+
process.env = originalEnv;
|
|
2828
|
+
}
|
|
2829
|
+
return {
|
|
2830
|
+
status: webhookResponse.status ?? 200,
|
|
2831
|
+
body: webhookResponse.body,
|
|
2832
|
+
headers: webhookResponse.headers
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
2836
|
+
const webhookDef = webhookRegistry[handle];
|
|
2837
|
+
if (!webhookDef) return false;
|
|
2838
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
2839
|
+
return allowedMethods.includes(method);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2488
2842
|
// src/server/dedicated.ts
|
|
2489
2843
|
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
2490
2844
|
const port = getListeningPort(config);
|
|
@@ -2511,13 +2865,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2511
2865
|
}
|
|
2512
2866
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
2513
2867
|
const handle = pathname.slice("/webhooks/".length);
|
|
2514
|
-
|
|
2515
|
-
if (!webhookDef) {
|
|
2868
|
+
if (!webhookRegistry[handle]) {
|
|
2516
2869
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
2517
2870
|
return;
|
|
2518
2871
|
}
|
|
2519
|
-
|
|
2520
|
-
if (!allowedMethods.includes(req.method)) {
|
|
2872
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
2521
2873
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
2522
2874
|
return;
|
|
2523
2875
|
}
|
|
@@ -2539,87 +2891,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2539
2891
|
} else {
|
|
2540
2892
|
parsedBody = rawBody;
|
|
2541
2893
|
}
|
|
2542
|
-
const
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
webhookContext = {
|
|
2556
|
-
env: envVars,
|
|
2557
|
-
app,
|
|
2558
|
-
appInstallationId: context.appInstallationId,
|
|
2559
|
-
workplace: context.workplace,
|
|
2560
|
-
registration: context.registration ?? {},
|
|
2561
|
-
invocation,
|
|
2562
|
-
log: createContextLogger()
|
|
2563
|
-
};
|
|
2564
|
-
} else {
|
|
2565
|
-
webhookContext = {
|
|
2566
|
-
env: envVars,
|
|
2567
|
-
app,
|
|
2568
|
-
invocation,
|
|
2569
|
-
log: createContextLogger()
|
|
2570
|
-
};
|
|
2571
|
-
}
|
|
2572
|
-
} else {
|
|
2573
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
2574
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
2575
|
-
if (!appId || !appVersionId) {
|
|
2576
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
2577
|
-
}
|
|
2578
|
-
webhookRequest = {
|
|
2579
|
-
method: req.method ?? "POST",
|
|
2580
|
-
url: url.toString(),
|
|
2581
|
-
path: pathname,
|
|
2582
|
-
headers: req.headers,
|
|
2583
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
2584
|
-
body: parsedBody,
|
|
2585
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2586
|
-
};
|
|
2587
|
-
webhookContext = {
|
|
2588
|
-
env: process.env,
|
|
2589
|
-
app: { id: appId, versionId: appVersionId },
|
|
2590
|
-
log: createContextLogger()
|
|
2591
|
-
};
|
|
2592
|
-
}
|
|
2593
|
-
const originalEnv = { ...process.env };
|
|
2594
|
-
Object.assign(process.env, requestEnv);
|
|
2595
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
2596
|
-
let webhookResponse;
|
|
2597
|
-
try {
|
|
2598
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
2599
|
-
return await runWithConfig(requestConfig, async () => {
|
|
2600
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
2601
|
-
});
|
|
2602
|
-
});
|
|
2603
|
-
} catch (err) {
|
|
2604
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2605
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
2894
|
+
const parseResult = parseWebhookRequest(
|
|
2895
|
+
parsedBody,
|
|
2896
|
+
req.method ?? "POST",
|
|
2897
|
+
url.toString(),
|
|
2898
|
+
pathname,
|
|
2899
|
+
req.headers,
|
|
2900
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
2901
|
+
rawBody,
|
|
2902
|
+
req.headers["x-skedyul-app-id"],
|
|
2903
|
+
req.headers["x-skedyul-app-version-id"]
|
|
2904
|
+
);
|
|
2905
|
+
if ("error" in parseResult) {
|
|
2906
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
2606
2907
|
return;
|
|
2607
|
-
} finally {
|
|
2608
|
-
process.env = originalEnv;
|
|
2609
2908
|
}
|
|
2610
|
-
const
|
|
2909
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
2611
2910
|
const responseHeaders = {
|
|
2612
|
-
...
|
|
2911
|
+
...result.headers
|
|
2613
2912
|
};
|
|
2614
2913
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
2615
2914
|
responseHeaders["Content-Type"] = "application/json";
|
|
2616
2915
|
}
|
|
2617
|
-
res.writeHead(status, responseHeaders);
|
|
2618
|
-
if (
|
|
2619
|
-
if (typeof
|
|
2620
|
-
res.end(
|
|
2916
|
+
res.writeHead(result.status, responseHeaders);
|
|
2917
|
+
if (result.body !== void 0) {
|
|
2918
|
+
if (typeof result.body === "string") {
|
|
2919
|
+
res.end(result.body);
|
|
2621
2920
|
} else {
|
|
2622
|
-
res.end(JSON.stringify(
|
|
2921
|
+
res.end(JSON.stringify(result.body));
|
|
2623
2922
|
}
|
|
2624
2923
|
} else {
|
|
2625
2924
|
res.end();
|
|
@@ -2658,10 +2957,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2658
2957
|
return;
|
|
2659
2958
|
}
|
|
2660
2959
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
2661
|
-
if (!config.hooks?.oauth_callback) {
|
|
2662
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
2663
|
-
return;
|
|
2664
|
-
}
|
|
2665
2960
|
let parsedBody;
|
|
2666
2961
|
try {
|
|
2667
2962
|
parsedBody = await parseJSONBody(req);
|
|
@@ -2672,50 +2967,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2672
2967
|
});
|
|
2673
2968
|
return;
|
|
2674
2969
|
}
|
|
2675
|
-
const
|
|
2676
|
-
|
|
2677
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2678
|
-
sendJSON(res, 400, {
|
|
2679
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
2680
|
-
});
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
const invocation = parsedBody.invocation;
|
|
2684
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2685
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
2686
|
-
const oauthCallbackContext = {
|
|
2687
|
-
request: oauthRequest,
|
|
2688
|
-
invocation,
|
|
2689
|
-
log: createContextLogger()
|
|
2690
|
-
};
|
|
2691
|
-
try {
|
|
2692
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
2693
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2694
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
2695
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
2696
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2697
|
-
});
|
|
2698
|
-
});
|
|
2699
|
-
sendJSON(res, 200, {
|
|
2700
|
-
appInstallationId: result.appInstallationId,
|
|
2701
|
-
env: result.env ?? {}
|
|
2702
|
-
});
|
|
2703
|
-
} catch (err) {
|
|
2704
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2705
|
-
sendJSON(res, 500, {
|
|
2706
|
-
error: {
|
|
2707
|
-
code: -32603,
|
|
2708
|
-
message: errorMessage
|
|
2709
|
-
}
|
|
2710
|
-
});
|
|
2711
|
-
}
|
|
2970
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
2971
|
+
sendJSON(res, result.status, result.body);
|
|
2712
2972
|
return;
|
|
2713
2973
|
}
|
|
2714
2974
|
if (pathname === "/install" && req.method === "POST") {
|
|
2715
|
-
if (!config.hooks?.install) {
|
|
2716
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
2975
|
let installBody;
|
|
2720
2976
|
try {
|
|
2721
2977
|
installBody = await parseJSONBody(req);
|
|
@@ -2725,61 +2981,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2725
2981
|
});
|
|
2726
2982
|
return;
|
|
2727
2983
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
2731
|
-
});
|
|
2732
|
-
return;
|
|
2733
|
-
}
|
|
2734
|
-
const installContext = {
|
|
2735
|
-
env: installBody.env ?? {},
|
|
2736
|
-
workplace: installBody.context.workplace,
|
|
2737
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
2738
|
-
app: installBody.context.app,
|
|
2739
|
-
invocation: installBody.invocation,
|
|
2740
|
-
log: createContextLogger()
|
|
2741
|
-
};
|
|
2742
|
-
const installRequestConfig = {
|
|
2743
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2744
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2745
|
-
};
|
|
2746
|
-
try {
|
|
2747
|
-
const installHook = config.hooks.install;
|
|
2748
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2749
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
2750
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
2751
|
-
return await installHandler(installContext);
|
|
2752
|
-
});
|
|
2753
|
-
});
|
|
2754
|
-
sendJSON(res, 200, {
|
|
2755
|
-
env: result.env ?? {},
|
|
2756
|
-
redirect: result.redirect
|
|
2757
|
-
});
|
|
2758
|
-
} catch (err) {
|
|
2759
|
-
if (err instanceof InstallError) {
|
|
2760
|
-
sendJSON(res, 400, {
|
|
2761
|
-
error: {
|
|
2762
|
-
code: err.code,
|
|
2763
|
-
message: err.message,
|
|
2764
|
-
field: err.field
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
} else {
|
|
2768
|
-
sendJSON(res, 500, {
|
|
2769
|
-
error: {
|
|
2770
|
-
code: -32603,
|
|
2771
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2772
|
-
}
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2984
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
2985
|
+
sendJSON(res, result.status, result.body);
|
|
2776
2986
|
return;
|
|
2777
2987
|
}
|
|
2778
2988
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
2779
|
-
if (!config.hooks?.uninstall) {
|
|
2780
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
2781
|
-
return;
|
|
2782
|
-
}
|
|
2783
2989
|
let uninstallBody;
|
|
2784
2990
|
try {
|
|
2785
2991
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -2789,53 +2995,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2789
2995
|
});
|
|
2790
2996
|
return;
|
|
2791
2997
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
error: {
|
|
2795
|
-
code: -32602,
|
|
2796
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2797
|
-
}
|
|
2798
|
-
});
|
|
2799
|
-
return;
|
|
2800
|
-
}
|
|
2801
|
-
const uninstallContext = {
|
|
2802
|
-
env: uninstallBody.env ?? {},
|
|
2803
|
-
workplace: uninstallBody.context.workplace,
|
|
2804
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
2805
|
-
app: uninstallBody.context.app,
|
|
2806
|
-
invocation: uninstallBody.invocation,
|
|
2807
|
-
log: createContextLogger()
|
|
2808
|
-
};
|
|
2809
|
-
const uninstallRequestConfig = {
|
|
2810
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2811
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2812
|
-
};
|
|
2813
|
-
try {
|
|
2814
|
-
const uninstallHook = config.hooks.uninstall;
|
|
2815
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2816
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
2817
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
2818
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
2819
|
-
});
|
|
2820
|
-
});
|
|
2821
|
-
sendJSON(res, 200, {
|
|
2822
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
2823
|
-
});
|
|
2824
|
-
} catch (err) {
|
|
2825
|
-
sendJSON(res, 500, {
|
|
2826
|
-
error: {
|
|
2827
|
-
code: -32603,
|
|
2828
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2829
|
-
}
|
|
2830
|
-
});
|
|
2831
|
-
}
|
|
2998
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
2999
|
+
sendJSON(res, result.status, result.body);
|
|
2832
3000
|
return;
|
|
2833
3001
|
}
|
|
2834
3002
|
if (pathname === "/provision" && req.method === "POST") {
|
|
2835
|
-
if (!config.hooks?.provision) {
|
|
2836
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
2837
|
-
return;
|
|
2838
|
-
}
|
|
2839
3003
|
let provisionBody;
|
|
2840
3004
|
try {
|
|
2841
3005
|
provisionBody = await parseJSONBody(req);
|
|
@@ -2845,46 +3009,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2845
3009
|
});
|
|
2846
3010
|
return;
|
|
2847
3011
|
}
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
2851
|
-
});
|
|
2852
|
-
return;
|
|
2853
|
-
}
|
|
2854
|
-
const mergedEnv = {};
|
|
2855
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
2856
|
-
if (value !== void 0) {
|
|
2857
|
-
mergedEnv[key] = value;
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
2861
|
-
const provisionContext = {
|
|
2862
|
-
env: mergedEnv,
|
|
2863
|
-
app: provisionBody.context.app,
|
|
2864
|
-
invocation: provisionBody.invocation,
|
|
2865
|
-
log: createContextLogger()
|
|
2866
|
-
};
|
|
2867
|
-
const provisionRequestConfig = {
|
|
2868
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
2869
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
2870
|
-
};
|
|
2871
|
-
try {
|
|
2872
|
-
const provisionHook = config.hooks.provision;
|
|
2873
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
2874
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
2875
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
2876
|
-
return await provisionHandler(provisionContext);
|
|
2877
|
-
});
|
|
2878
|
-
});
|
|
2879
|
-
sendJSON(res, 200, result);
|
|
2880
|
-
} catch (err) {
|
|
2881
|
-
sendJSON(res, 500, {
|
|
2882
|
-
error: {
|
|
2883
|
-
code: -32603,
|
|
2884
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2885
|
-
}
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
3012
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3013
|
+
sendJSON(res, result.status, result.body);
|
|
2888
3014
|
return;
|
|
2889
3015
|
}
|
|
2890
3016
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -3065,12 +3191,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3065
3191
|
}
|
|
3066
3192
|
if (path2.startsWith("/webhooks/") && webhookRegistry) {
|
|
3067
3193
|
const handle = path2.slice("/webhooks/".length);
|
|
3068
|
-
|
|
3069
|
-
if (!webhookDef) {
|
|
3194
|
+
if (!webhookRegistry[handle]) {
|
|
3070
3195
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
3071
3196
|
}
|
|
3072
|
-
|
|
3073
|
-
if (!allowedMethods.includes(method)) {
|
|
3197
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
3074
3198
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
3075
3199
|
}
|
|
3076
3200
|
const rawBody = event.body ?? "";
|
|
@@ -3085,107 +3209,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3085
3209
|
} else {
|
|
3086
3210
|
parsedBody = rawBody;
|
|
3087
3211
|
}
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
method: envelope.request.method,
|
|
3107
|
-
url: envelope.request.url,
|
|
3108
|
-
path: envelope.request.path,
|
|
3109
|
-
headers: envelope.request.headers,
|
|
3110
|
-
query: envelope.request.query,
|
|
3111
|
-
body: originalParsedBody,
|
|
3112
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
3113
|
-
};
|
|
3114
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
3115
|
-
const app = envelope.context.app;
|
|
3116
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
3117
|
-
webhookContext = {
|
|
3118
|
-
env: envVars,
|
|
3119
|
-
app,
|
|
3120
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
3121
|
-
workplace: envelope.context.workplace,
|
|
3122
|
-
registration: envelope.context.registration ?? {},
|
|
3123
|
-
invocation,
|
|
3124
|
-
log: createContextLogger()
|
|
3125
|
-
};
|
|
3126
|
-
} else {
|
|
3127
|
-
webhookContext = {
|
|
3128
|
-
env: envVars,
|
|
3129
|
-
app,
|
|
3130
|
-
invocation,
|
|
3131
|
-
log: createContextLogger()
|
|
3132
|
-
};
|
|
3133
|
-
}
|
|
3134
|
-
} else {
|
|
3135
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
3136
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
3137
|
-
if (!appId || !appVersionId) {
|
|
3138
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
3139
|
-
}
|
|
3140
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3141
|
-
const protocol = forwardedProto ?? "https";
|
|
3142
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3143
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3144
|
-
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3145
|
-
webhookRequest = {
|
|
3146
|
-
method,
|
|
3147
|
-
url: webhookUrl,
|
|
3148
|
-
path: path2,
|
|
3149
|
-
headers: event.headers,
|
|
3150
|
-
query: event.queryStringParameters ?? {},
|
|
3151
|
-
body: parsedBody,
|
|
3152
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3153
|
-
};
|
|
3154
|
-
webhookContext = {
|
|
3155
|
-
env: process.env,
|
|
3156
|
-
app: { id: appId, versionId: appVersionId },
|
|
3157
|
-
log: createContextLogger()
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
const originalEnv = { ...process.env };
|
|
3161
|
-
Object.assign(process.env, requestEnv);
|
|
3162
|
-
const requestConfig = {
|
|
3163
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3164
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3165
|
-
};
|
|
3166
|
-
let webhookResponse;
|
|
3167
|
-
try {
|
|
3168
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
3169
|
-
return await runWithConfig(requestConfig, async () => {
|
|
3170
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
3171
|
-
});
|
|
3172
|
-
});
|
|
3173
|
-
} catch (err) {
|
|
3174
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3175
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
3176
|
-
} finally {
|
|
3177
|
-
process.env = originalEnv;
|
|
3212
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3213
|
+
const protocol = forwardedProto ?? "https";
|
|
3214
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3215
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3216
|
+
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3217
|
+
const parseResult = parseWebhookRequest(
|
|
3218
|
+
parsedBody,
|
|
3219
|
+
method,
|
|
3220
|
+
webhookUrl,
|
|
3221
|
+
path2,
|
|
3222
|
+
event.headers,
|
|
3223
|
+
event.queryStringParameters ?? {},
|
|
3224
|
+
rawBody,
|
|
3225
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
3226
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
3227
|
+
);
|
|
3228
|
+
if ("error" in parseResult) {
|
|
3229
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
3178
3230
|
}
|
|
3231
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
3179
3232
|
const responseHeaders = {
|
|
3180
3233
|
...headers,
|
|
3181
|
-
...
|
|
3234
|
+
...result.headers
|
|
3182
3235
|
};
|
|
3183
|
-
const status = webhookResponse.status ?? 200;
|
|
3184
|
-
const body = webhookResponse.body;
|
|
3185
3236
|
return {
|
|
3186
|
-
statusCode: status,
|
|
3237
|
+
statusCode: result.status,
|
|
3187
3238
|
headers: responseHeaders,
|
|
3188
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
3239
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
3189
3240
|
};
|
|
3190
3241
|
}
|
|
3191
3242
|
if (path2 === "/core" && method === "POST") {
|
|
@@ -3321,9 +3372,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3321
3372
|
}
|
|
3322
3373
|
}
|
|
3323
3374
|
if (path2 === "/install" && method === "POST") {
|
|
3324
|
-
if (!config.hooks?.install) {
|
|
3325
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
3326
|
-
}
|
|
3327
3375
|
let installBody;
|
|
3328
3376
|
try {
|
|
3329
3377
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3334,68 +3382,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3334
3382
|
headers
|
|
3335
3383
|
);
|
|
3336
3384
|
}
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
400,
|
|
3340
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
3341
|
-
headers
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
const installContext = {
|
|
3345
|
-
env: installBody.env ?? {},
|
|
3346
|
-
workplace: installBody.context.workplace,
|
|
3347
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
3348
|
-
app: installBody.context.app,
|
|
3349
|
-
invocation: installBody.invocation,
|
|
3350
|
-
log: createContextLogger()
|
|
3351
|
-
};
|
|
3352
|
-
const installRequestConfig = {
|
|
3353
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3354
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3355
|
-
};
|
|
3356
|
-
try {
|
|
3357
|
-
const installHook = config.hooks.install;
|
|
3358
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
3359
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
3360
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
3361
|
-
return await installHandler(installContext);
|
|
3362
|
-
});
|
|
3363
|
-
});
|
|
3364
|
-
return createResponse(
|
|
3365
|
-
200,
|
|
3366
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
3367
|
-
headers
|
|
3368
|
-
);
|
|
3369
|
-
} catch (err) {
|
|
3370
|
-
if (err instanceof InstallError) {
|
|
3371
|
-
return createResponse(
|
|
3372
|
-
400,
|
|
3373
|
-
{
|
|
3374
|
-
error: {
|
|
3375
|
-
code: err.code,
|
|
3376
|
-
message: err.message,
|
|
3377
|
-
field: err.field
|
|
3378
|
-
}
|
|
3379
|
-
},
|
|
3380
|
-
headers
|
|
3381
|
-
);
|
|
3382
|
-
}
|
|
3383
|
-
return createResponse(
|
|
3384
|
-
500,
|
|
3385
|
-
{
|
|
3386
|
-
error: {
|
|
3387
|
-
code: -32603,
|
|
3388
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3389
|
-
}
|
|
3390
|
-
},
|
|
3391
|
-
headers
|
|
3392
|
-
);
|
|
3393
|
-
}
|
|
3385
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3386
|
+
return createResponse(result.status, result.body, headers);
|
|
3394
3387
|
}
|
|
3395
3388
|
if (path2 === "/uninstall" && method === "POST") {
|
|
3396
|
-
if (!config.hooks?.uninstall) {
|
|
3397
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
3398
|
-
}
|
|
3399
3389
|
let uninstallBody;
|
|
3400
3390
|
try {
|
|
3401
3391
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3406,137 +3396,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3406
3396
|
headers
|
|
3407
3397
|
);
|
|
3408
3398
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
400,
|
|
3412
|
-
{
|
|
3413
|
-
error: {
|
|
3414
|
-
code: -32602,
|
|
3415
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3416
|
-
}
|
|
3417
|
-
},
|
|
3418
|
-
headers
|
|
3419
|
-
);
|
|
3420
|
-
}
|
|
3421
|
-
const uninstallContext = {
|
|
3422
|
-
env: uninstallBody.env ?? {},
|
|
3423
|
-
workplace: uninstallBody.context.workplace,
|
|
3424
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
3425
|
-
app: uninstallBody.context.app,
|
|
3426
|
-
invocation: uninstallBody.invocation,
|
|
3427
|
-
log: createContextLogger()
|
|
3428
|
-
};
|
|
3429
|
-
const uninstallRequestConfig = {
|
|
3430
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3431
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3432
|
-
};
|
|
3433
|
-
try {
|
|
3434
|
-
const uninstallHook = config.hooks.uninstall;
|
|
3435
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3436
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
3437
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
3438
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
3439
|
-
});
|
|
3440
|
-
});
|
|
3441
|
-
return createResponse(
|
|
3442
|
-
200,
|
|
3443
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
3444
|
-
headers
|
|
3445
|
-
);
|
|
3446
|
-
} catch (err) {
|
|
3447
|
-
return createResponse(
|
|
3448
|
-
500,
|
|
3449
|
-
{
|
|
3450
|
-
error: {
|
|
3451
|
-
code: -32603,
|
|
3452
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3453
|
-
}
|
|
3454
|
-
},
|
|
3455
|
-
headers
|
|
3456
|
-
);
|
|
3457
|
-
}
|
|
3399
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3400
|
+
return createResponse(result.status, result.body, headers);
|
|
3458
3401
|
}
|
|
3459
3402
|
if (path2 === "/provision" && method === "POST") {
|
|
3460
|
-
console.log("[serverless] /provision endpoint called");
|
|
3461
|
-
if (!config.hooks?.provision) {
|
|
3462
|
-
console.log("[serverless] No provision handler configured");
|
|
3463
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
3464
|
-
}
|
|
3465
3403
|
let provisionBody;
|
|
3466
3404
|
try {
|
|
3467
3405
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
3468
|
-
console.log("[serverless] Provision body parsed:", {
|
|
3469
|
-
hasEnv: !!provisionBody.env,
|
|
3470
|
-
hasContext: !!provisionBody.context,
|
|
3471
|
-
appId: provisionBody.context?.app?.id,
|
|
3472
|
-
versionId: provisionBody.context?.app?.versionId
|
|
3473
|
-
});
|
|
3474
3406
|
} catch {
|
|
3475
|
-
console.log("[serverless] Failed to parse provision body");
|
|
3476
3407
|
return createResponse(
|
|
3477
3408
|
400,
|
|
3478
3409
|
{ error: { code: -32700, message: "Parse error" } },
|
|
3479
3410
|
headers
|
|
3480
3411
|
);
|
|
3481
3412
|
}
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
return createResponse(
|
|
3485
|
-
400,
|
|
3486
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
3487
|
-
headers
|
|
3488
|
-
);
|
|
3489
|
-
}
|
|
3490
|
-
const mergedEnv = {};
|
|
3491
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3492
|
-
if (value !== void 0) {
|
|
3493
|
-
mergedEnv[key] = value;
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3497
|
-
const provisionContext = {
|
|
3498
|
-
env: mergedEnv,
|
|
3499
|
-
app: provisionBody.context.app,
|
|
3500
|
-
invocation: provisionBody.invocation,
|
|
3501
|
-
log: createContextLogger()
|
|
3502
|
-
};
|
|
3503
|
-
const provisionRequestConfig = {
|
|
3504
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3505
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3506
|
-
};
|
|
3507
|
-
console.log("[serverless] Calling provision handler...");
|
|
3508
|
-
try {
|
|
3509
|
-
const provisionHook = config.hooks.provision;
|
|
3510
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3511
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
3512
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
3513
|
-
return await provisionHandler(provisionContext);
|
|
3514
|
-
});
|
|
3515
|
-
});
|
|
3516
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
3517
|
-
return createResponse(200, result, headers);
|
|
3518
|
-
} catch (err) {
|
|
3519
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
3520
|
-
return createResponse(
|
|
3521
|
-
500,
|
|
3522
|
-
{
|
|
3523
|
-
error: {
|
|
3524
|
-
code: -32603,
|
|
3525
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3526
|
-
}
|
|
3527
|
-
},
|
|
3528
|
-
headers
|
|
3529
|
-
);
|
|
3530
|
-
}
|
|
3413
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3414
|
+
return createResponse(result.status, result.body, headers);
|
|
3531
3415
|
}
|
|
3532
3416
|
if (path2 === "/oauth_callback" && method === "POST") {
|
|
3533
|
-
if (!config.hooks?.oauth_callback) {
|
|
3534
|
-
return createResponse(
|
|
3535
|
-
404,
|
|
3536
|
-
{ error: "OAuth callback handler not configured" },
|
|
3537
|
-
headers
|
|
3538
|
-
);
|
|
3539
|
-
}
|
|
3540
3417
|
let parsedBody;
|
|
3541
3418
|
try {
|
|
3542
3419
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3548,52 +3425,8 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3548
3425
|
headers
|
|
3549
3426
|
);
|
|
3550
3427
|
}
|
|
3551
|
-
const
|
|
3552
|
-
|
|
3553
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
3554
|
-
return createResponse(
|
|
3555
|
-
400,
|
|
3556
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
3557
|
-
headers
|
|
3558
|
-
);
|
|
3559
|
-
}
|
|
3560
|
-
const invocation = parsedBody.invocation;
|
|
3561
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
3562
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
3563
|
-
const oauthCallbackContext = {
|
|
3564
|
-
request: oauthRequest,
|
|
3565
|
-
invocation,
|
|
3566
|
-
log: createContextLogger()
|
|
3567
|
-
};
|
|
3568
|
-
try {
|
|
3569
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
3570
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
3571
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
3572
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
3573
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
3574
|
-
});
|
|
3575
|
-
});
|
|
3576
|
-
return createResponse(
|
|
3577
|
-
200,
|
|
3578
|
-
{
|
|
3579
|
-
appInstallationId: result.appInstallationId,
|
|
3580
|
-
env: result.env ?? {}
|
|
3581
|
-
},
|
|
3582
|
-
headers
|
|
3583
|
-
);
|
|
3584
|
-
} catch (err) {
|
|
3585
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
3586
|
-
return createResponse(
|
|
3587
|
-
500,
|
|
3588
|
-
{
|
|
3589
|
-
error: {
|
|
3590
|
-
code: -32603,
|
|
3591
|
-
message: errorMessage
|
|
3592
|
-
}
|
|
3593
|
-
},
|
|
3594
|
-
headers
|
|
3595
|
-
);
|
|
3596
|
-
}
|
|
3428
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3429
|
+
return createResponse(result.status, result.body, headers);
|
|
3597
3430
|
}
|
|
3598
3431
|
if (path2 === "/health" && method === "GET") {
|
|
3599
3432
|
return createResponse(200, state.getHealthStatus(), headers);
|