skedyul 1.2.21 → 1.2.23
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/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/index.js
CHANGED
|
@@ -2244,8 +2244,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
2244
2244
|
};
|
|
2245
2245
|
}
|
|
2246
2246
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
2247
|
-
return async function callTool(
|
|
2248
|
-
const toolName = String(
|
|
2247
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
2248
|
+
const toolName = String(toolNameInput);
|
|
2249
2249
|
const tool = registry[toolName];
|
|
2250
2250
|
if (!tool) {
|
|
2251
2251
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -2254,7 +2254,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
2254
2254
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
2255
2255
|
}
|
|
2256
2256
|
const fn = tool.handler;
|
|
2257
|
-
const args =
|
|
2257
|
+
const args = toolArgsInput ?? {};
|
|
2258
2258
|
const estimateMode = args.estimate === true;
|
|
2259
2259
|
if (!estimateMode) {
|
|
2260
2260
|
state.incrementRequestCount();
|
|
@@ -2509,51 +2509,6 @@ async function handleCoreMethod(method, params) {
|
|
|
2509
2509
|
};
|
|
2510
2510
|
}
|
|
2511
2511
|
|
|
2512
|
-
// src/server/handler-helpers.ts
|
|
2513
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
2514
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2515
|
-
return null;
|
|
2516
|
-
}
|
|
2517
|
-
const envelope = parsedBody;
|
|
2518
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2519
|
-
return null;
|
|
2520
|
-
}
|
|
2521
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2522
|
-
return null;
|
|
2523
|
-
}
|
|
2524
|
-
return {
|
|
2525
|
-
env: envelope.env,
|
|
2526
|
-
request: envelope.request,
|
|
2527
|
-
context: envelope.context
|
|
2528
|
-
};
|
|
2529
|
-
}
|
|
2530
|
-
function buildRequestFromRaw(raw) {
|
|
2531
|
-
let parsedBody = raw.body;
|
|
2532
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
2533
|
-
if (contentType.includes("application/json")) {
|
|
2534
|
-
try {
|
|
2535
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2536
|
-
} catch {
|
|
2537
|
-
parsedBody = raw.body;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
return {
|
|
2541
|
-
method: raw.method,
|
|
2542
|
-
url: raw.url,
|
|
2543
|
-
path: raw.path,
|
|
2544
|
-
headers: raw.headers,
|
|
2545
|
-
query: raw.query,
|
|
2546
|
-
body: parsedBody,
|
|
2547
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2548
|
-
};
|
|
2549
|
-
}
|
|
2550
|
-
function buildRequestScopedConfig(env) {
|
|
2551
|
-
return {
|
|
2552
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2553
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2554
|
-
};
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
2512
|
// src/server/startup-logger.ts
|
|
2558
2513
|
function padEnd(str, length) {
|
|
2559
2514
|
if (str.length >= length) {
|
|
@@ -2651,6 +2606,405 @@ function isProvisionConfig(value) {
|
|
|
2651
2606
|
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
2652
2607
|
}
|
|
2653
2608
|
|
|
2609
|
+
// src/server/handlers/install-handler.ts
|
|
2610
|
+
async function handleInstall(body, hooks) {
|
|
2611
|
+
if (!hooks?.install) {
|
|
2612
|
+
return {
|
|
2613
|
+
status: 404,
|
|
2614
|
+
body: { error: "Install handler not configured" }
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
2618
|
+
return {
|
|
2619
|
+
status: 400,
|
|
2620
|
+
body: {
|
|
2621
|
+
error: {
|
|
2622
|
+
code: -32602,
|
|
2623
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
const installContext = {
|
|
2629
|
+
env: body.env ?? {},
|
|
2630
|
+
workplace: body.context.workplace,
|
|
2631
|
+
appInstallationId: body.context.appInstallationId,
|
|
2632
|
+
app: body.context.app,
|
|
2633
|
+
invocation: body.invocation,
|
|
2634
|
+
log: createContextLogger()
|
|
2635
|
+
};
|
|
2636
|
+
const requestConfig = {
|
|
2637
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2638
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2639
|
+
};
|
|
2640
|
+
try {
|
|
2641
|
+
const installHook = hooks.install;
|
|
2642
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2643
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2644
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2645
|
+
return await installHandler(installContext);
|
|
2646
|
+
});
|
|
2647
|
+
});
|
|
2648
|
+
return {
|
|
2649
|
+
status: 200,
|
|
2650
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
2651
|
+
};
|
|
2652
|
+
} catch (err) {
|
|
2653
|
+
if (err instanceof InstallError) {
|
|
2654
|
+
return {
|
|
2655
|
+
status: 400,
|
|
2656
|
+
body: {
|
|
2657
|
+
error: {
|
|
2658
|
+
code: err.code,
|
|
2659
|
+
message: err.message,
|
|
2660
|
+
field: err.field
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
return {
|
|
2666
|
+
status: 500,
|
|
2667
|
+
body: {
|
|
2668
|
+
error: {
|
|
2669
|
+
code: -32603,
|
|
2670
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/server/handlers/uninstall-handler.ts
|
|
2678
|
+
async function handleUninstall(body, hooks) {
|
|
2679
|
+
if (!hooks?.uninstall) {
|
|
2680
|
+
return {
|
|
2681
|
+
status: 404,
|
|
2682
|
+
body: { error: "Uninstall handler not configured" }
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
2686
|
+
return {
|
|
2687
|
+
status: 400,
|
|
2688
|
+
body: {
|
|
2689
|
+
error: {
|
|
2690
|
+
code: -32602,
|
|
2691
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
const uninstallContext = {
|
|
2697
|
+
env: body.env ?? {},
|
|
2698
|
+
workplace: body.context.workplace,
|
|
2699
|
+
appInstallationId: body.context.appInstallationId,
|
|
2700
|
+
app: body.context.app,
|
|
2701
|
+
invocation: body.invocation,
|
|
2702
|
+
log: createContextLogger()
|
|
2703
|
+
};
|
|
2704
|
+
const requestConfig = {
|
|
2705
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2706
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2707
|
+
};
|
|
2708
|
+
try {
|
|
2709
|
+
const uninstallHook = hooks.uninstall;
|
|
2710
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2711
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2712
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2713
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
2714
|
+
});
|
|
2715
|
+
});
|
|
2716
|
+
return {
|
|
2717
|
+
status: 200,
|
|
2718
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
2719
|
+
};
|
|
2720
|
+
} catch (err) {
|
|
2721
|
+
return {
|
|
2722
|
+
status: 500,
|
|
2723
|
+
body: {
|
|
2724
|
+
error: {
|
|
2725
|
+
code: -32603,
|
|
2726
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// src/server/handlers/provision-handler.ts
|
|
2734
|
+
async function handleProvision(body, hooks) {
|
|
2735
|
+
if (!hooks?.provision) {
|
|
2736
|
+
return {
|
|
2737
|
+
status: 404,
|
|
2738
|
+
body: { error: "Provision handler not configured" }
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
if (!body.context?.app) {
|
|
2742
|
+
return {
|
|
2743
|
+
status: 400,
|
|
2744
|
+
body: {
|
|
2745
|
+
error: {
|
|
2746
|
+
code: -32602,
|
|
2747
|
+
message: "Missing context (app required)"
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
const mergedEnv = {};
|
|
2753
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2754
|
+
if (value !== void 0) {
|
|
2755
|
+
mergedEnv[key] = value;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
2759
|
+
const provisionContext = {
|
|
2760
|
+
env: mergedEnv,
|
|
2761
|
+
app: body.context.app,
|
|
2762
|
+
invocation: body.invocation,
|
|
2763
|
+
log: createContextLogger()
|
|
2764
|
+
};
|
|
2765
|
+
const requestConfig = {
|
|
2766
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
2767
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
2768
|
+
};
|
|
2769
|
+
try {
|
|
2770
|
+
const provisionHook = hooks.provision;
|
|
2771
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
2772
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2773
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2774
|
+
return await provisionHandler(provisionContext);
|
|
2775
|
+
});
|
|
2776
|
+
});
|
|
2777
|
+
return {
|
|
2778
|
+
status: 200,
|
|
2779
|
+
body: result
|
|
2780
|
+
};
|
|
2781
|
+
} catch (err) {
|
|
2782
|
+
return {
|
|
2783
|
+
status: 500,
|
|
2784
|
+
body: {
|
|
2785
|
+
error: {
|
|
2786
|
+
code: -32603,
|
|
2787
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/server/handler-helpers.ts
|
|
2795
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
2796
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2797
|
+
return null;
|
|
2798
|
+
}
|
|
2799
|
+
const envelope = parsedBody;
|
|
2800
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2801
|
+
return null;
|
|
2802
|
+
}
|
|
2803
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
return {
|
|
2807
|
+
env: envelope.env,
|
|
2808
|
+
request: envelope.request,
|
|
2809
|
+
context: envelope.context
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function buildRequestFromRaw(raw) {
|
|
2813
|
+
let parsedBody = raw.body;
|
|
2814
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
2815
|
+
if (contentType.includes("application/json")) {
|
|
2816
|
+
try {
|
|
2817
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2818
|
+
} catch {
|
|
2819
|
+
parsedBody = raw.body;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
return {
|
|
2823
|
+
method: raw.method,
|
|
2824
|
+
url: raw.url,
|
|
2825
|
+
path: raw.path,
|
|
2826
|
+
headers: raw.headers,
|
|
2827
|
+
query: raw.query,
|
|
2828
|
+
body: parsedBody,
|
|
2829
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
function buildRequestScopedConfig(env) {
|
|
2833
|
+
return {
|
|
2834
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2835
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
2840
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
2841
|
+
if (!hooks?.oauth_callback) {
|
|
2842
|
+
return {
|
|
2843
|
+
status: 404,
|
|
2844
|
+
body: { error: "OAuth callback handler not configured" }
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
2848
|
+
if (!envelope) {
|
|
2849
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2850
|
+
return {
|
|
2851
|
+
status: 400,
|
|
2852
|
+
body: {
|
|
2853
|
+
error: {
|
|
2854
|
+
code: -32602,
|
|
2855
|
+
message: "Missing envelope format: expected { env, request }"
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
const invocation = parsedBody.invocation;
|
|
2861
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2862
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
2863
|
+
const oauthCallbackContext = {
|
|
2864
|
+
request: oauthRequest,
|
|
2865
|
+
invocation,
|
|
2866
|
+
log: createContextLogger()
|
|
2867
|
+
};
|
|
2868
|
+
try {
|
|
2869
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
2870
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2871
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
2872
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2873
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2874
|
+
});
|
|
2875
|
+
});
|
|
2876
|
+
return {
|
|
2877
|
+
status: 200,
|
|
2878
|
+
body: {
|
|
2879
|
+
appInstallationId: result.appInstallationId,
|
|
2880
|
+
env: result.env ?? {}
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
} catch (err) {
|
|
2884
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2885
|
+
return {
|
|
2886
|
+
status: 500,
|
|
2887
|
+
body: {
|
|
2888
|
+
error: {
|
|
2889
|
+
code: -32603,
|
|
2890
|
+
message: errorMessage
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
// src/server/handlers/webhook-handler.ts
|
|
2898
|
+
function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
2899
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
2900
|
+
if (isEnvelope) {
|
|
2901
|
+
const envelope = parsedBody;
|
|
2902
|
+
const requestEnv = envelope.env ?? {};
|
|
2903
|
+
const invocation = envelope.invocation;
|
|
2904
|
+
let originalParsedBody = envelope.request.body;
|
|
2905
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
2906
|
+
if (originalContentType.includes("application/json")) {
|
|
2907
|
+
try {
|
|
2908
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
2909
|
+
} catch {
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
const webhookRequest2 = {
|
|
2913
|
+
method: envelope.request.method,
|
|
2914
|
+
url: envelope.request.url,
|
|
2915
|
+
path: envelope.request.path,
|
|
2916
|
+
headers: envelope.request.headers,
|
|
2917
|
+
query: envelope.request.query,
|
|
2918
|
+
body: originalParsedBody,
|
|
2919
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
2920
|
+
};
|
|
2921
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
2922
|
+
const app = envelope.context.app;
|
|
2923
|
+
let webhookContext2;
|
|
2924
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
2925
|
+
webhookContext2 = {
|
|
2926
|
+
env: envVars,
|
|
2927
|
+
app,
|
|
2928
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
2929
|
+
workplace: envelope.context.workplace,
|
|
2930
|
+
registration: envelope.context.registration ?? {},
|
|
2931
|
+
invocation,
|
|
2932
|
+
log: createContextLogger()
|
|
2933
|
+
};
|
|
2934
|
+
} else {
|
|
2935
|
+
webhookContext2 = {
|
|
2936
|
+
env: envVars,
|
|
2937
|
+
app,
|
|
2938
|
+
invocation,
|
|
2939
|
+
log: createContextLogger()
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
2943
|
+
}
|
|
2944
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
2945
|
+
return {
|
|
2946
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
const webhookRequest = {
|
|
2950
|
+
method,
|
|
2951
|
+
url,
|
|
2952
|
+
path: path2,
|
|
2953
|
+
headers,
|
|
2954
|
+
query,
|
|
2955
|
+
body: parsedBody,
|
|
2956
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2957
|
+
};
|
|
2958
|
+
const webhookContext = {
|
|
2959
|
+
env: process.env,
|
|
2960
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
2961
|
+
log: createContextLogger()
|
|
2962
|
+
};
|
|
2963
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
2964
|
+
}
|
|
2965
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
2966
|
+
const webhookDef = webhookRegistry[handle];
|
|
2967
|
+
if (!webhookDef) {
|
|
2968
|
+
return {
|
|
2969
|
+
status: 404,
|
|
2970
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
const originalEnv = { ...process.env };
|
|
2974
|
+
Object.assign(process.env, data.requestEnv);
|
|
2975
|
+
const requestConfig = {
|
|
2976
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2977
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2978
|
+
};
|
|
2979
|
+
let webhookResponse;
|
|
2980
|
+
try {
|
|
2981
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
2982
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2983
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
2984
|
+
});
|
|
2985
|
+
});
|
|
2986
|
+
} catch (err) {
|
|
2987
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2988
|
+
return {
|
|
2989
|
+
status: 500,
|
|
2990
|
+
body: { error: "Webhook handler error" }
|
|
2991
|
+
};
|
|
2992
|
+
} finally {
|
|
2993
|
+
process.env = originalEnv;
|
|
2994
|
+
}
|
|
2995
|
+
return {
|
|
2996
|
+
status: webhookResponse.status ?? 200,
|
|
2997
|
+
body: webhookResponse.body,
|
|
2998
|
+
headers: webhookResponse.headers
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
3002
|
+
const webhookDef = webhookRegistry[handle];
|
|
3003
|
+
if (!webhookDef) return false;
|
|
3004
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
3005
|
+
return allowedMethods.includes(method);
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2654
3008
|
// src/server/dedicated.ts
|
|
2655
3009
|
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
2656
3010
|
const port = getListeningPort(config);
|
|
@@ -2677,13 +3031,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2677
3031
|
}
|
|
2678
3032
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
2679
3033
|
const handle = pathname.slice("/webhooks/".length);
|
|
2680
|
-
|
|
2681
|
-
if (!webhookDef) {
|
|
3034
|
+
if (!webhookRegistry[handle]) {
|
|
2682
3035
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
2683
3036
|
return;
|
|
2684
3037
|
}
|
|
2685
|
-
|
|
2686
|
-
if (!allowedMethods.includes(req.method)) {
|
|
3038
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
2687
3039
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
2688
3040
|
return;
|
|
2689
3041
|
}
|
|
@@ -2705,87 +3057,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2705
3057
|
} else {
|
|
2706
3058
|
parsedBody = rawBody;
|
|
2707
3059
|
}
|
|
2708
|
-
const
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
webhookContext = {
|
|
2722
|
-
env: envVars,
|
|
2723
|
-
app,
|
|
2724
|
-
appInstallationId: context.appInstallationId,
|
|
2725
|
-
workplace: context.workplace,
|
|
2726
|
-
registration: context.registration ?? {},
|
|
2727
|
-
invocation,
|
|
2728
|
-
log: createContextLogger()
|
|
2729
|
-
};
|
|
2730
|
-
} else {
|
|
2731
|
-
webhookContext = {
|
|
2732
|
-
env: envVars,
|
|
2733
|
-
app,
|
|
2734
|
-
invocation,
|
|
2735
|
-
log: createContextLogger()
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
} else {
|
|
2739
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
2740
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
2741
|
-
if (!appId || !appVersionId) {
|
|
2742
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
2743
|
-
}
|
|
2744
|
-
webhookRequest = {
|
|
2745
|
-
method: req.method ?? "POST",
|
|
2746
|
-
url: url.toString(),
|
|
2747
|
-
path: pathname,
|
|
2748
|
-
headers: req.headers,
|
|
2749
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
2750
|
-
body: parsedBody,
|
|
2751
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
2752
|
-
};
|
|
2753
|
-
webhookContext = {
|
|
2754
|
-
env: process.env,
|
|
2755
|
-
app: { id: appId, versionId: appVersionId },
|
|
2756
|
-
log: createContextLogger()
|
|
2757
|
-
};
|
|
2758
|
-
}
|
|
2759
|
-
const originalEnv = { ...process.env };
|
|
2760
|
-
Object.assign(process.env, requestEnv);
|
|
2761
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
2762
|
-
let webhookResponse;
|
|
2763
|
-
try {
|
|
2764
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
2765
|
-
return await runWithConfig(requestConfig, async () => {
|
|
2766
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
2767
|
-
});
|
|
2768
|
-
});
|
|
2769
|
-
} catch (err) {
|
|
2770
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
2771
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
3060
|
+
const parseResult = parseWebhookRequest(
|
|
3061
|
+
parsedBody,
|
|
3062
|
+
req.method ?? "POST",
|
|
3063
|
+
url.toString(),
|
|
3064
|
+
pathname,
|
|
3065
|
+
req.headers,
|
|
3066
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
3067
|
+
rawBody,
|
|
3068
|
+
req.headers["x-skedyul-app-id"],
|
|
3069
|
+
req.headers["x-skedyul-app-version-id"]
|
|
3070
|
+
);
|
|
3071
|
+
if ("error" in parseResult) {
|
|
3072
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
2772
3073
|
return;
|
|
2773
|
-
} finally {
|
|
2774
|
-
process.env = originalEnv;
|
|
2775
3074
|
}
|
|
2776
|
-
const
|
|
3075
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
2777
3076
|
const responseHeaders = {
|
|
2778
|
-
...
|
|
3077
|
+
...result.headers
|
|
2779
3078
|
};
|
|
2780
3079
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
2781
3080
|
responseHeaders["Content-Type"] = "application/json";
|
|
2782
3081
|
}
|
|
2783
|
-
res.writeHead(status, responseHeaders);
|
|
2784
|
-
if (
|
|
2785
|
-
if (typeof
|
|
2786
|
-
res.end(
|
|
3082
|
+
res.writeHead(result.status, responseHeaders);
|
|
3083
|
+
if (result.body !== void 0) {
|
|
3084
|
+
if (typeof result.body === "string") {
|
|
3085
|
+
res.end(result.body);
|
|
2787
3086
|
} else {
|
|
2788
|
-
res.end(JSON.stringify(
|
|
3087
|
+
res.end(JSON.stringify(result.body));
|
|
2789
3088
|
}
|
|
2790
3089
|
} else {
|
|
2791
3090
|
res.end();
|
|
@@ -2824,10 +3123,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2824
3123
|
return;
|
|
2825
3124
|
}
|
|
2826
3125
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
2827
|
-
if (!config.hooks?.oauth_callback) {
|
|
2828
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
2829
|
-
return;
|
|
2830
|
-
}
|
|
2831
3126
|
let parsedBody;
|
|
2832
3127
|
try {
|
|
2833
3128
|
parsedBody = await parseJSONBody(req);
|
|
@@ -2838,50 +3133,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2838
3133
|
});
|
|
2839
3134
|
return;
|
|
2840
3135
|
}
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
2844
|
-
sendJSON(res, 400, {
|
|
2845
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
2846
|
-
});
|
|
2847
|
-
return;
|
|
2848
|
-
}
|
|
2849
|
-
const invocation = parsedBody.invocation;
|
|
2850
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
2851
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
2852
|
-
const oauthCallbackContext = {
|
|
2853
|
-
request: oauthRequest,
|
|
2854
|
-
invocation,
|
|
2855
|
-
log: createContextLogger()
|
|
2856
|
-
};
|
|
2857
|
-
try {
|
|
2858
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
2859
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
2860
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
2861
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
2862
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
2863
|
-
});
|
|
2864
|
-
});
|
|
2865
|
-
sendJSON(res, 200, {
|
|
2866
|
-
appInstallationId: result.appInstallationId,
|
|
2867
|
-
env: result.env ?? {}
|
|
2868
|
-
});
|
|
2869
|
-
} catch (err) {
|
|
2870
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
2871
|
-
sendJSON(res, 500, {
|
|
2872
|
-
error: {
|
|
2873
|
-
code: -32603,
|
|
2874
|
-
message: errorMessage
|
|
2875
|
-
}
|
|
2876
|
-
});
|
|
2877
|
-
}
|
|
3136
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3137
|
+
sendJSON(res, result.status, result.body);
|
|
2878
3138
|
return;
|
|
2879
3139
|
}
|
|
2880
3140
|
if (pathname === "/install" && req.method === "POST") {
|
|
2881
|
-
if (!config.hooks?.install) {
|
|
2882
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
2883
|
-
return;
|
|
2884
|
-
}
|
|
2885
3141
|
let installBody;
|
|
2886
3142
|
try {
|
|
2887
3143
|
installBody = await parseJSONBody(req);
|
|
@@ -2891,61 +3147,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2891
3147
|
});
|
|
2892
3148
|
return;
|
|
2893
3149
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
2897
|
-
});
|
|
2898
|
-
return;
|
|
2899
|
-
}
|
|
2900
|
-
const installContext = {
|
|
2901
|
-
env: installBody.env ?? {},
|
|
2902
|
-
workplace: installBody.context.workplace,
|
|
2903
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
2904
|
-
app: installBody.context.app,
|
|
2905
|
-
invocation: installBody.invocation,
|
|
2906
|
-
log: createContextLogger()
|
|
2907
|
-
};
|
|
2908
|
-
const installRequestConfig = {
|
|
2909
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2910
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2911
|
-
};
|
|
2912
|
-
try {
|
|
2913
|
-
const installHook = config.hooks.install;
|
|
2914
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2915
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
2916
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
2917
|
-
return await installHandler(installContext);
|
|
2918
|
-
});
|
|
2919
|
-
});
|
|
2920
|
-
sendJSON(res, 200, {
|
|
2921
|
-
env: result.env ?? {},
|
|
2922
|
-
redirect: result.redirect
|
|
2923
|
-
});
|
|
2924
|
-
} catch (err) {
|
|
2925
|
-
if (err instanceof InstallError) {
|
|
2926
|
-
sendJSON(res, 400, {
|
|
2927
|
-
error: {
|
|
2928
|
-
code: err.code,
|
|
2929
|
-
message: err.message,
|
|
2930
|
-
field: err.field
|
|
2931
|
-
}
|
|
2932
|
-
});
|
|
2933
|
-
} else {
|
|
2934
|
-
sendJSON(res, 500, {
|
|
2935
|
-
error: {
|
|
2936
|
-
code: -32603,
|
|
2937
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2938
|
-
}
|
|
2939
|
-
});
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
3150
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3151
|
+
sendJSON(res, result.status, result.body);
|
|
2942
3152
|
return;
|
|
2943
3153
|
}
|
|
2944
3154
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
2945
|
-
if (!config.hooks?.uninstall) {
|
|
2946
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
2947
|
-
return;
|
|
2948
|
-
}
|
|
2949
3155
|
let uninstallBody;
|
|
2950
3156
|
try {
|
|
2951
3157
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -2955,53 +3161,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2955
3161
|
});
|
|
2956
3162
|
return;
|
|
2957
3163
|
}
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
error: {
|
|
2961
|
-
code: -32602,
|
|
2962
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
2963
|
-
}
|
|
2964
|
-
});
|
|
2965
|
-
return;
|
|
2966
|
-
}
|
|
2967
|
-
const uninstallContext = {
|
|
2968
|
-
env: uninstallBody.env ?? {},
|
|
2969
|
-
workplace: uninstallBody.context.workplace,
|
|
2970
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
2971
|
-
app: uninstallBody.context.app,
|
|
2972
|
-
invocation: uninstallBody.invocation,
|
|
2973
|
-
log: createContextLogger()
|
|
2974
|
-
};
|
|
2975
|
-
const uninstallRequestConfig = {
|
|
2976
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2977
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2978
|
-
};
|
|
2979
|
-
try {
|
|
2980
|
-
const uninstallHook = config.hooks.uninstall;
|
|
2981
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
2982
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
2983
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
2984
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
2985
|
-
});
|
|
2986
|
-
});
|
|
2987
|
-
sendJSON(res, 200, {
|
|
2988
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
2989
|
-
});
|
|
2990
|
-
} catch (err) {
|
|
2991
|
-
sendJSON(res, 500, {
|
|
2992
|
-
error: {
|
|
2993
|
-
code: -32603,
|
|
2994
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
2995
|
-
}
|
|
2996
|
-
});
|
|
2997
|
-
}
|
|
3164
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3165
|
+
sendJSON(res, result.status, result.body);
|
|
2998
3166
|
return;
|
|
2999
3167
|
}
|
|
3000
3168
|
if (pathname === "/provision" && req.method === "POST") {
|
|
3001
|
-
if (!config.hooks?.provision) {
|
|
3002
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
3003
|
-
return;
|
|
3004
|
-
}
|
|
3005
3169
|
let provisionBody;
|
|
3006
3170
|
try {
|
|
3007
3171
|
provisionBody = await parseJSONBody(req);
|
|
@@ -3011,46 +3175,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3011
3175
|
});
|
|
3012
3176
|
return;
|
|
3013
3177
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
3017
|
-
});
|
|
3018
|
-
return;
|
|
3019
|
-
}
|
|
3020
|
-
const mergedEnv = {};
|
|
3021
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3022
|
-
if (value !== void 0) {
|
|
3023
|
-
mergedEnv[key] = value;
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3027
|
-
const provisionContext = {
|
|
3028
|
-
env: mergedEnv,
|
|
3029
|
-
app: provisionBody.context.app,
|
|
3030
|
-
invocation: provisionBody.invocation,
|
|
3031
|
-
log: createContextLogger()
|
|
3032
|
-
};
|
|
3033
|
-
const provisionRequestConfig = {
|
|
3034
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3035
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3036
|
-
};
|
|
3037
|
-
try {
|
|
3038
|
-
const provisionHook = config.hooks.provision;
|
|
3039
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3040
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
3041
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
3042
|
-
return await provisionHandler(provisionContext);
|
|
3043
|
-
});
|
|
3044
|
-
});
|
|
3045
|
-
sendJSON(res, 200, result);
|
|
3046
|
-
} catch (err) {
|
|
3047
|
-
sendJSON(res, 500, {
|
|
3048
|
-
error: {
|
|
3049
|
-
code: -32603,
|
|
3050
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3051
|
-
}
|
|
3052
|
-
});
|
|
3053
|
-
}
|
|
3178
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3179
|
+
sendJSON(res, result.status, result.body);
|
|
3054
3180
|
return;
|
|
3055
3181
|
}
|
|
3056
3182
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -3231,12 +3357,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3231
3357
|
}
|
|
3232
3358
|
if (path2.startsWith("/webhooks/") && webhookRegistry) {
|
|
3233
3359
|
const handle = path2.slice("/webhooks/".length);
|
|
3234
|
-
|
|
3235
|
-
if (!webhookDef) {
|
|
3360
|
+
if (!webhookRegistry[handle]) {
|
|
3236
3361
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
3237
3362
|
}
|
|
3238
|
-
|
|
3239
|
-
if (!allowedMethods.includes(method)) {
|
|
3363
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
3240
3364
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
3241
3365
|
}
|
|
3242
3366
|
const rawBody = event.body ?? "";
|
|
@@ -3251,107 +3375,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3251
3375
|
} else {
|
|
3252
3376
|
parsedBody = rawBody;
|
|
3253
3377
|
}
|
|
3254
|
-
const
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
method: envelope.request.method,
|
|
3273
|
-
url: envelope.request.url,
|
|
3274
|
-
path: envelope.request.path,
|
|
3275
|
-
headers: envelope.request.headers,
|
|
3276
|
-
query: envelope.request.query,
|
|
3277
|
-
body: originalParsedBody,
|
|
3278
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
3279
|
-
};
|
|
3280
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
3281
|
-
const app = envelope.context.app;
|
|
3282
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
3283
|
-
webhookContext = {
|
|
3284
|
-
env: envVars,
|
|
3285
|
-
app,
|
|
3286
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
3287
|
-
workplace: envelope.context.workplace,
|
|
3288
|
-
registration: envelope.context.registration ?? {},
|
|
3289
|
-
invocation,
|
|
3290
|
-
log: createContextLogger()
|
|
3291
|
-
};
|
|
3292
|
-
} else {
|
|
3293
|
-
webhookContext = {
|
|
3294
|
-
env: envVars,
|
|
3295
|
-
app,
|
|
3296
|
-
invocation,
|
|
3297
|
-
log: createContextLogger()
|
|
3298
|
-
};
|
|
3299
|
-
}
|
|
3300
|
-
} else {
|
|
3301
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
3302
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
3303
|
-
if (!appId || !appVersionId) {
|
|
3304
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
3305
|
-
}
|
|
3306
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3307
|
-
const protocol = forwardedProto ?? "https";
|
|
3308
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3309
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3310
|
-
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3311
|
-
webhookRequest = {
|
|
3312
|
-
method,
|
|
3313
|
-
url: webhookUrl,
|
|
3314
|
-
path: path2,
|
|
3315
|
-
headers: event.headers,
|
|
3316
|
-
query: event.queryStringParameters ?? {},
|
|
3317
|
-
body: parsedBody,
|
|
3318
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3319
|
-
};
|
|
3320
|
-
webhookContext = {
|
|
3321
|
-
env: process.env,
|
|
3322
|
-
app: { id: appId, versionId: appVersionId },
|
|
3323
|
-
log: createContextLogger()
|
|
3324
|
-
};
|
|
3325
|
-
}
|
|
3326
|
-
const originalEnv = { ...process.env };
|
|
3327
|
-
Object.assign(process.env, requestEnv);
|
|
3328
|
-
const requestConfig = {
|
|
3329
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3330
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3331
|
-
};
|
|
3332
|
-
let webhookResponse;
|
|
3333
|
-
try {
|
|
3334
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
3335
|
-
return await runWithConfig(requestConfig, async () => {
|
|
3336
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
3337
|
-
});
|
|
3338
|
-
});
|
|
3339
|
-
} catch (err) {
|
|
3340
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3341
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
3342
|
-
} finally {
|
|
3343
|
-
process.env = originalEnv;
|
|
3378
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3379
|
+
const protocol = forwardedProto ?? "https";
|
|
3380
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3381
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3382
|
+
const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
|
|
3383
|
+
const parseResult = parseWebhookRequest(
|
|
3384
|
+
parsedBody,
|
|
3385
|
+
method,
|
|
3386
|
+
webhookUrl,
|
|
3387
|
+
path2,
|
|
3388
|
+
event.headers,
|
|
3389
|
+
event.queryStringParameters ?? {},
|
|
3390
|
+
rawBody,
|
|
3391
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
3392
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
3393
|
+
);
|
|
3394
|
+
if ("error" in parseResult) {
|
|
3395
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
3344
3396
|
}
|
|
3397
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
3345
3398
|
const responseHeaders = {
|
|
3346
3399
|
...headers,
|
|
3347
|
-
...
|
|
3400
|
+
...result.headers
|
|
3348
3401
|
};
|
|
3349
|
-
const status = webhookResponse.status ?? 200;
|
|
3350
|
-
const body = webhookResponse.body;
|
|
3351
3402
|
return {
|
|
3352
|
-
statusCode: status,
|
|
3403
|
+
statusCode: result.status,
|
|
3353
3404
|
headers: responseHeaders,
|
|
3354
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
3405
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
3355
3406
|
};
|
|
3356
3407
|
}
|
|
3357
3408
|
if (path2 === "/core" && method === "POST") {
|
|
@@ -3487,9 +3538,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3487
3538
|
}
|
|
3488
3539
|
}
|
|
3489
3540
|
if (path2 === "/install" && method === "POST") {
|
|
3490
|
-
if (!config.hooks?.install) {
|
|
3491
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
3492
|
-
}
|
|
3493
3541
|
let installBody;
|
|
3494
3542
|
try {
|
|
3495
3543
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3500,68 +3548,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3500
3548
|
headers
|
|
3501
3549
|
);
|
|
3502
3550
|
}
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
400,
|
|
3506
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
3507
|
-
headers
|
|
3508
|
-
);
|
|
3509
|
-
}
|
|
3510
|
-
const installContext = {
|
|
3511
|
-
env: installBody.env ?? {},
|
|
3512
|
-
workplace: installBody.context.workplace,
|
|
3513
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
3514
|
-
app: installBody.context.app,
|
|
3515
|
-
invocation: installBody.invocation,
|
|
3516
|
-
log: createContextLogger()
|
|
3517
|
-
};
|
|
3518
|
-
const installRequestConfig = {
|
|
3519
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3520
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3521
|
-
};
|
|
3522
|
-
try {
|
|
3523
|
-
const installHook = config.hooks.install;
|
|
3524
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
3525
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
3526
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
3527
|
-
return await installHandler(installContext);
|
|
3528
|
-
});
|
|
3529
|
-
});
|
|
3530
|
-
return createResponse(
|
|
3531
|
-
200,
|
|
3532
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
3533
|
-
headers
|
|
3534
|
-
);
|
|
3535
|
-
} catch (err) {
|
|
3536
|
-
if (err instanceof InstallError) {
|
|
3537
|
-
return createResponse(
|
|
3538
|
-
400,
|
|
3539
|
-
{
|
|
3540
|
-
error: {
|
|
3541
|
-
code: err.code,
|
|
3542
|
-
message: err.message,
|
|
3543
|
-
field: err.field
|
|
3544
|
-
}
|
|
3545
|
-
},
|
|
3546
|
-
headers
|
|
3547
|
-
);
|
|
3548
|
-
}
|
|
3549
|
-
return createResponse(
|
|
3550
|
-
500,
|
|
3551
|
-
{
|
|
3552
|
-
error: {
|
|
3553
|
-
code: -32603,
|
|
3554
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3555
|
-
}
|
|
3556
|
-
},
|
|
3557
|
-
headers
|
|
3558
|
-
);
|
|
3559
|
-
}
|
|
3551
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3552
|
+
return createResponse(result.status, result.body, headers);
|
|
3560
3553
|
}
|
|
3561
3554
|
if (path2 === "/uninstall" && method === "POST") {
|
|
3562
|
-
if (!config.hooks?.uninstall) {
|
|
3563
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
3564
|
-
}
|
|
3565
3555
|
let uninstallBody;
|
|
3566
3556
|
try {
|
|
3567
3557
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3572,137 +3562,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3572
3562
|
headers
|
|
3573
3563
|
);
|
|
3574
3564
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
400,
|
|
3578
|
-
{
|
|
3579
|
-
error: {
|
|
3580
|
-
code: -32602,
|
|
3581
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3582
|
-
}
|
|
3583
|
-
},
|
|
3584
|
-
headers
|
|
3585
|
-
);
|
|
3586
|
-
}
|
|
3587
|
-
const uninstallContext = {
|
|
3588
|
-
env: uninstallBody.env ?? {},
|
|
3589
|
-
workplace: uninstallBody.context.workplace,
|
|
3590
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
3591
|
-
app: uninstallBody.context.app,
|
|
3592
|
-
invocation: uninstallBody.invocation,
|
|
3593
|
-
log: createContextLogger()
|
|
3594
|
-
};
|
|
3595
|
-
const uninstallRequestConfig = {
|
|
3596
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3597
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3598
|
-
};
|
|
3599
|
-
try {
|
|
3600
|
-
const uninstallHook = config.hooks.uninstall;
|
|
3601
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3602
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
3603
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
3604
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
3605
|
-
});
|
|
3606
|
-
});
|
|
3607
|
-
return createResponse(
|
|
3608
|
-
200,
|
|
3609
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
3610
|
-
headers
|
|
3611
|
-
);
|
|
3612
|
-
} catch (err) {
|
|
3613
|
-
return createResponse(
|
|
3614
|
-
500,
|
|
3615
|
-
{
|
|
3616
|
-
error: {
|
|
3617
|
-
code: -32603,
|
|
3618
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3619
|
-
}
|
|
3620
|
-
},
|
|
3621
|
-
headers
|
|
3622
|
-
);
|
|
3623
|
-
}
|
|
3565
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3566
|
+
return createResponse(result.status, result.body, headers);
|
|
3624
3567
|
}
|
|
3625
3568
|
if (path2 === "/provision" && method === "POST") {
|
|
3626
|
-
console.log("[serverless] /provision endpoint called");
|
|
3627
|
-
if (!config.hooks?.provision) {
|
|
3628
|
-
console.log("[serverless] No provision handler configured");
|
|
3629
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
3630
|
-
}
|
|
3631
3569
|
let provisionBody;
|
|
3632
3570
|
try {
|
|
3633
3571
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
3634
|
-
console.log("[serverless] Provision body parsed:", {
|
|
3635
|
-
hasEnv: !!provisionBody.env,
|
|
3636
|
-
hasContext: !!provisionBody.context,
|
|
3637
|
-
appId: provisionBody.context?.app?.id,
|
|
3638
|
-
versionId: provisionBody.context?.app?.versionId
|
|
3639
|
-
});
|
|
3640
3572
|
} catch {
|
|
3641
|
-
console.log("[serverless] Failed to parse provision body");
|
|
3642
3573
|
return createResponse(
|
|
3643
3574
|
400,
|
|
3644
3575
|
{ error: { code: -32700, message: "Parse error" } },
|
|
3645
3576
|
headers
|
|
3646
3577
|
);
|
|
3647
3578
|
}
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
return createResponse(
|
|
3651
|
-
400,
|
|
3652
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
3653
|
-
headers
|
|
3654
|
-
);
|
|
3655
|
-
}
|
|
3656
|
-
const mergedEnv = {};
|
|
3657
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3658
|
-
if (value !== void 0) {
|
|
3659
|
-
mergedEnv[key] = value;
|
|
3660
|
-
}
|
|
3661
|
-
}
|
|
3662
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3663
|
-
const provisionContext = {
|
|
3664
|
-
env: mergedEnv,
|
|
3665
|
-
app: provisionBody.context.app,
|
|
3666
|
-
invocation: provisionBody.invocation,
|
|
3667
|
-
log: createContextLogger()
|
|
3668
|
-
};
|
|
3669
|
-
const provisionRequestConfig = {
|
|
3670
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3671
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3672
|
-
};
|
|
3673
|
-
console.log("[serverless] Calling provision handler...");
|
|
3674
|
-
try {
|
|
3675
|
-
const provisionHook = config.hooks.provision;
|
|
3676
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3677
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
3678
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
3679
|
-
return await provisionHandler(provisionContext);
|
|
3680
|
-
});
|
|
3681
|
-
});
|
|
3682
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
3683
|
-
return createResponse(200, result, headers);
|
|
3684
|
-
} catch (err) {
|
|
3685
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
3686
|
-
return createResponse(
|
|
3687
|
-
500,
|
|
3688
|
-
{
|
|
3689
|
-
error: {
|
|
3690
|
-
code: -32603,
|
|
3691
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3692
|
-
}
|
|
3693
|
-
},
|
|
3694
|
-
headers
|
|
3695
|
-
);
|
|
3696
|
-
}
|
|
3579
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3580
|
+
return createResponse(result.status, result.body, headers);
|
|
3697
3581
|
}
|
|
3698
3582
|
if (path2 === "/oauth_callback" && method === "POST") {
|
|
3699
|
-
if (!config.hooks?.oauth_callback) {
|
|
3700
|
-
return createResponse(
|
|
3701
|
-
404,
|
|
3702
|
-
{ error: "OAuth callback handler not configured" },
|
|
3703
|
-
headers
|
|
3704
|
-
);
|
|
3705
|
-
}
|
|
3706
3583
|
let parsedBody;
|
|
3707
3584
|
try {
|
|
3708
3585
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3714,52 +3591,8 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3714
3591
|
headers
|
|
3715
3592
|
);
|
|
3716
3593
|
}
|
|
3717
|
-
const
|
|
3718
|
-
|
|
3719
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
3720
|
-
return createResponse(
|
|
3721
|
-
400,
|
|
3722
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
3723
|
-
headers
|
|
3724
|
-
);
|
|
3725
|
-
}
|
|
3726
|
-
const invocation = parsedBody.invocation;
|
|
3727
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
3728
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
3729
|
-
const oauthCallbackContext = {
|
|
3730
|
-
request: oauthRequest,
|
|
3731
|
-
invocation,
|
|
3732
|
-
log: createContextLogger()
|
|
3733
|
-
};
|
|
3734
|
-
try {
|
|
3735
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
3736
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
3737
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
3738
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
3739
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
3740
|
-
});
|
|
3741
|
-
});
|
|
3742
|
-
return createResponse(
|
|
3743
|
-
200,
|
|
3744
|
-
{
|
|
3745
|
-
appInstallationId: result.appInstallationId,
|
|
3746
|
-
env: result.env ?? {}
|
|
3747
|
-
},
|
|
3748
|
-
headers
|
|
3749
|
-
);
|
|
3750
|
-
} catch (err) {
|
|
3751
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
3752
|
-
return createResponse(
|
|
3753
|
-
500,
|
|
3754
|
-
{
|
|
3755
|
-
error: {
|
|
3756
|
-
code: -32603,
|
|
3757
|
-
message: errorMessage
|
|
3758
|
-
}
|
|
3759
|
-
},
|
|
3760
|
-
headers
|
|
3761
|
-
);
|
|
3762
|
-
}
|
|
3594
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3595
|
+
return createResponse(result.status, result.body, headers);
|
|
3763
3596
|
}
|
|
3764
3597
|
if (path2 === "/health" && method === "GET") {
|
|
3765
3598
|
return createResponse(200, state.getHealthStatus(), headers);
|