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/cli/index.js
CHANGED
|
@@ -2574,8 +2574,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
2574
2574
|
};
|
|
2575
2575
|
}
|
|
2576
2576
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
2577
|
-
return async function callTool(
|
|
2578
|
-
const toolName = String(
|
|
2577
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
2578
|
+
const toolName = String(toolNameInput);
|
|
2579
2579
|
const tool = registry[toolName];
|
|
2580
2580
|
if (!tool) {
|
|
2581
2581
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -2584,7 +2584,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
2584
2584
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
2585
2585
|
}
|
|
2586
2586
|
const fn = tool.handler;
|
|
2587
|
-
const args2 =
|
|
2587
|
+
const args2 = toolArgsInput ?? {};
|
|
2588
2588
|
const estimateMode = args2.estimate === true;
|
|
2589
2589
|
if (!estimateMode) {
|
|
2590
2590
|
state.incrementRequestCount();
|
|
@@ -2839,51 +2839,6 @@ async function handleCoreMethod(method, params) {
|
|
|
2839
2839
|
};
|
|
2840
2840
|
}
|
|
2841
2841
|
|
|
2842
|
-
// src/server/handler-helpers.ts
|
|
2843
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
2844
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
2845
|
-
return null;
|
|
2846
|
-
}
|
|
2847
|
-
const envelope = parsedBody;
|
|
2848
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
2849
|
-
return null;
|
|
2850
|
-
}
|
|
2851
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
2852
|
-
return null;
|
|
2853
|
-
}
|
|
2854
|
-
return {
|
|
2855
|
-
env: envelope.env,
|
|
2856
|
-
request: envelope.request,
|
|
2857
|
-
context: envelope.context
|
|
2858
|
-
};
|
|
2859
|
-
}
|
|
2860
|
-
function buildRequestFromRaw(raw) {
|
|
2861
|
-
let parsedBody = raw.body;
|
|
2862
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
2863
|
-
if (contentType.includes("application/json")) {
|
|
2864
|
-
try {
|
|
2865
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
2866
|
-
} catch {
|
|
2867
|
-
parsedBody = raw.body;
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
return {
|
|
2871
|
-
method: raw.method,
|
|
2872
|
-
url: raw.url,
|
|
2873
|
-
path: raw.path,
|
|
2874
|
-
headers: raw.headers,
|
|
2875
|
-
query: raw.query,
|
|
2876
|
-
body: parsedBody,
|
|
2877
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
function buildRequestScopedConfig(env) {
|
|
2881
|
-
return {
|
|
2882
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2883
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2884
|
-
};
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
2842
|
// src/server/startup-logger.ts
|
|
2888
2843
|
function padEnd(str, length) {
|
|
2889
2844
|
if (str.length >= length) {
|
|
@@ -2981,6 +2936,405 @@ function isProvisionConfig(value) {
|
|
|
2981
2936
|
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
2982
2937
|
}
|
|
2983
2938
|
|
|
2939
|
+
// src/server/handlers/install-handler.ts
|
|
2940
|
+
async function handleInstall(body, hooks) {
|
|
2941
|
+
if (!hooks?.install) {
|
|
2942
|
+
return {
|
|
2943
|
+
status: 404,
|
|
2944
|
+
body: { error: "Install handler not configured" }
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
2948
|
+
return {
|
|
2949
|
+
status: 400,
|
|
2950
|
+
body: {
|
|
2951
|
+
error: {
|
|
2952
|
+
code: -32602,
|
|
2953
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
const installContext = {
|
|
2959
|
+
env: body.env ?? {},
|
|
2960
|
+
workplace: body.context.workplace,
|
|
2961
|
+
appInstallationId: body.context.appInstallationId,
|
|
2962
|
+
app: body.context.app,
|
|
2963
|
+
invocation: body.invocation,
|
|
2964
|
+
log: createContextLogger()
|
|
2965
|
+
};
|
|
2966
|
+
const requestConfig = {
|
|
2967
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2968
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2969
|
+
};
|
|
2970
|
+
try {
|
|
2971
|
+
const installHook = hooks.install;
|
|
2972
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
2973
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
2974
|
+
return await runWithConfig(requestConfig, async () => {
|
|
2975
|
+
return await installHandler(installContext);
|
|
2976
|
+
});
|
|
2977
|
+
});
|
|
2978
|
+
return {
|
|
2979
|
+
status: 200,
|
|
2980
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
2981
|
+
};
|
|
2982
|
+
} catch (err) {
|
|
2983
|
+
if (err instanceof InstallError) {
|
|
2984
|
+
return {
|
|
2985
|
+
status: 400,
|
|
2986
|
+
body: {
|
|
2987
|
+
error: {
|
|
2988
|
+
code: err.code,
|
|
2989
|
+
message: err.message,
|
|
2990
|
+
field: err.field
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
return {
|
|
2996
|
+
status: 500,
|
|
2997
|
+
body: {
|
|
2998
|
+
error: {
|
|
2999
|
+
code: -32603,
|
|
3000
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/server/handlers/uninstall-handler.ts
|
|
3008
|
+
async function handleUninstall(body, hooks) {
|
|
3009
|
+
if (!hooks?.uninstall) {
|
|
3010
|
+
return {
|
|
3011
|
+
status: 404,
|
|
3012
|
+
body: { error: "Uninstall handler not configured" }
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
3016
|
+
return {
|
|
3017
|
+
status: 400,
|
|
3018
|
+
body: {
|
|
3019
|
+
error: {
|
|
3020
|
+
code: -32602,
|
|
3021
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
const uninstallContext = {
|
|
3027
|
+
env: body.env ?? {},
|
|
3028
|
+
workplace: body.context.workplace,
|
|
3029
|
+
appInstallationId: body.context.appInstallationId,
|
|
3030
|
+
app: body.context.app,
|
|
3031
|
+
invocation: body.invocation,
|
|
3032
|
+
log: createContextLogger()
|
|
3033
|
+
};
|
|
3034
|
+
const requestConfig = {
|
|
3035
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3036
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3037
|
+
};
|
|
3038
|
+
try {
|
|
3039
|
+
const uninstallHook = hooks.uninstall;
|
|
3040
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3041
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
3042
|
+
return await runWithConfig(requestConfig, async () => {
|
|
3043
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
3044
|
+
});
|
|
3045
|
+
});
|
|
3046
|
+
return {
|
|
3047
|
+
status: 200,
|
|
3048
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
3049
|
+
};
|
|
3050
|
+
} catch (err) {
|
|
3051
|
+
return {
|
|
3052
|
+
status: 500,
|
|
3053
|
+
body: {
|
|
3054
|
+
error: {
|
|
3055
|
+
code: -32603,
|
|
3056
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
// src/server/handlers/provision-handler.ts
|
|
3064
|
+
async function handleProvision(body, hooks) {
|
|
3065
|
+
if (!hooks?.provision) {
|
|
3066
|
+
return {
|
|
3067
|
+
status: 404,
|
|
3068
|
+
body: { error: "Provision handler not configured" }
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
if (!body.context?.app) {
|
|
3072
|
+
return {
|
|
3073
|
+
status: 400,
|
|
3074
|
+
body: {
|
|
3075
|
+
error: {
|
|
3076
|
+
code: -32602,
|
|
3077
|
+
message: "Missing context (app required)"
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
const mergedEnv = {};
|
|
3083
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
3084
|
+
if (value !== void 0) {
|
|
3085
|
+
mergedEnv[key] = value;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
3089
|
+
const provisionContext = {
|
|
3090
|
+
env: mergedEnv,
|
|
3091
|
+
app: body.context.app,
|
|
3092
|
+
invocation: body.invocation,
|
|
3093
|
+
log: createContextLogger()
|
|
3094
|
+
};
|
|
3095
|
+
const requestConfig = {
|
|
3096
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3097
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3098
|
+
};
|
|
3099
|
+
try {
|
|
3100
|
+
const provisionHook = hooks.provision;
|
|
3101
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3102
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
3103
|
+
return await runWithConfig(requestConfig, async () => {
|
|
3104
|
+
return await provisionHandler(provisionContext);
|
|
3105
|
+
});
|
|
3106
|
+
});
|
|
3107
|
+
return {
|
|
3108
|
+
status: 200,
|
|
3109
|
+
body: result
|
|
3110
|
+
};
|
|
3111
|
+
} catch (err) {
|
|
3112
|
+
return {
|
|
3113
|
+
status: 500,
|
|
3114
|
+
body: {
|
|
3115
|
+
error: {
|
|
3116
|
+
code: -32603,
|
|
3117
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
// src/server/handler-helpers.ts
|
|
3125
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
3126
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
const envelope = parsedBody;
|
|
3130
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
return {
|
|
3137
|
+
env: envelope.env,
|
|
3138
|
+
request: envelope.request,
|
|
3139
|
+
context: envelope.context
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
function buildRequestFromRaw(raw) {
|
|
3143
|
+
let parsedBody = raw.body;
|
|
3144
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
3145
|
+
if (contentType.includes("application/json")) {
|
|
3146
|
+
try {
|
|
3147
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
3148
|
+
} catch {
|
|
3149
|
+
parsedBody = raw.body;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
return {
|
|
3153
|
+
method: raw.method,
|
|
3154
|
+
url: raw.url,
|
|
3155
|
+
path: raw.path,
|
|
3156
|
+
headers: raw.headers,
|
|
3157
|
+
query: raw.query,
|
|
3158
|
+
body: parsedBody,
|
|
3159
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
function buildRequestScopedConfig(env) {
|
|
3163
|
+
return {
|
|
3164
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3165
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
3170
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
3171
|
+
if (!hooks?.oauth_callback) {
|
|
3172
|
+
return {
|
|
3173
|
+
status: 404,
|
|
3174
|
+
body: { error: "OAuth callback handler not configured" }
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
3178
|
+
if (!envelope) {
|
|
3179
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
3180
|
+
return {
|
|
3181
|
+
status: 400,
|
|
3182
|
+
body: {
|
|
3183
|
+
error: {
|
|
3184
|
+
code: -32602,
|
|
3185
|
+
message: "Missing envelope format: expected { env, request }"
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
const invocation = parsedBody.invocation;
|
|
3191
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
3192
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
3193
|
+
const oauthCallbackContext = {
|
|
3194
|
+
request: oauthRequest,
|
|
3195
|
+
invocation,
|
|
3196
|
+
log: createContextLogger()
|
|
3197
|
+
};
|
|
3198
|
+
try {
|
|
3199
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
3200
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
3201
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
3202
|
+
return await runWithConfig(requestConfig, async () => {
|
|
3203
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
3204
|
+
});
|
|
3205
|
+
});
|
|
3206
|
+
return {
|
|
3207
|
+
status: 200,
|
|
3208
|
+
body: {
|
|
3209
|
+
appInstallationId: result.appInstallationId,
|
|
3210
|
+
env: result.env ?? {}
|
|
3211
|
+
}
|
|
3212
|
+
};
|
|
3213
|
+
} catch (err) {
|
|
3214
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
3215
|
+
return {
|
|
3216
|
+
status: 500,
|
|
3217
|
+
body: {
|
|
3218
|
+
error: {
|
|
3219
|
+
code: -32603,
|
|
3220
|
+
message: errorMessage
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/server/handlers/webhook-handler.ts
|
|
3228
|
+
function parseWebhookRequest(parsedBody, method, url, path14, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
3229
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
3230
|
+
if (isEnvelope) {
|
|
3231
|
+
const envelope = parsedBody;
|
|
3232
|
+
const requestEnv = envelope.env ?? {};
|
|
3233
|
+
const invocation = envelope.invocation;
|
|
3234
|
+
let originalParsedBody = envelope.request.body;
|
|
3235
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
3236
|
+
if (originalContentType.includes("application/json")) {
|
|
3237
|
+
try {
|
|
3238
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
const webhookRequest2 = {
|
|
3243
|
+
method: envelope.request.method,
|
|
3244
|
+
url: envelope.request.url,
|
|
3245
|
+
path: envelope.request.path,
|
|
3246
|
+
headers: envelope.request.headers,
|
|
3247
|
+
query: envelope.request.query,
|
|
3248
|
+
body: originalParsedBody,
|
|
3249
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
3250
|
+
};
|
|
3251
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
3252
|
+
const app = envelope.context.app;
|
|
3253
|
+
let webhookContext2;
|
|
3254
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
3255
|
+
webhookContext2 = {
|
|
3256
|
+
env: envVars,
|
|
3257
|
+
app,
|
|
3258
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
3259
|
+
workplace: envelope.context.workplace,
|
|
3260
|
+
registration: envelope.context.registration ?? {},
|
|
3261
|
+
invocation,
|
|
3262
|
+
log: createContextLogger()
|
|
3263
|
+
};
|
|
3264
|
+
} else {
|
|
3265
|
+
webhookContext2 = {
|
|
3266
|
+
env: envVars,
|
|
3267
|
+
app,
|
|
3268
|
+
invocation,
|
|
3269
|
+
log: createContextLogger()
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
3273
|
+
}
|
|
3274
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
3275
|
+
return {
|
|
3276
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
const webhookRequest = {
|
|
3280
|
+
method,
|
|
3281
|
+
url,
|
|
3282
|
+
path: path14,
|
|
3283
|
+
headers,
|
|
3284
|
+
query,
|
|
3285
|
+
body: parsedBody,
|
|
3286
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3287
|
+
};
|
|
3288
|
+
const webhookContext = {
|
|
3289
|
+
env: process.env,
|
|
3290
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
3291
|
+
log: createContextLogger()
|
|
3292
|
+
};
|
|
3293
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
3294
|
+
}
|
|
3295
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
3296
|
+
const webhookDef = webhookRegistry[handle];
|
|
3297
|
+
if (!webhookDef) {
|
|
3298
|
+
return {
|
|
3299
|
+
status: 404,
|
|
3300
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
const originalEnv = { ...process.env };
|
|
3304
|
+
Object.assign(process.env, data.requestEnv);
|
|
3305
|
+
const requestConfig = {
|
|
3306
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3307
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3308
|
+
};
|
|
3309
|
+
let webhookResponse;
|
|
3310
|
+
try {
|
|
3311
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
3312
|
+
return await runWithConfig(requestConfig, async () => {
|
|
3313
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
3314
|
+
});
|
|
3315
|
+
});
|
|
3316
|
+
} catch (err) {
|
|
3317
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3318
|
+
return {
|
|
3319
|
+
status: 500,
|
|
3320
|
+
body: { error: "Webhook handler error" }
|
|
3321
|
+
};
|
|
3322
|
+
} finally {
|
|
3323
|
+
process.env = originalEnv;
|
|
3324
|
+
}
|
|
3325
|
+
return {
|
|
3326
|
+
status: webhookResponse.status ?? 200,
|
|
3327
|
+
body: webhookResponse.body,
|
|
3328
|
+
headers: webhookResponse.headers
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
3332
|
+
const webhookDef = webhookRegistry[handle];
|
|
3333
|
+
if (!webhookDef) return false;
|
|
3334
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
3335
|
+
return allowedMethods.includes(method);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
2984
3338
|
// src/server/dedicated.ts
|
|
2985
3339
|
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
2986
3340
|
const port = getListeningPort(config);
|
|
@@ -3007,13 +3361,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3007
3361
|
}
|
|
3008
3362
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
3009
3363
|
const handle = pathname.slice("/webhooks/".length);
|
|
3010
|
-
|
|
3011
|
-
if (!webhookDef) {
|
|
3364
|
+
if (!webhookRegistry[handle]) {
|
|
3012
3365
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
3013
3366
|
return;
|
|
3014
3367
|
}
|
|
3015
|
-
|
|
3016
|
-
if (!allowedMethods.includes(req.method)) {
|
|
3368
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
3017
3369
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
3018
3370
|
return;
|
|
3019
3371
|
}
|
|
@@ -3035,87 +3387,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3035
3387
|
} else {
|
|
3036
3388
|
parsedBody = rawBody;
|
|
3037
3389
|
}
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
webhookContext = {
|
|
3052
|
-
env: envVars,
|
|
3053
|
-
app,
|
|
3054
|
-
appInstallationId: context.appInstallationId,
|
|
3055
|
-
workplace: context.workplace,
|
|
3056
|
-
registration: context.registration ?? {},
|
|
3057
|
-
invocation,
|
|
3058
|
-
log: createContextLogger()
|
|
3059
|
-
};
|
|
3060
|
-
} else {
|
|
3061
|
-
webhookContext = {
|
|
3062
|
-
env: envVars,
|
|
3063
|
-
app,
|
|
3064
|
-
invocation,
|
|
3065
|
-
log: createContextLogger()
|
|
3066
|
-
};
|
|
3067
|
-
}
|
|
3068
|
-
} else {
|
|
3069
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
3070
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
3071
|
-
if (!appId || !appVersionId) {
|
|
3072
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
3073
|
-
}
|
|
3074
|
-
webhookRequest = {
|
|
3075
|
-
method: req.method ?? "POST",
|
|
3076
|
-
url: url.toString(),
|
|
3077
|
-
path: pathname,
|
|
3078
|
-
headers: req.headers,
|
|
3079
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
3080
|
-
body: parsedBody,
|
|
3081
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3082
|
-
};
|
|
3083
|
-
webhookContext = {
|
|
3084
|
-
env: process.env,
|
|
3085
|
-
app: { id: appId, versionId: appVersionId },
|
|
3086
|
-
log: createContextLogger()
|
|
3087
|
-
};
|
|
3088
|
-
}
|
|
3089
|
-
const originalEnv = { ...process.env };
|
|
3090
|
-
Object.assign(process.env, requestEnv);
|
|
3091
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
3092
|
-
let webhookResponse;
|
|
3093
|
-
try {
|
|
3094
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
3095
|
-
return await runWithConfig(requestConfig, async () => {
|
|
3096
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
3097
|
-
});
|
|
3098
|
-
});
|
|
3099
|
-
} catch (err) {
|
|
3100
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3101
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
3390
|
+
const parseResult = parseWebhookRequest(
|
|
3391
|
+
parsedBody,
|
|
3392
|
+
req.method ?? "POST",
|
|
3393
|
+
url.toString(),
|
|
3394
|
+
pathname,
|
|
3395
|
+
req.headers,
|
|
3396
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
3397
|
+
rawBody,
|
|
3398
|
+
req.headers["x-skedyul-app-id"],
|
|
3399
|
+
req.headers["x-skedyul-app-version-id"]
|
|
3400
|
+
);
|
|
3401
|
+
if ("error" in parseResult) {
|
|
3402
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
3102
3403
|
return;
|
|
3103
|
-
} finally {
|
|
3104
|
-
process.env = originalEnv;
|
|
3105
3404
|
}
|
|
3106
|
-
const
|
|
3405
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
3107
3406
|
const responseHeaders = {
|
|
3108
|
-
...
|
|
3407
|
+
...result.headers
|
|
3109
3408
|
};
|
|
3110
3409
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
3111
3410
|
responseHeaders["Content-Type"] = "application/json";
|
|
3112
3411
|
}
|
|
3113
|
-
res.writeHead(status, responseHeaders);
|
|
3114
|
-
if (
|
|
3115
|
-
if (typeof
|
|
3116
|
-
res.end(
|
|
3412
|
+
res.writeHead(result.status, responseHeaders);
|
|
3413
|
+
if (result.body !== void 0) {
|
|
3414
|
+
if (typeof result.body === "string") {
|
|
3415
|
+
res.end(result.body);
|
|
3117
3416
|
} else {
|
|
3118
|
-
res.end(JSON.stringify(
|
|
3417
|
+
res.end(JSON.stringify(result.body));
|
|
3119
3418
|
}
|
|
3120
3419
|
} else {
|
|
3121
3420
|
res.end();
|
|
@@ -3154,10 +3453,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3154
3453
|
return;
|
|
3155
3454
|
}
|
|
3156
3455
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
3157
|
-
if (!config.hooks?.oauth_callback) {
|
|
3158
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
3159
|
-
return;
|
|
3160
|
-
}
|
|
3161
3456
|
let parsedBody;
|
|
3162
3457
|
try {
|
|
3163
3458
|
parsedBody = await parseJSONBody(req);
|
|
@@ -3168,50 +3463,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3168
3463
|
});
|
|
3169
3464
|
return;
|
|
3170
3465
|
}
|
|
3171
|
-
const
|
|
3172
|
-
|
|
3173
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
3174
|
-
sendJSON(res, 400, {
|
|
3175
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
3176
|
-
});
|
|
3177
|
-
return;
|
|
3178
|
-
}
|
|
3179
|
-
const invocation = parsedBody.invocation;
|
|
3180
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
3181
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
3182
|
-
const oauthCallbackContext = {
|
|
3183
|
-
request: oauthRequest,
|
|
3184
|
-
invocation,
|
|
3185
|
-
log: createContextLogger()
|
|
3186
|
-
};
|
|
3187
|
-
try {
|
|
3188
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
3189
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
3190
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
3191
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
3192
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
3193
|
-
});
|
|
3194
|
-
});
|
|
3195
|
-
sendJSON(res, 200, {
|
|
3196
|
-
appInstallationId: result.appInstallationId,
|
|
3197
|
-
env: result.env ?? {}
|
|
3198
|
-
});
|
|
3199
|
-
} catch (err) {
|
|
3200
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
3201
|
-
sendJSON(res, 500, {
|
|
3202
|
-
error: {
|
|
3203
|
-
code: -32603,
|
|
3204
|
-
message: errorMessage
|
|
3205
|
-
}
|
|
3206
|
-
});
|
|
3207
|
-
}
|
|
3466
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3467
|
+
sendJSON(res, result.status, result.body);
|
|
3208
3468
|
return;
|
|
3209
3469
|
}
|
|
3210
3470
|
if (pathname === "/install" && req.method === "POST") {
|
|
3211
|
-
if (!config.hooks?.install) {
|
|
3212
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
3213
|
-
return;
|
|
3214
|
-
}
|
|
3215
3471
|
let installBody;
|
|
3216
3472
|
try {
|
|
3217
3473
|
installBody = await parseJSONBody(req);
|
|
@@ -3221,61 +3477,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3221
3477
|
});
|
|
3222
3478
|
return;
|
|
3223
3479
|
}
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
3227
|
-
});
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
const installContext = {
|
|
3231
|
-
env: installBody.env ?? {},
|
|
3232
|
-
workplace: installBody.context.workplace,
|
|
3233
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
3234
|
-
app: installBody.context.app,
|
|
3235
|
-
invocation: installBody.invocation,
|
|
3236
|
-
log: createContextLogger()
|
|
3237
|
-
};
|
|
3238
|
-
const installRequestConfig = {
|
|
3239
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3240
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3241
|
-
};
|
|
3242
|
-
try {
|
|
3243
|
-
const installHook = config.hooks.install;
|
|
3244
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
3245
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
3246
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
3247
|
-
return await installHandler(installContext);
|
|
3248
|
-
});
|
|
3249
|
-
});
|
|
3250
|
-
sendJSON(res, 200, {
|
|
3251
|
-
env: result.env ?? {},
|
|
3252
|
-
redirect: result.redirect
|
|
3253
|
-
});
|
|
3254
|
-
} catch (err) {
|
|
3255
|
-
if (err instanceof InstallError) {
|
|
3256
|
-
sendJSON(res, 400, {
|
|
3257
|
-
error: {
|
|
3258
|
-
code: err.code,
|
|
3259
|
-
message: err.message,
|
|
3260
|
-
field: err.field
|
|
3261
|
-
}
|
|
3262
|
-
});
|
|
3263
|
-
} else {
|
|
3264
|
-
sendJSON(res, 500, {
|
|
3265
|
-
error: {
|
|
3266
|
-
code: -32603,
|
|
3267
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3268
|
-
}
|
|
3269
|
-
});
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3480
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3481
|
+
sendJSON(res, result.status, result.body);
|
|
3272
3482
|
return;
|
|
3273
3483
|
}
|
|
3274
3484
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
3275
|
-
if (!config.hooks?.uninstall) {
|
|
3276
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
3277
|
-
return;
|
|
3278
|
-
}
|
|
3279
3485
|
let uninstallBody;
|
|
3280
3486
|
try {
|
|
3281
3487
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -3285,53 +3491,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3285
3491
|
});
|
|
3286
3492
|
return;
|
|
3287
3493
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
error: {
|
|
3291
|
-
code: -32602,
|
|
3292
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3293
|
-
}
|
|
3294
|
-
});
|
|
3295
|
-
return;
|
|
3296
|
-
}
|
|
3297
|
-
const uninstallContext = {
|
|
3298
|
-
env: uninstallBody.env ?? {},
|
|
3299
|
-
workplace: uninstallBody.context.workplace,
|
|
3300
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
3301
|
-
app: uninstallBody.context.app,
|
|
3302
|
-
invocation: uninstallBody.invocation,
|
|
3303
|
-
log: createContextLogger()
|
|
3304
|
-
};
|
|
3305
|
-
const uninstallRequestConfig = {
|
|
3306
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3307
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3308
|
-
};
|
|
3309
|
-
try {
|
|
3310
|
-
const uninstallHook = config.hooks.uninstall;
|
|
3311
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3312
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
3313
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
3314
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
3315
|
-
});
|
|
3316
|
-
});
|
|
3317
|
-
sendJSON(res, 200, {
|
|
3318
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
3319
|
-
});
|
|
3320
|
-
} catch (err) {
|
|
3321
|
-
sendJSON(res, 500, {
|
|
3322
|
-
error: {
|
|
3323
|
-
code: -32603,
|
|
3324
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3325
|
-
}
|
|
3326
|
-
});
|
|
3327
|
-
}
|
|
3494
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3495
|
+
sendJSON(res, result.status, result.body);
|
|
3328
3496
|
return;
|
|
3329
3497
|
}
|
|
3330
3498
|
if (pathname === "/provision" && req.method === "POST") {
|
|
3331
|
-
if (!config.hooks?.provision) {
|
|
3332
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
3333
|
-
return;
|
|
3334
|
-
}
|
|
3335
3499
|
let provisionBody;
|
|
3336
3500
|
try {
|
|
3337
3501
|
provisionBody = await parseJSONBody(req);
|
|
@@ -3341,46 +3505,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
3341
3505
|
});
|
|
3342
3506
|
return;
|
|
3343
3507
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
3347
|
-
});
|
|
3348
|
-
return;
|
|
3349
|
-
}
|
|
3350
|
-
const mergedEnv = {};
|
|
3351
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3352
|
-
if (value !== void 0) {
|
|
3353
|
-
mergedEnv[key] = value;
|
|
3354
|
-
}
|
|
3355
|
-
}
|
|
3356
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3357
|
-
const provisionContext = {
|
|
3358
|
-
env: mergedEnv,
|
|
3359
|
-
app: provisionBody.context.app,
|
|
3360
|
-
invocation: provisionBody.invocation,
|
|
3361
|
-
log: createContextLogger()
|
|
3362
|
-
};
|
|
3363
|
-
const provisionRequestConfig = {
|
|
3364
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
3365
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3366
|
-
};
|
|
3367
|
-
try {
|
|
3368
|
-
const provisionHook = config.hooks.provision;
|
|
3369
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
3370
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
3371
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
3372
|
-
return await provisionHandler(provisionContext);
|
|
3373
|
-
});
|
|
3374
|
-
});
|
|
3375
|
-
sendJSON(res, 200, result);
|
|
3376
|
-
} catch (err) {
|
|
3377
|
-
sendJSON(res, 500, {
|
|
3378
|
-
error: {
|
|
3379
|
-
code: -32603,
|
|
3380
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3381
|
-
}
|
|
3382
|
-
});
|
|
3383
|
-
}
|
|
3508
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3509
|
+
sendJSON(res, result.status, result.body);
|
|
3384
3510
|
return;
|
|
3385
3511
|
}
|
|
3386
3512
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -3561,12 +3687,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3561
3687
|
}
|
|
3562
3688
|
if (path14.startsWith("/webhooks/") && webhookRegistry) {
|
|
3563
3689
|
const handle = path14.slice("/webhooks/".length);
|
|
3564
|
-
|
|
3565
|
-
if (!webhookDef) {
|
|
3690
|
+
if (!webhookRegistry[handle]) {
|
|
3566
3691
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
3567
3692
|
}
|
|
3568
|
-
|
|
3569
|
-
if (!allowedMethods.includes(method)) {
|
|
3693
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
3570
3694
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
3571
3695
|
}
|
|
3572
3696
|
const rawBody = event.body ?? "";
|
|
@@ -3581,107 +3705,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3581
3705
|
} else {
|
|
3582
3706
|
parsedBody = rawBody;
|
|
3583
3707
|
}
|
|
3584
|
-
const
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
method: envelope.request.method,
|
|
3603
|
-
url: envelope.request.url,
|
|
3604
|
-
path: envelope.request.path,
|
|
3605
|
-
headers: envelope.request.headers,
|
|
3606
|
-
query: envelope.request.query,
|
|
3607
|
-
body: originalParsedBody,
|
|
3608
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
3609
|
-
};
|
|
3610
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
3611
|
-
const app = envelope.context.app;
|
|
3612
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
3613
|
-
webhookContext = {
|
|
3614
|
-
env: envVars,
|
|
3615
|
-
app,
|
|
3616
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
3617
|
-
workplace: envelope.context.workplace,
|
|
3618
|
-
registration: envelope.context.registration ?? {},
|
|
3619
|
-
invocation,
|
|
3620
|
-
log: createContextLogger()
|
|
3621
|
-
};
|
|
3622
|
-
} else {
|
|
3623
|
-
webhookContext = {
|
|
3624
|
-
env: envVars,
|
|
3625
|
-
app,
|
|
3626
|
-
invocation,
|
|
3627
|
-
log: createContextLogger()
|
|
3628
|
-
};
|
|
3629
|
-
}
|
|
3630
|
-
} else {
|
|
3631
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
3632
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
3633
|
-
if (!appId || !appVersionId) {
|
|
3634
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
3635
|
-
}
|
|
3636
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3637
|
-
const protocol = forwardedProto ?? "https";
|
|
3638
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3639
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3640
|
-
const webhookUrl = `${protocol}://${host}${path14}${queryString}`;
|
|
3641
|
-
webhookRequest = {
|
|
3642
|
-
method,
|
|
3643
|
-
url: webhookUrl,
|
|
3644
|
-
path: path14,
|
|
3645
|
-
headers: event.headers,
|
|
3646
|
-
query: event.queryStringParameters ?? {},
|
|
3647
|
-
body: parsedBody,
|
|
3648
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
3649
|
-
};
|
|
3650
|
-
webhookContext = {
|
|
3651
|
-
env: process.env,
|
|
3652
|
-
app: { id: appId, versionId: appVersionId },
|
|
3653
|
-
log: createContextLogger()
|
|
3654
|
-
};
|
|
3655
|
-
}
|
|
3656
|
-
const originalEnv = { ...process.env };
|
|
3657
|
-
Object.assign(process.env, requestEnv);
|
|
3658
|
-
const requestConfig = {
|
|
3659
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3660
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3661
|
-
};
|
|
3662
|
-
let webhookResponse;
|
|
3663
|
-
try {
|
|
3664
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
3665
|
-
return await runWithConfig(requestConfig, async () => {
|
|
3666
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
3667
|
-
});
|
|
3668
|
-
});
|
|
3669
|
-
} catch (err) {
|
|
3670
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
3671
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
3672
|
-
} finally {
|
|
3673
|
-
process.env = originalEnv;
|
|
3708
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
3709
|
+
const protocol = forwardedProto ?? "https";
|
|
3710
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
3711
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
3712
|
+
const webhookUrl = `${protocol}://${host}${path14}${queryString}`;
|
|
3713
|
+
const parseResult = parseWebhookRequest(
|
|
3714
|
+
parsedBody,
|
|
3715
|
+
method,
|
|
3716
|
+
webhookUrl,
|
|
3717
|
+
path14,
|
|
3718
|
+
event.headers,
|
|
3719
|
+
event.queryStringParameters ?? {},
|
|
3720
|
+
rawBody,
|
|
3721
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
3722
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
3723
|
+
);
|
|
3724
|
+
if ("error" in parseResult) {
|
|
3725
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
3674
3726
|
}
|
|
3727
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
3675
3728
|
const responseHeaders = {
|
|
3676
3729
|
...headers,
|
|
3677
|
-
...
|
|
3730
|
+
...result.headers
|
|
3678
3731
|
};
|
|
3679
|
-
const status = webhookResponse.status ?? 200;
|
|
3680
|
-
const body = webhookResponse.body;
|
|
3681
3732
|
return {
|
|
3682
|
-
statusCode: status,
|
|
3733
|
+
statusCode: result.status,
|
|
3683
3734
|
headers: responseHeaders,
|
|
3684
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
3735
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
3685
3736
|
};
|
|
3686
3737
|
}
|
|
3687
3738
|
if (path14 === "/core" && method === "POST") {
|
|
@@ -3817,9 +3868,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3817
3868
|
}
|
|
3818
3869
|
}
|
|
3819
3870
|
if (path14 === "/install" && method === "POST") {
|
|
3820
|
-
if (!config.hooks?.install) {
|
|
3821
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
3822
|
-
}
|
|
3823
3871
|
let installBody;
|
|
3824
3872
|
try {
|
|
3825
3873
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3830,68 +3878,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3830
3878
|
headers
|
|
3831
3879
|
);
|
|
3832
3880
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
400,
|
|
3836
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
3837
|
-
headers
|
|
3838
|
-
);
|
|
3839
|
-
}
|
|
3840
|
-
const installContext = {
|
|
3841
|
-
env: installBody.env ?? {},
|
|
3842
|
-
workplace: installBody.context.workplace,
|
|
3843
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
3844
|
-
app: installBody.context.app,
|
|
3845
|
-
invocation: installBody.invocation,
|
|
3846
|
-
log: createContextLogger()
|
|
3847
|
-
};
|
|
3848
|
-
const installRequestConfig = {
|
|
3849
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3850
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3851
|
-
};
|
|
3852
|
-
try {
|
|
3853
|
-
const installHook = config.hooks.install;
|
|
3854
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
3855
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
3856
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
3857
|
-
return await installHandler(installContext);
|
|
3858
|
-
});
|
|
3859
|
-
});
|
|
3860
|
-
return createResponse(
|
|
3861
|
-
200,
|
|
3862
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
3863
|
-
headers
|
|
3864
|
-
);
|
|
3865
|
-
} catch (err) {
|
|
3866
|
-
if (err instanceof InstallError) {
|
|
3867
|
-
return createResponse(
|
|
3868
|
-
400,
|
|
3869
|
-
{
|
|
3870
|
-
error: {
|
|
3871
|
-
code: err.code,
|
|
3872
|
-
message: err.message,
|
|
3873
|
-
field: err.field
|
|
3874
|
-
}
|
|
3875
|
-
},
|
|
3876
|
-
headers
|
|
3877
|
-
);
|
|
3878
|
-
}
|
|
3879
|
-
return createResponse(
|
|
3880
|
-
500,
|
|
3881
|
-
{
|
|
3882
|
-
error: {
|
|
3883
|
-
code: -32603,
|
|
3884
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3885
|
-
}
|
|
3886
|
-
},
|
|
3887
|
-
headers
|
|
3888
|
-
);
|
|
3889
|
-
}
|
|
3881
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
3882
|
+
return createResponse(result.status, result.body, headers);
|
|
3890
3883
|
}
|
|
3891
3884
|
if (path14 === "/uninstall" && method === "POST") {
|
|
3892
|
-
if (!config.hooks?.uninstall) {
|
|
3893
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
3894
|
-
}
|
|
3895
3885
|
let uninstallBody;
|
|
3896
3886
|
try {
|
|
3897
3887
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -3902,137 +3892,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
3902
3892
|
headers
|
|
3903
3893
|
);
|
|
3904
3894
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
400,
|
|
3908
|
-
{
|
|
3909
|
-
error: {
|
|
3910
|
-
code: -32602,
|
|
3911
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
3912
|
-
}
|
|
3913
|
-
},
|
|
3914
|
-
headers
|
|
3915
|
-
);
|
|
3916
|
-
}
|
|
3917
|
-
const uninstallContext = {
|
|
3918
|
-
env: uninstallBody.env ?? {},
|
|
3919
|
-
workplace: uninstallBody.context.workplace,
|
|
3920
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
3921
|
-
app: uninstallBody.context.app,
|
|
3922
|
-
invocation: uninstallBody.invocation,
|
|
3923
|
-
log: createContextLogger()
|
|
3924
|
-
};
|
|
3925
|
-
const uninstallRequestConfig = {
|
|
3926
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
3927
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
3928
|
-
};
|
|
3929
|
-
try {
|
|
3930
|
-
const uninstallHook = config.hooks.uninstall;
|
|
3931
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
3932
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
3933
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
3934
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
3935
|
-
});
|
|
3936
|
-
});
|
|
3937
|
-
return createResponse(
|
|
3938
|
-
200,
|
|
3939
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
3940
|
-
headers
|
|
3941
|
-
);
|
|
3942
|
-
} catch (err) {
|
|
3943
|
-
return createResponse(
|
|
3944
|
-
500,
|
|
3945
|
-
{
|
|
3946
|
-
error: {
|
|
3947
|
-
code: -32603,
|
|
3948
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
3949
|
-
}
|
|
3950
|
-
},
|
|
3951
|
-
headers
|
|
3952
|
-
);
|
|
3953
|
-
}
|
|
3895
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
3896
|
+
return createResponse(result.status, result.body, headers);
|
|
3954
3897
|
}
|
|
3955
3898
|
if (path14 === "/provision" && method === "POST") {
|
|
3956
|
-
console.log("[serverless] /provision endpoint called");
|
|
3957
|
-
if (!config.hooks?.provision) {
|
|
3958
|
-
console.log("[serverless] No provision handler configured");
|
|
3959
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
3960
|
-
}
|
|
3961
3899
|
let provisionBody;
|
|
3962
3900
|
try {
|
|
3963
3901
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
3964
|
-
console.log("[serverless] Provision body parsed:", {
|
|
3965
|
-
hasEnv: !!provisionBody.env,
|
|
3966
|
-
hasContext: !!provisionBody.context,
|
|
3967
|
-
appId: provisionBody.context?.app?.id,
|
|
3968
|
-
versionId: provisionBody.context?.app?.versionId
|
|
3969
|
-
});
|
|
3970
3902
|
} catch {
|
|
3971
|
-
console.log("[serverless] Failed to parse provision body");
|
|
3972
3903
|
return createResponse(
|
|
3973
3904
|
400,
|
|
3974
3905
|
{ error: { code: -32700, message: "Parse error" } },
|
|
3975
3906
|
headers
|
|
3976
3907
|
);
|
|
3977
3908
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
return createResponse(
|
|
3981
|
-
400,
|
|
3982
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
3983
|
-
headers
|
|
3984
|
-
);
|
|
3985
|
-
}
|
|
3986
|
-
const mergedEnv = {};
|
|
3987
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3988
|
-
if (value !== void 0) {
|
|
3989
|
-
mergedEnv[key] = value;
|
|
3990
|
-
}
|
|
3991
|
-
}
|
|
3992
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
3993
|
-
const provisionContext = {
|
|
3994
|
-
env: mergedEnv,
|
|
3995
|
-
app: provisionBody.context.app,
|
|
3996
|
-
invocation: provisionBody.invocation,
|
|
3997
|
-
log: createContextLogger()
|
|
3998
|
-
};
|
|
3999
|
-
const provisionRequestConfig = {
|
|
4000
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
4001
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
4002
|
-
};
|
|
4003
|
-
console.log("[serverless] Calling provision handler...");
|
|
4004
|
-
try {
|
|
4005
|
-
const provisionHook = config.hooks.provision;
|
|
4006
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
4007
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
4008
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
4009
|
-
return await provisionHandler(provisionContext);
|
|
4010
|
-
});
|
|
4011
|
-
});
|
|
4012
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
4013
|
-
return createResponse(200, result, headers);
|
|
4014
|
-
} catch (err) {
|
|
4015
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
4016
|
-
return createResponse(
|
|
4017
|
-
500,
|
|
4018
|
-
{
|
|
4019
|
-
error: {
|
|
4020
|
-
code: -32603,
|
|
4021
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
4022
|
-
}
|
|
4023
|
-
},
|
|
4024
|
-
headers
|
|
4025
|
-
);
|
|
4026
|
-
}
|
|
3909
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
3910
|
+
return createResponse(result.status, result.body, headers);
|
|
4027
3911
|
}
|
|
4028
3912
|
if (path14 === "/oauth_callback" && method === "POST") {
|
|
4029
|
-
if (!config.hooks?.oauth_callback) {
|
|
4030
|
-
return createResponse(
|
|
4031
|
-
404,
|
|
4032
|
-
{ error: "OAuth callback handler not configured" },
|
|
4033
|
-
headers
|
|
4034
|
-
);
|
|
4035
|
-
}
|
|
4036
3913
|
let parsedBody;
|
|
4037
3914
|
try {
|
|
4038
3915
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -4044,52 +3921,8 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
4044
3921
|
headers
|
|
4045
3922
|
);
|
|
4046
3923
|
}
|
|
4047
|
-
const
|
|
4048
|
-
|
|
4049
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
4050
|
-
return createResponse(
|
|
4051
|
-
400,
|
|
4052
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
4053
|
-
headers
|
|
4054
|
-
);
|
|
4055
|
-
}
|
|
4056
|
-
const invocation = parsedBody.invocation;
|
|
4057
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
4058
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
4059
|
-
const oauthCallbackContext = {
|
|
4060
|
-
request: oauthRequest,
|
|
4061
|
-
invocation,
|
|
4062
|
-
log: createContextLogger()
|
|
4063
|
-
};
|
|
4064
|
-
try {
|
|
4065
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
4066
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
4067
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
4068
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
4069
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
4070
|
-
});
|
|
4071
|
-
});
|
|
4072
|
-
return createResponse(
|
|
4073
|
-
200,
|
|
4074
|
-
{
|
|
4075
|
-
appInstallationId: result.appInstallationId,
|
|
4076
|
-
env: result.env ?? {}
|
|
4077
|
-
},
|
|
4078
|
-
headers
|
|
4079
|
-
);
|
|
4080
|
-
} catch (err) {
|
|
4081
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
4082
|
-
return createResponse(
|
|
4083
|
-
500,
|
|
4084
|
-
{
|
|
4085
|
-
error: {
|
|
4086
|
-
code: -32603,
|
|
4087
|
-
message: errorMessage
|
|
4088
|
-
}
|
|
4089
|
-
},
|
|
4090
|
-
headers
|
|
4091
|
-
);
|
|
4092
|
-
}
|
|
3924
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
3925
|
+
return createResponse(result.status, result.body, headers);
|
|
4093
3926
|
}
|
|
4094
3927
|
if (path14 === "/health" && method === "GET") {
|
|
4095
3928
|
return createResponse(200, state.getHealthStatus(), headers);
|