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
|
@@ -142,10 +142,6 @@ function sendJSON(res, statusCode, data) {
|
|
|
142
142
|
res.writeHead(statusCode, { "Content-Type": "application/json" });
|
|
143
143
|
res.end(JSON.stringify(data));
|
|
144
144
|
}
|
|
145
|
-
function sendHTML(res, statusCode, html) {
|
|
146
|
-
res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
|
|
147
|
-
res.end(html);
|
|
148
|
-
}
|
|
149
145
|
function getDefaultHeaders(options) {
|
|
150
146
|
return {
|
|
151
147
|
"Content-Type": "application/json",
|
|
@@ -331,8 +327,8 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
|
|
|
331
327
|
};
|
|
332
328
|
}
|
|
333
329
|
function createCallToolHandler(registry, state, onMaxRequests) {
|
|
334
|
-
return async function callTool(
|
|
335
|
-
const toolName = String(
|
|
330
|
+
return async function callTool(toolNameInput, toolArgsInput) {
|
|
331
|
+
const toolName = String(toolNameInput);
|
|
336
332
|
const tool = registry[toolName];
|
|
337
333
|
if (!tool) {
|
|
338
334
|
throw new Error(`Tool "${toolName}" not found in registry`);
|
|
@@ -341,7 +337,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
341
337
|
throw new Error(`Tool "${toolName}" handler is not a function`);
|
|
342
338
|
}
|
|
343
339
|
const fn = tool.handler;
|
|
344
|
-
const args =
|
|
340
|
+
const args = toolArgsInput ?? {};
|
|
345
341
|
const estimateMode = args.estimate === true;
|
|
346
342
|
if (!estimateMode) {
|
|
347
343
|
state.incrementRequestCount();
|
|
@@ -596,51 +592,6 @@ async function handleCoreMethod(method, params) {
|
|
|
596
592
|
};
|
|
597
593
|
}
|
|
598
594
|
|
|
599
|
-
// src/server/handler-helpers.ts
|
|
600
|
-
function parseHandlerEnvelope(parsedBody) {
|
|
601
|
-
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
const envelope = parsedBody;
|
|
605
|
-
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
609
|
-
return null;
|
|
610
|
-
}
|
|
611
|
-
return {
|
|
612
|
-
env: envelope.env,
|
|
613
|
-
request: envelope.request,
|
|
614
|
-
context: envelope.context
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
function buildRequestFromRaw(raw) {
|
|
618
|
-
let parsedBody = raw.body;
|
|
619
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
620
|
-
if (contentType.includes("application/json")) {
|
|
621
|
-
try {
|
|
622
|
-
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
623
|
-
} catch {
|
|
624
|
-
parsedBody = raw.body;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
method: raw.method,
|
|
629
|
-
url: raw.url,
|
|
630
|
-
path: raw.path,
|
|
631
|
-
headers: raw.headers,
|
|
632
|
-
query: raw.query,
|
|
633
|
-
body: parsedBody,
|
|
634
|
-
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function buildRequestScopedConfig(env) {
|
|
638
|
-
return {
|
|
639
|
-
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
640
|
-
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
595
|
// src/server/startup-logger.ts
|
|
645
596
|
function padEnd(str, length) {
|
|
646
597
|
if (str.length >= length) {
|
|
@@ -738,6 +689,405 @@ function isProvisionConfig(value) {
|
|
|
738
689
|
return value !== void 0 && value !== null && !(value instanceof Promise);
|
|
739
690
|
}
|
|
740
691
|
|
|
692
|
+
// src/server/handlers/install-handler.ts
|
|
693
|
+
async function handleInstall(body, hooks) {
|
|
694
|
+
if (!hooks?.install) {
|
|
695
|
+
return {
|
|
696
|
+
status: 404,
|
|
697
|
+
body: { error: "Install handler not configured" }
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (!body.context?.appInstallationId || !body.context?.workplace) {
|
|
701
|
+
return {
|
|
702
|
+
status: 400,
|
|
703
|
+
body: {
|
|
704
|
+
error: {
|
|
705
|
+
code: -32602,
|
|
706
|
+
message: "Missing context (appInstallationId and workplace required)"
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const installContext = {
|
|
712
|
+
env: body.env ?? {},
|
|
713
|
+
workplace: body.context.workplace,
|
|
714
|
+
appInstallationId: body.context.appInstallationId,
|
|
715
|
+
app: body.context.app,
|
|
716
|
+
invocation: body.invocation,
|
|
717
|
+
log: createContextLogger()
|
|
718
|
+
};
|
|
719
|
+
const requestConfig = {
|
|
720
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
721
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
722
|
+
};
|
|
723
|
+
try {
|
|
724
|
+
const installHook = hooks.install;
|
|
725
|
+
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
726
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
727
|
+
return await runWithConfig(requestConfig, async () => {
|
|
728
|
+
return await installHandler(installContext);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
return {
|
|
732
|
+
status: 200,
|
|
733
|
+
body: { env: result.env ?? {}, redirect: result.redirect }
|
|
734
|
+
};
|
|
735
|
+
} catch (err) {
|
|
736
|
+
if (err instanceof InstallError) {
|
|
737
|
+
return {
|
|
738
|
+
status: 400,
|
|
739
|
+
body: {
|
|
740
|
+
error: {
|
|
741
|
+
code: err.code,
|
|
742
|
+
message: err.message,
|
|
743
|
+
field: err.field
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
status: 500,
|
|
750
|
+
body: {
|
|
751
|
+
error: {
|
|
752
|
+
code: -32603,
|
|
753
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/server/handlers/uninstall-handler.ts
|
|
761
|
+
async function handleUninstall(body, hooks) {
|
|
762
|
+
if (!hooks?.uninstall) {
|
|
763
|
+
return {
|
|
764
|
+
status: 404,
|
|
765
|
+
body: { error: "Uninstall handler not configured" }
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
if (!body.context?.appInstallationId || !body.context?.workplace || !body.context?.app) {
|
|
769
|
+
return {
|
|
770
|
+
status: 400,
|
|
771
|
+
body: {
|
|
772
|
+
error: {
|
|
773
|
+
code: -32602,
|
|
774
|
+
message: "Missing context (appInstallationId, workplace and app required)"
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const uninstallContext = {
|
|
780
|
+
env: body.env ?? {},
|
|
781
|
+
workplace: body.context.workplace,
|
|
782
|
+
appInstallationId: body.context.appInstallationId,
|
|
783
|
+
app: body.context.app,
|
|
784
|
+
invocation: body.invocation,
|
|
785
|
+
log: createContextLogger()
|
|
786
|
+
};
|
|
787
|
+
const requestConfig = {
|
|
788
|
+
baseUrl: body.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
789
|
+
apiToken: body.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
const uninstallHook = hooks.uninstall;
|
|
793
|
+
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
794
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
795
|
+
return await runWithConfig(requestConfig, async () => {
|
|
796
|
+
return await uninstallHandlerFn(uninstallContext);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
status: 200,
|
|
801
|
+
body: { cleanedWebhookIds: result.cleanedWebhookIds ?? [] }
|
|
802
|
+
};
|
|
803
|
+
} catch (err) {
|
|
804
|
+
return {
|
|
805
|
+
status: 500,
|
|
806
|
+
body: {
|
|
807
|
+
error: {
|
|
808
|
+
code: -32603,
|
|
809
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/server/handlers/provision-handler.ts
|
|
817
|
+
async function handleProvision(body, hooks) {
|
|
818
|
+
if (!hooks?.provision) {
|
|
819
|
+
return {
|
|
820
|
+
status: 404,
|
|
821
|
+
body: { error: "Provision handler not configured" }
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (!body.context?.app) {
|
|
825
|
+
return {
|
|
826
|
+
status: 400,
|
|
827
|
+
body: {
|
|
828
|
+
error: {
|
|
829
|
+
code: -32602,
|
|
830
|
+
message: "Missing context (app required)"
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
const mergedEnv = {};
|
|
836
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
837
|
+
if (value !== void 0) {
|
|
838
|
+
mergedEnv[key] = value;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
Object.assign(mergedEnv, body.env ?? {});
|
|
842
|
+
const provisionContext = {
|
|
843
|
+
env: mergedEnv,
|
|
844
|
+
app: body.context.app,
|
|
845
|
+
invocation: body.invocation,
|
|
846
|
+
log: createContextLogger()
|
|
847
|
+
};
|
|
848
|
+
const requestConfig = {
|
|
849
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
850
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
851
|
+
};
|
|
852
|
+
try {
|
|
853
|
+
const provisionHook = hooks.provision;
|
|
854
|
+
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
855
|
+
const result = await runWithLogContext({ invocation: body.invocation }, async () => {
|
|
856
|
+
return await runWithConfig(requestConfig, async () => {
|
|
857
|
+
return await provisionHandler(provisionContext);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
status: 200,
|
|
862
|
+
body: result
|
|
863
|
+
};
|
|
864
|
+
} catch (err) {
|
|
865
|
+
return {
|
|
866
|
+
status: 500,
|
|
867
|
+
body: {
|
|
868
|
+
error: {
|
|
869
|
+
code: -32603,
|
|
870
|
+
message: err instanceof Error ? err.message : String(err ?? "")
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/server/handler-helpers.ts
|
|
878
|
+
function parseHandlerEnvelope(parsedBody) {
|
|
879
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
const envelope = parsedBody;
|
|
883
|
+
if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
env: envelope.env,
|
|
891
|
+
request: envelope.request,
|
|
892
|
+
context: envelope.context
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function buildRequestFromRaw(raw) {
|
|
896
|
+
let parsedBody = raw.body;
|
|
897
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
898
|
+
if (contentType.includes("application/json")) {
|
|
899
|
+
try {
|
|
900
|
+
parsedBody = raw.body ? JSON.parse(raw.body) : {};
|
|
901
|
+
} catch {
|
|
902
|
+
parsedBody = raw.body;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
method: raw.method,
|
|
907
|
+
url: raw.url,
|
|
908
|
+
path: raw.path,
|
|
909
|
+
headers: raw.headers,
|
|
910
|
+
query: raw.query,
|
|
911
|
+
body: parsedBody,
|
|
912
|
+
rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function buildRequestScopedConfig(env) {
|
|
916
|
+
return {
|
|
917
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
918
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/server/handlers/oauth-callback-handler.ts
|
|
923
|
+
async function handleOAuthCallback(parsedBody, hooks) {
|
|
924
|
+
if (!hooks?.oauth_callback) {
|
|
925
|
+
return {
|
|
926
|
+
status: 404,
|
|
927
|
+
body: { error: "OAuth callback handler not configured" }
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const envelope = parseHandlerEnvelope(parsedBody);
|
|
931
|
+
if (!envelope) {
|
|
932
|
+
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
933
|
+
return {
|
|
934
|
+
status: 400,
|
|
935
|
+
body: {
|
|
936
|
+
error: {
|
|
937
|
+
code: -32602,
|
|
938
|
+
message: "Missing envelope format: expected { env, request }"
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
const invocation = parsedBody.invocation;
|
|
944
|
+
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
945
|
+
const requestConfig = buildRequestScopedConfig(envelope.env);
|
|
946
|
+
const oauthCallbackContext = {
|
|
947
|
+
request: oauthRequest,
|
|
948
|
+
invocation,
|
|
949
|
+
log: createContextLogger()
|
|
950
|
+
};
|
|
951
|
+
try {
|
|
952
|
+
const oauthCallbackHook = hooks.oauth_callback;
|
|
953
|
+
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
954
|
+
const result = await runWithLogContext({ invocation }, async () => {
|
|
955
|
+
return await runWithConfig(requestConfig, async () => {
|
|
956
|
+
return await oauthCallbackHandler(oauthCallbackContext);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
return {
|
|
960
|
+
status: 200,
|
|
961
|
+
body: {
|
|
962
|
+
appInstallationId: result.appInstallationId,
|
|
963
|
+
env: result.env ?? {}
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
} catch (err) {
|
|
967
|
+
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
968
|
+
return {
|
|
969
|
+
status: 500,
|
|
970
|
+
body: {
|
|
971
|
+
error: {
|
|
972
|
+
code: -32603,
|
|
973
|
+
message: errorMessage
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/server/handlers/webhook-handler.ts
|
|
981
|
+
function parseWebhookRequest(parsedBody, method, url, path, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
982
|
+
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
983
|
+
if (isEnvelope) {
|
|
984
|
+
const envelope = parsedBody;
|
|
985
|
+
const requestEnv = envelope.env ?? {};
|
|
986
|
+
const invocation = envelope.invocation;
|
|
987
|
+
let originalParsedBody = envelope.request.body;
|
|
988
|
+
const originalContentType = envelope.request.headers["content-type"] ?? "";
|
|
989
|
+
if (originalContentType.includes("application/json")) {
|
|
990
|
+
try {
|
|
991
|
+
originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const webhookRequest2 = {
|
|
996
|
+
method: envelope.request.method,
|
|
997
|
+
url: envelope.request.url,
|
|
998
|
+
path: envelope.request.path,
|
|
999
|
+
headers: envelope.request.headers,
|
|
1000
|
+
query: envelope.request.query,
|
|
1001
|
+
body: originalParsedBody,
|
|
1002
|
+
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1003
|
+
};
|
|
1004
|
+
const envVars = { ...process.env, ...requestEnv };
|
|
1005
|
+
const app = envelope.context.app;
|
|
1006
|
+
let webhookContext2;
|
|
1007
|
+
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1008
|
+
webhookContext2 = {
|
|
1009
|
+
env: envVars,
|
|
1010
|
+
app,
|
|
1011
|
+
appInstallationId: envelope.context.appInstallationId,
|
|
1012
|
+
workplace: envelope.context.workplace,
|
|
1013
|
+
registration: envelope.context.registration ?? {},
|
|
1014
|
+
invocation,
|
|
1015
|
+
log: createContextLogger()
|
|
1016
|
+
};
|
|
1017
|
+
} else {
|
|
1018
|
+
webhookContext2 = {
|
|
1019
|
+
env: envVars,
|
|
1020
|
+
app,
|
|
1021
|
+
invocation,
|
|
1022
|
+
log: createContextLogger()
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return { webhookRequest: webhookRequest2, webhookContext: webhookContext2, requestEnv, invocation };
|
|
1026
|
+
}
|
|
1027
|
+
if (!appIdHeader || !appVersionIdHeader) {
|
|
1028
|
+
return {
|
|
1029
|
+
error: "Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)"
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
const webhookRequest = {
|
|
1033
|
+
method,
|
|
1034
|
+
url,
|
|
1035
|
+
path,
|
|
1036
|
+
headers,
|
|
1037
|
+
query,
|
|
1038
|
+
body: parsedBody,
|
|
1039
|
+
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1040
|
+
};
|
|
1041
|
+
const webhookContext = {
|
|
1042
|
+
env: process.env,
|
|
1043
|
+
app: { id: appIdHeader, versionId: appVersionIdHeader },
|
|
1044
|
+
log: createContextLogger()
|
|
1045
|
+
};
|
|
1046
|
+
return { webhookRequest, webhookContext, requestEnv: {} };
|
|
1047
|
+
}
|
|
1048
|
+
async function executeWebhookHandler(handle, webhookRegistry, data) {
|
|
1049
|
+
const webhookDef = webhookRegistry[handle];
|
|
1050
|
+
if (!webhookDef) {
|
|
1051
|
+
return {
|
|
1052
|
+
status: 404,
|
|
1053
|
+
body: { error: `Webhook handler '${handle}' not found` }
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const originalEnv = { ...process.env };
|
|
1057
|
+
Object.assign(process.env, data.requestEnv);
|
|
1058
|
+
const requestConfig = {
|
|
1059
|
+
baseUrl: data.requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1060
|
+
apiToken: data.requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1061
|
+
};
|
|
1062
|
+
let webhookResponse;
|
|
1063
|
+
try {
|
|
1064
|
+
webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
|
|
1065
|
+
return await runWithConfig(requestConfig, async () => {
|
|
1066
|
+
return await webhookDef.handler(data.webhookRequest, data.webhookContext);
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1071
|
+
return {
|
|
1072
|
+
status: 500,
|
|
1073
|
+
body: { error: "Webhook handler error" }
|
|
1074
|
+
};
|
|
1075
|
+
} finally {
|
|
1076
|
+
process.env = originalEnv;
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
status: webhookResponse.status ?? 200,
|
|
1080
|
+
body: webhookResponse.body,
|
|
1081
|
+
headers: webhookResponse.headers
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function isMethodAllowed(webhookRegistry, handle, method) {
|
|
1085
|
+
const webhookDef = webhookRegistry[handle];
|
|
1086
|
+
if (!webhookDef) return false;
|
|
1087
|
+
const allowedMethods = webhookDef.methods ?? ["POST"];
|
|
1088
|
+
return allowedMethods.includes(method);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
741
1091
|
// src/server/dedicated.ts
|
|
742
1092
|
function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
|
|
743
1093
|
const port = getListeningPort(config);
|
|
@@ -764,13 +1114,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
764
1114
|
}
|
|
765
1115
|
if (pathname.startsWith("/webhooks/") && webhookRegistry) {
|
|
766
1116
|
const handle = pathname.slice("/webhooks/".length);
|
|
767
|
-
|
|
768
|
-
if (!webhookDef) {
|
|
1117
|
+
if (!webhookRegistry[handle]) {
|
|
769
1118
|
sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
|
|
770
1119
|
return;
|
|
771
1120
|
}
|
|
772
|
-
|
|
773
|
-
if (!allowedMethods.includes(req.method)) {
|
|
1121
|
+
if (!isMethodAllowed(webhookRegistry, handle, req.method ?? "POST")) {
|
|
774
1122
|
sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
|
|
775
1123
|
return;
|
|
776
1124
|
}
|
|
@@ -792,87 +1140,34 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
792
1140
|
} else {
|
|
793
1141
|
parsedBody = rawBody;
|
|
794
1142
|
}
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
webhookContext = {
|
|
809
|
-
env: envVars,
|
|
810
|
-
app,
|
|
811
|
-
appInstallationId: context.appInstallationId,
|
|
812
|
-
workplace: context.workplace,
|
|
813
|
-
registration: context.registration ?? {},
|
|
814
|
-
invocation,
|
|
815
|
-
log: createContextLogger()
|
|
816
|
-
};
|
|
817
|
-
} else {
|
|
818
|
-
webhookContext = {
|
|
819
|
-
env: envVars,
|
|
820
|
-
app,
|
|
821
|
-
invocation,
|
|
822
|
-
log: createContextLogger()
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
} else {
|
|
826
|
-
const appId = req.headers["x-skedyul-app-id"];
|
|
827
|
-
const appVersionId = req.headers["x-skedyul-app-version-id"];
|
|
828
|
-
if (!appId || !appVersionId) {
|
|
829
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
830
|
-
}
|
|
831
|
-
webhookRequest = {
|
|
832
|
-
method: req.method ?? "POST",
|
|
833
|
-
url: url.toString(),
|
|
834
|
-
path: pathname,
|
|
835
|
-
headers: req.headers,
|
|
836
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
837
|
-
body: parsedBody,
|
|
838
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
839
|
-
};
|
|
840
|
-
webhookContext = {
|
|
841
|
-
env: process.env,
|
|
842
|
-
app: { id: appId, versionId: appVersionId },
|
|
843
|
-
log: createContextLogger()
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
const originalEnv = { ...process.env };
|
|
847
|
-
Object.assign(process.env, requestEnv);
|
|
848
|
-
const requestConfig = buildRequestScopedConfig(requestEnv);
|
|
849
|
-
let webhookResponse;
|
|
850
|
-
try {
|
|
851
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
852
|
-
return await runWithConfig(requestConfig, async () => {
|
|
853
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
854
|
-
});
|
|
855
|
-
});
|
|
856
|
-
} catch (err) {
|
|
857
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
858
|
-
sendJSON(res, 500, { error: "Webhook handler error" });
|
|
1143
|
+
const parseResult = parseWebhookRequest(
|
|
1144
|
+
parsedBody,
|
|
1145
|
+
req.method ?? "POST",
|
|
1146
|
+
url.toString(),
|
|
1147
|
+
pathname,
|
|
1148
|
+
req.headers,
|
|
1149
|
+
Object.fromEntries(url.searchParams.entries()),
|
|
1150
|
+
rawBody,
|
|
1151
|
+
req.headers["x-skedyul-app-id"],
|
|
1152
|
+
req.headers["x-skedyul-app-version-id"]
|
|
1153
|
+
);
|
|
1154
|
+
if ("error" in parseResult) {
|
|
1155
|
+
sendJSON(res, 400, { error: parseResult.error });
|
|
859
1156
|
return;
|
|
860
|
-
} finally {
|
|
861
|
-
process.env = originalEnv;
|
|
862
1157
|
}
|
|
863
|
-
const
|
|
1158
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
864
1159
|
const responseHeaders = {
|
|
865
|
-
...
|
|
1160
|
+
...result.headers
|
|
866
1161
|
};
|
|
867
1162
|
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
868
1163
|
responseHeaders["Content-Type"] = "application/json";
|
|
869
1164
|
}
|
|
870
|
-
res.writeHead(status, responseHeaders);
|
|
871
|
-
if (
|
|
872
|
-
if (typeof
|
|
873
|
-
res.end(
|
|
1165
|
+
res.writeHead(result.status, responseHeaders);
|
|
1166
|
+
if (result.body !== void 0) {
|
|
1167
|
+
if (typeof result.body === "string") {
|
|
1168
|
+
res.end(result.body);
|
|
874
1169
|
} else {
|
|
875
|
-
res.end(JSON.stringify(
|
|
1170
|
+
res.end(JSON.stringify(result.body));
|
|
876
1171
|
}
|
|
877
1172
|
} else {
|
|
878
1173
|
res.end();
|
|
@@ -911,10 +1206,6 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
911
1206
|
return;
|
|
912
1207
|
}
|
|
913
1208
|
if (pathname === "/oauth_callback" && req.method === "POST") {
|
|
914
|
-
if (!config.hooks?.oauth_callback) {
|
|
915
|
-
sendJSON(res, 404, { error: "OAuth callback handler not configured" });
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
1209
|
let parsedBody;
|
|
919
1210
|
try {
|
|
920
1211
|
parsedBody = await parseJSONBody(req);
|
|
@@ -925,50 +1216,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
925
1216
|
});
|
|
926
1217
|
return;
|
|
927
1218
|
}
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
931
|
-
sendJSON(res, 400, {
|
|
932
|
-
error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
|
|
933
|
-
});
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
const invocation = parsedBody.invocation;
|
|
937
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
938
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
939
|
-
const oauthCallbackContext = {
|
|
940
|
-
request: oauthRequest,
|
|
941
|
-
invocation,
|
|
942
|
-
log: createContextLogger()
|
|
943
|
-
};
|
|
944
|
-
try {
|
|
945
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
946
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
947
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
948
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
949
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
950
|
-
});
|
|
951
|
-
});
|
|
952
|
-
sendJSON(res, 200, {
|
|
953
|
-
appInstallationId: result.appInstallationId,
|
|
954
|
-
env: result.env ?? {}
|
|
955
|
-
});
|
|
956
|
-
} catch (err) {
|
|
957
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
958
|
-
sendJSON(res, 500, {
|
|
959
|
-
error: {
|
|
960
|
-
code: -32603,
|
|
961
|
-
message: errorMessage
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
}
|
|
1219
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1220
|
+
sendJSON(res, result.status, result.body);
|
|
965
1221
|
return;
|
|
966
1222
|
}
|
|
967
1223
|
if (pathname === "/install" && req.method === "POST") {
|
|
968
|
-
if (!config.hooks?.install) {
|
|
969
|
-
sendJSON(res, 404, { error: "Install handler not configured" });
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
1224
|
let installBody;
|
|
973
1225
|
try {
|
|
974
1226
|
installBody = await parseJSONBody(req);
|
|
@@ -978,61 +1230,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
978
1230
|
});
|
|
979
1231
|
return;
|
|
980
1232
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
|
|
984
|
-
});
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
const installContext = {
|
|
988
|
-
env: installBody.env ?? {},
|
|
989
|
-
workplace: installBody.context.workplace,
|
|
990
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
991
|
-
app: installBody.context.app,
|
|
992
|
-
invocation: installBody.invocation,
|
|
993
|
-
log: createContextLogger()
|
|
994
|
-
};
|
|
995
|
-
const installRequestConfig = {
|
|
996
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
997
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
998
|
-
};
|
|
999
|
-
try {
|
|
1000
|
-
const installHook = config.hooks.install;
|
|
1001
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1002
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1003
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1004
|
-
return await installHandler(installContext);
|
|
1005
|
-
});
|
|
1006
|
-
});
|
|
1007
|
-
sendJSON(res, 200, {
|
|
1008
|
-
env: result.env ?? {},
|
|
1009
|
-
redirect: result.redirect
|
|
1010
|
-
});
|
|
1011
|
-
} catch (err) {
|
|
1012
|
-
if (err instanceof InstallError) {
|
|
1013
|
-
sendJSON(res, 400, {
|
|
1014
|
-
error: {
|
|
1015
|
-
code: err.code,
|
|
1016
|
-
message: err.message,
|
|
1017
|
-
field: err.field
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
1020
|
-
} else {
|
|
1021
|
-
sendJSON(res, 500, {
|
|
1022
|
-
error: {
|
|
1023
|
-
code: -32603,
|
|
1024
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1025
|
-
}
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1233
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1234
|
+
sendJSON(res, result.status, result.body);
|
|
1029
1235
|
return;
|
|
1030
1236
|
}
|
|
1031
1237
|
if (pathname === "/uninstall" && req.method === "POST") {
|
|
1032
|
-
if (!config.hooks?.uninstall) {
|
|
1033
|
-
sendJSON(res, 404, { error: "Uninstall handler not configured" });
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1036
1238
|
let uninstallBody;
|
|
1037
1239
|
try {
|
|
1038
1240
|
uninstallBody = await parseJSONBody(req);
|
|
@@ -1042,53 +1244,11 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1042
1244
|
});
|
|
1043
1245
|
return;
|
|
1044
1246
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
error: {
|
|
1048
|
-
code: -32602,
|
|
1049
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1050
|
-
}
|
|
1051
|
-
});
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
const uninstallContext = {
|
|
1055
|
-
env: uninstallBody.env ?? {},
|
|
1056
|
-
workplace: uninstallBody.context.workplace,
|
|
1057
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1058
|
-
app: uninstallBody.context.app,
|
|
1059
|
-
invocation: uninstallBody.invocation,
|
|
1060
|
-
log: createContextLogger()
|
|
1061
|
-
};
|
|
1062
|
-
const uninstallRequestConfig = {
|
|
1063
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1064
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1065
|
-
};
|
|
1066
|
-
try {
|
|
1067
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1068
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1069
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1070
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1071
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1072
|
-
});
|
|
1073
|
-
});
|
|
1074
|
-
sendJSON(res, 200, {
|
|
1075
|
-
cleanedWebhookIds: result.cleanedWebhookIds ?? []
|
|
1076
|
-
});
|
|
1077
|
-
} catch (err) {
|
|
1078
|
-
sendJSON(res, 500, {
|
|
1079
|
-
error: {
|
|
1080
|
-
code: -32603,
|
|
1081
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1082
|
-
}
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1247
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1248
|
+
sendJSON(res, result.status, result.body);
|
|
1085
1249
|
return;
|
|
1086
1250
|
}
|
|
1087
1251
|
if (pathname === "/provision" && req.method === "POST") {
|
|
1088
|
-
if (!config.hooks?.provision) {
|
|
1089
|
-
sendJSON(res, 404, { error: "Provision handler not configured" });
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
1252
|
let provisionBody;
|
|
1093
1253
|
try {
|
|
1094
1254
|
provisionBody = await parseJSONBody(req);
|
|
@@ -1098,46 +1258,8 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
1098
1258
|
});
|
|
1099
1259
|
return;
|
|
1100
1260
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
error: { code: -32602, message: "Missing context (app required)" }
|
|
1104
|
-
});
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
const mergedEnv = {};
|
|
1108
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1109
|
-
if (value !== void 0) {
|
|
1110
|
-
mergedEnv[key] = value;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1114
|
-
const provisionContext = {
|
|
1115
|
-
env: mergedEnv,
|
|
1116
|
-
app: provisionBody.context.app,
|
|
1117
|
-
invocation: provisionBody.invocation,
|
|
1118
|
-
log: createContextLogger()
|
|
1119
|
-
};
|
|
1120
|
-
const provisionRequestConfig = {
|
|
1121
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1122
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1123
|
-
};
|
|
1124
|
-
try {
|
|
1125
|
-
const provisionHook = config.hooks.provision;
|
|
1126
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1127
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1128
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1129
|
-
return await provisionHandler(provisionContext);
|
|
1130
|
-
});
|
|
1131
|
-
});
|
|
1132
|
-
sendJSON(res, 200, result);
|
|
1133
|
-
} catch (err) {
|
|
1134
|
-
sendJSON(res, 500, {
|
|
1135
|
-
error: {
|
|
1136
|
-
code: -32603,
|
|
1137
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1138
|
-
}
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1261
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1262
|
+
sendJSON(res, result.status, result.body);
|
|
1141
1263
|
return;
|
|
1142
1264
|
}
|
|
1143
1265
|
if (pathname === "/core" && req.method === "POST") {
|
|
@@ -1318,12 +1440,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1318
1440
|
}
|
|
1319
1441
|
if (path.startsWith("/webhooks/") && webhookRegistry) {
|
|
1320
1442
|
const handle = path.slice("/webhooks/".length);
|
|
1321
|
-
|
|
1322
|
-
if (!webhookDef) {
|
|
1443
|
+
if (!webhookRegistry[handle]) {
|
|
1323
1444
|
return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
|
|
1324
1445
|
}
|
|
1325
|
-
|
|
1326
|
-
if (!allowedMethods.includes(method)) {
|
|
1446
|
+
if (!isMethodAllowed(webhookRegistry, handle, method)) {
|
|
1327
1447
|
return createResponse(405, { error: `Method ${method} not allowed` }, headers);
|
|
1328
1448
|
}
|
|
1329
1449
|
const rawBody = event.body ?? "";
|
|
@@ -1338,107 +1458,34 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1338
1458
|
} else {
|
|
1339
1459
|
parsedBody = rawBody;
|
|
1340
1460
|
}
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
method: envelope.request.method,
|
|
1360
|
-
url: envelope.request.url,
|
|
1361
|
-
path: envelope.request.path,
|
|
1362
|
-
headers: envelope.request.headers,
|
|
1363
|
-
query: envelope.request.query,
|
|
1364
|
-
body: originalParsedBody,
|
|
1365
|
-
rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
|
|
1366
|
-
};
|
|
1367
|
-
const envVars = { ...process.env, ...requestEnv };
|
|
1368
|
-
const app = envelope.context.app;
|
|
1369
|
-
if (envelope.context.appInstallationId && envelope.context.workplace) {
|
|
1370
|
-
webhookContext = {
|
|
1371
|
-
env: envVars,
|
|
1372
|
-
app,
|
|
1373
|
-
appInstallationId: envelope.context.appInstallationId,
|
|
1374
|
-
workplace: envelope.context.workplace,
|
|
1375
|
-
registration: envelope.context.registration ?? {},
|
|
1376
|
-
invocation,
|
|
1377
|
-
log: createContextLogger()
|
|
1378
|
-
};
|
|
1379
|
-
} else {
|
|
1380
|
-
webhookContext = {
|
|
1381
|
-
env: envVars,
|
|
1382
|
-
app,
|
|
1383
|
-
invocation,
|
|
1384
|
-
log: createContextLogger()
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
} else {
|
|
1388
|
-
const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
|
|
1389
|
-
const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
|
|
1390
|
-
if (!appId || !appVersionId) {
|
|
1391
|
-
throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
|
|
1392
|
-
}
|
|
1393
|
-
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1394
|
-
const protocol = forwardedProto ?? "https";
|
|
1395
|
-
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1396
|
-
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1397
|
-
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1398
|
-
webhookRequest = {
|
|
1399
|
-
method,
|
|
1400
|
-
url: webhookUrl,
|
|
1401
|
-
path,
|
|
1402
|
-
headers: event.headers,
|
|
1403
|
-
query: event.queryStringParameters ?? {},
|
|
1404
|
-
body: parsedBody,
|
|
1405
|
-
rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
|
|
1406
|
-
};
|
|
1407
|
-
webhookContext = {
|
|
1408
|
-
env: process.env,
|
|
1409
|
-
app: { id: appId, versionId: appVersionId },
|
|
1410
|
-
log: createContextLogger()
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
const originalEnv = { ...process.env };
|
|
1414
|
-
Object.assign(process.env, requestEnv);
|
|
1415
|
-
const requestConfig = {
|
|
1416
|
-
baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1417
|
-
apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1418
|
-
};
|
|
1419
|
-
let webhookResponse;
|
|
1420
|
-
try {
|
|
1421
|
-
webhookResponse = await runWithLogContext({ invocation }, async () => {
|
|
1422
|
-
return await runWithConfig(requestConfig, async () => {
|
|
1423
|
-
return await webhookDef.handler(webhookRequest, webhookContext);
|
|
1424
|
-
});
|
|
1425
|
-
});
|
|
1426
|
-
} catch (err) {
|
|
1427
|
-
console.error(`Webhook handler '${handle}' error:`, err);
|
|
1428
|
-
return createResponse(500, { error: "Webhook handler error" }, headers);
|
|
1429
|
-
} finally {
|
|
1430
|
-
process.env = originalEnv;
|
|
1461
|
+
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
1462
|
+
const protocol = forwardedProto ?? "https";
|
|
1463
|
+
const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
|
|
1464
|
+
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
|
|
1465
|
+
const webhookUrl = `${protocol}://${host}${path}${queryString}`;
|
|
1466
|
+
const parseResult = parseWebhookRequest(
|
|
1467
|
+
parsedBody,
|
|
1468
|
+
method,
|
|
1469
|
+
webhookUrl,
|
|
1470
|
+
path,
|
|
1471
|
+
event.headers,
|
|
1472
|
+
event.queryStringParameters ?? {},
|
|
1473
|
+
rawBody,
|
|
1474
|
+
event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"],
|
|
1475
|
+
event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"]
|
|
1476
|
+
);
|
|
1477
|
+
if ("error" in parseResult) {
|
|
1478
|
+
return createResponse(400, { error: parseResult.error }, headers);
|
|
1431
1479
|
}
|
|
1480
|
+
const result = await executeWebhookHandler(handle, webhookRegistry, parseResult);
|
|
1432
1481
|
const responseHeaders = {
|
|
1433
1482
|
...headers,
|
|
1434
|
-
...
|
|
1483
|
+
...result.headers
|
|
1435
1484
|
};
|
|
1436
|
-
const status = webhookResponse.status ?? 200;
|
|
1437
|
-
const body = webhookResponse.body;
|
|
1438
1485
|
return {
|
|
1439
|
-
statusCode: status,
|
|
1486
|
+
statusCode: result.status,
|
|
1440
1487
|
headers: responseHeaders,
|
|
1441
|
-
body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
|
|
1488
|
+
body: result.body !== void 0 ? typeof result.body === "string" ? result.body : JSON.stringify(result.body) : ""
|
|
1442
1489
|
};
|
|
1443
1490
|
}
|
|
1444
1491
|
if (path === "/core" && method === "POST") {
|
|
@@ -1574,9 +1621,6 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1574
1621
|
}
|
|
1575
1622
|
}
|
|
1576
1623
|
if (path === "/install" && method === "POST") {
|
|
1577
|
-
if (!config.hooks?.install) {
|
|
1578
|
-
return createResponse(404, { error: "Install handler not configured" }, headers);
|
|
1579
|
-
}
|
|
1580
1624
|
let installBody;
|
|
1581
1625
|
try {
|
|
1582
1626
|
installBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1587,68 +1631,10 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1587
1631
|
headers
|
|
1588
1632
|
);
|
|
1589
1633
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
400,
|
|
1593
|
-
{ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
|
|
1594
|
-
headers
|
|
1595
|
-
);
|
|
1596
|
-
}
|
|
1597
|
-
const installContext = {
|
|
1598
|
-
env: installBody.env ?? {},
|
|
1599
|
-
workplace: installBody.context.workplace,
|
|
1600
|
-
appInstallationId: installBody.context.appInstallationId,
|
|
1601
|
-
app: installBody.context.app,
|
|
1602
|
-
invocation: installBody.invocation,
|
|
1603
|
-
log: createContextLogger()
|
|
1604
|
-
};
|
|
1605
|
-
const installRequestConfig = {
|
|
1606
|
-
baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1607
|
-
apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1608
|
-
};
|
|
1609
|
-
try {
|
|
1610
|
-
const installHook = config.hooks.install;
|
|
1611
|
-
const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
|
|
1612
|
-
const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
|
|
1613
|
-
return await runWithConfig(installRequestConfig, async () => {
|
|
1614
|
-
return await installHandler(installContext);
|
|
1615
|
-
});
|
|
1616
|
-
});
|
|
1617
|
-
return createResponse(
|
|
1618
|
-
200,
|
|
1619
|
-
{ env: result.env ?? {}, redirect: result.redirect },
|
|
1620
|
-
headers
|
|
1621
|
-
);
|
|
1622
|
-
} catch (err) {
|
|
1623
|
-
if (err instanceof InstallError) {
|
|
1624
|
-
return createResponse(
|
|
1625
|
-
400,
|
|
1626
|
-
{
|
|
1627
|
-
error: {
|
|
1628
|
-
code: err.code,
|
|
1629
|
-
message: err.message,
|
|
1630
|
-
field: err.field
|
|
1631
|
-
}
|
|
1632
|
-
},
|
|
1633
|
-
headers
|
|
1634
|
-
);
|
|
1635
|
-
}
|
|
1636
|
-
return createResponse(
|
|
1637
|
-
500,
|
|
1638
|
-
{
|
|
1639
|
-
error: {
|
|
1640
|
-
code: -32603,
|
|
1641
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1642
|
-
}
|
|
1643
|
-
},
|
|
1644
|
-
headers
|
|
1645
|
-
);
|
|
1646
|
-
}
|
|
1634
|
+
const result = await handleInstall(installBody, config.hooks);
|
|
1635
|
+
return createResponse(result.status, result.body, headers);
|
|
1647
1636
|
}
|
|
1648
1637
|
if (path === "/uninstall" && method === "POST") {
|
|
1649
|
-
if (!config.hooks?.uninstall) {
|
|
1650
|
-
return createResponse(404, { error: "Uninstall handler not configured" }, headers);
|
|
1651
|
-
}
|
|
1652
1638
|
let uninstallBody;
|
|
1653
1639
|
try {
|
|
1654
1640
|
uninstallBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1659,137 +1645,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1659
1645
|
headers
|
|
1660
1646
|
);
|
|
1661
1647
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
400,
|
|
1665
|
-
{
|
|
1666
|
-
error: {
|
|
1667
|
-
code: -32602,
|
|
1668
|
-
message: "Missing context (appInstallationId, workplace and app required)"
|
|
1669
|
-
}
|
|
1670
|
-
},
|
|
1671
|
-
headers
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
const uninstallContext = {
|
|
1675
|
-
env: uninstallBody.env ?? {},
|
|
1676
|
-
workplace: uninstallBody.context.workplace,
|
|
1677
|
-
appInstallationId: uninstallBody.context.appInstallationId,
|
|
1678
|
-
app: uninstallBody.context.app,
|
|
1679
|
-
invocation: uninstallBody.invocation,
|
|
1680
|
-
log: createContextLogger()
|
|
1681
|
-
};
|
|
1682
|
-
const uninstallRequestConfig = {
|
|
1683
|
-
baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
1684
|
-
apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
1685
|
-
};
|
|
1686
|
-
try {
|
|
1687
|
-
const uninstallHook = config.hooks.uninstall;
|
|
1688
|
-
const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
|
|
1689
|
-
const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
|
|
1690
|
-
return await runWithConfig(uninstallRequestConfig, async () => {
|
|
1691
|
-
return await uninstallHandlerFn(uninstallContext);
|
|
1692
|
-
});
|
|
1693
|
-
});
|
|
1694
|
-
return createResponse(
|
|
1695
|
-
200,
|
|
1696
|
-
{ cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
|
|
1697
|
-
headers
|
|
1698
|
-
);
|
|
1699
|
-
} catch (err) {
|
|
1700
|
-
return createResponse(
|
|
1701
|
-
500,
|
|
1702
|
-
{
|
|
1703
|
-
error: {
|
|
1704
|
-
code: -32603,
|
|
1705
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1706
|
-
}
|
|
1707
|
-
},
|
|
1708
|
-
headers
|
|
1709
|
-
);
|
|
1710
|
-
}
|
|
1648
|
+
const result = await handleUninstall(uninstallBody, config.hooks);
|
|
1649
|
+
return createResponse(result.status, result.body, headers);
|
|
1711
1650
|
}
|
|
1712
1651
|
if (path === "/provision" && method === "POST") {
|
|
1713
|
-
console.log("[serverless] /provision endpoint called");
|
|
1714
|
-
if (!config.hooks?.provision) {
|
|
1715
|
-
console.log("[serverless] No provision handler configured");
|
|
1716
|
-
return createResponse(404, { error: "Provision handler not configured" }, headers);
|
|
1717
|
-
}
|
|
1718
1652
|
let provisionBody;
|
|
1719
1653
|
try {
|
|
1720
1654
|
provisionBody = event.body ? JSON.parse(event.body) : {};
|
|
1721
|
-
console.log("[serverless] Provision body parsed:", {
|
|
1722
|
-
hasEnv: !!provisionBody.env,
|
|
1723
|
-
hasContext: !!provisionBody.context,
|
|
1724
|
-
appId: provisionBody.context?.app?.id,
|
|
1725
|
-
versionId: provisionBody.context?.app?.versionId
|
|
1726
|
-
});
|
|
1727
1655
|
} catch {
|
|
1728
|
-
console.log("[serverless] Failed to parse provision body");
|
|
1729
1656
|
return createResponse(
|
|
1730
1657
|
400,
|
|
1731
1658
|
{ error: { code: -32700, message: "Parse error" } },
|
|
1732
1659
|
headers
|
|
1733
1660
|
);
|
|
1734
1661
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
return createResponse(
|
|
1738
|
-
400,
|
|
1739
|
-
{ error: { code: -32602, message: "Missing context (app required)" } },
|
|
1740
|
-
headers
|
|
1741
|
-
);
|
|
1742
|
-
}
|
|
1743
|
-
const mergedEnv = {};
|
|
1744
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
1745
|
-
if (value !== void 0) {
|
|
1746
|
-
mergedEnv[key] = value;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
Object.assign(mergedEnv, provisionBody.env ?? {});
|
|
1750
|
-
const provisionContext = {
|
|
1751
|
-
env: mergedEnv,
|
|
1752
|
-
app: provisionBody.context.app,
|
|
1753
|
-
invocation: provisionBody.invocation,
|
|
1754
|
-
log: createContextLogger()
|
|
1755
|
-
};
|
|
1756
|
-
const provisionRequestConfig = {
|
|
1757
|
-
baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
|
|
1758
|
-
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
|
|
1759
|
-
};
|
|
1760
|
-
console.log("[serverless] Calling provision handler...");
|
|
1761
|
-
try {
|
|
1762
|
-
const provisionHook = config.hooks.provision;
|
|
1763
|
-
const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
|
|
1764
|
-
const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
|
|
1765
|
-
return await runWithConfig(provisionRequestConfig, async () => {
|
|
1766
|
-
return await provisionHandler(provisionContext);
|
|
1767
|
-
});
|
|
1768
|
-
});
|
|
1769
|
-
console.log("[serverless] Provision handler completed successfully");
|
|
1770
|
-
return createResponse(200, result, headers);
|
|
1771
|
-
} catch (err) {
|
|
1772
|
-
console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
|
|
1773
|
-
return createResponse(
|
|
1774
|
-
500,
|
|
1775
|
-
{
|
|
1776
|
-
error: {
|
|
1777
|
-
code: -32603,
|
|
1778
|
-
message: err instanceof Error ? err.message : String(err ?? "")
|
|
1779
|
-
}
|
|
1780
|
-
},
|
|
1781
|
-
headers
|
|
1782
|
-
);
|
|
1783
|
-
}
|
|
1662
|
+
const result = await handleProvision(provisionBody, config.hooks);
|
|
1663
|
+
return createResponse(result.status, result.body, headers);
|
|
1784
1664
|
}
|
|
1785
1665
|
if (path === "/oauth_callback" && method === "POST") {
|
|
1786
|
-
if (!config.hooks?.oauth_callback) {
|
|
1787
|
-
return createResponse(
|
|
1788
|
-
404,
|
|
1789
|
-
{ error: "OAuth callback handler not configured" },
|
|
1790
|
-
headers
|
|
1791
|
-
);
|
|
1792
|
-
}
|
|
1793
1666
|
let parsedBody;
|
|
1794
1667
|
try {
|
|
1795
1668
|
parsedBody = event.body ? JSON.parse(event.body) : {};
|
|
@@ -1801,52 +1674,8 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1801
1674
|
headers
|
|
1802
1675
|
);
|
|
1803
1676
|
}
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1806
|
-
console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
|
|
1807
|
-
return createResponse(
|
|
1808
|
-
400,
|
|
1809
|
-
{ error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
|
|
1810
|
-
headers
|
|
1811
|
-
);
|
|
1812
|
-
}
|
|
1813
|
-
const invocation = parsedBody.invocation;
|
|
1814
|
-
const oauthRequest = buildRequestFromRaw(envelope.request);
|
|
1815
|
-
const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
|
|
1816
|
-
const oauthCallbackContext = {
|
|
1817
|
-
request: oauthRequest,
|
|
1818
|
-
invocation,
|
|
1819
|
-
log: createContextLogger()
|
|
1820
|
-
};
|
|
1821
|
-
try {
|
|
1822
|
-
const oauthCallbackHook = config.hooks.oauth_callback;
|
|
1823
|
-
const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
|
|
1824
|
-
const result = await runWithLogContext({ invocation }, async () => {
|
|
1825
|
-
return await runWithConfig(oauthCallbackRequestConfig, async () => {
|
|
1826
|
-
return await oauthCallbackHandler(oauthCallbackContext);
|
|
1827
|
-
});
|
|
1828
|
-
});
|
|
1829
|
-
return createResponse(
|
|
1830
|
-
200,
|
|
1831
|
-
{
|
|
1832
|
-
appInstallationId: result.appInstallationId,
|
|
1833
|
-
env: result.env ?? {}
|
|
1834
|
-
},
|
|
1835
|
-
headers
|
|
1836
|
-
);
|
|
1837
|
-
} catch (err) {
|
|
1838
|
-
const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
1839
|
-
return createResponse(
|
|
1840
|
-
500,
|
|
1841
|
-
{
|
|
1842
|
-
error: {
|
|
1843
|
-
code: -32603,
|
|
1844
|
-
message: errorMessage
|
|
1845
|
-
}
|
|
1846
|
-
},
|
|
1847
|
-
headers
|
|
1848
|
-
);
|
|
1849
|
-
}
|
|
1677
|
+
const result = await handleOAuthCallback(parsedBody, config.hooks);
|
|
1678
|
+
return createResponse(result.status, result.body, headers);
|
|
1850
1679
|
}
|
|
1851
1680
|
if (path === "/health" && method === "GET") {
|
|
1852
1681
|
return createResponse(200, state.getHealthStatus(), headers);
|
|
@@ -2240,32 +2069,10 @@ var server = {
|
|
|
2240
2069
|
create: createSkedyulServer
|
|
2241
2070
|
};
|
|
2242
2071
|
export {
|
|
2243
|
-
buildRequestFromRaw,
|
|
2244
|
-
buildRequestScopedConfig,
|
|
2245
|
-
buildToolMetadata,
|
|
2246
|
-
createCallToolHandler,
|
|
2247
|
-
createDedicatedServerInstance,
|
|
2248
|
-
createRequestState,
|
|
2249
|
-
createResponse,
|
|
2250
|
-
createServerlessInstance,
|
|
2251
2072
|
createSkedyulServer,
|
|
2252
|
-
getDefaultHeaders,
|
|
2253
2073
|
getJsonSchemaFromToolSchema,
|
|
2254
|
-
getListeningPort,
|
|
2255
2074
|
getZodSchema,
|
|
2256
|
-
handleCoreMethod,
|
|
2257
2075
|
isToolSchemaWithJson,
|
|
2258
|
-
mergeRuntimeEnv,
|
|
2259
|
-
normalizeBilling,
|
|
2260
|
-
padEnd,
|
|
2261
|
-
parseHandlerEnvelope,
|
|
2262
|
-
parseJSONBody,
|
|
2263
|
-
parseJsonRecord,
|
|
2264
|
-
parseNumberEnv,
|
|
2265
|
-
printStartupLog,
|
|
2266
|
-
readRawRequestBody,
|
|
2267
|
-
sendHTML,
|
|
2268
|
-
sendJSON,
|
|
2269
2076
|
server,
|
|
2270
2077
|
toJsonSchema
|
|
2271
2078
|
};
|