skedyul 0.2.151 → 0.2.152

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/.build-stamp CHANGED
@@ -1 +1 @@
1
- 1770695243488
1
+ 1770698146381
package/dist/server.js CHANGED
@@ -690,6 +690,66 @@ function createSkedyulServer(config, registry, webhookRegistry) {
690
690
  }
691
691
  return createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
692
692
  }
693
+ // ─────────────────────────────────────────────────────────────────────────────
694
+ // Shared Handler Helpers (used by both webhooks and OAuth callbacks)
695
+ // ─────────────────────────────────────────────────────────────────────────────
696
+ /**
697
+ * Parses a handler envelope from the request body.
698
+ * Detects envelope format: { env: {...}, request: {...}, context?: {...} }
699
+ * Returns the extracted env and request, or null if not an envelope.
700
+ */
701
+ function parseHandlerEnvelope(parsedBody) {
702
+ const isEnvelope = typeof parsedBody === 'object' &&
703
+ parsedBody !== null &&
704
+ 'env' in parsedBody &&
705
+ 'request' in parsedBody;
706
+ if (!isEnvelope) {
707
+ return null;
708
+ }
709
+ const envelope = parsedBody;
710
+ return {
711
+ env: envelope.env ?? {},
712
+ request: envelope.request,
713
+ context: envelope.context,
714
+ };
715
+ }
716
+ /**
717
+ * Converts a raw HandlerRawRequest (wire format) to a rich WebhookRequest.
718
+ * Parses JSON body if content-type is application/json, creates Buffer rawBody.
719
+ */
720
+ function buildRequestFromRaw(raw) {
721
+ // Parse the original request body
722
+ let parsedBody = raw.body;
723
+ const contentType = raw.headers['content-type'] ?? '';
724
+ if (contentType.includes('application/json')) {
725
+ try {
726
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
727
+ }
728
+ catch {
729
+ // Keep as string if JSON parsing fails
730
+ parsedBody = raw.body;
731
+ }
732
+ }
733
+ return {
734
+ method: raw.method,
735
+ url: raw.url,
736
+ path: raw.path,
737
+ headers: raw.headers,
738
+ query: raw.query,
739
+ body: parsedBody,
740
+ rawBody: raw.body ? Buffer.from(raw.body, 'utf-8') : undefined,
741
+ };
742
+ }
743
+ /**
744
+ * Builds request-scoped config by merging env from envelope with process.env fallbacks.
745
+ * Used for SKEDYUL_API_TOKEN and SKEDYUL_API_URL overrides.
746
+ */
747
+ function buildRequestScopedConfig(env) {
748
+ return {
749
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
750
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
751
+ };
752
+ }
693
753
  function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, webhookRegistry) {
694
754
  const port = getListeningPort(config);
695
755
  const httpServer = http_1.default.createServer(async (req, res) => {
@@ -742,49 +802,27 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
742
802
  }
743
803
  // Check if this is an envelope format from the platform
744
804
  // Envelope format: { env: {...}, request: {...}, context: {...} }
745
- const isEnvelope = (typeof parsedBody === 'object' &&
746
- parsedBody !== null &&
747
- 'env' in parsedBody &&
748
- 'request' in parsedBody &&
749
- 'context' in parsedBody);
805
+ const envelope = parseHandlerEnvelope(parsedBody);
750
806
  let webhookRequest;
751
807
  let webhookContext;
752
808
  let requestEnv = {};
753
- if (isEnvelope) {
754
- // Platform envelope format - extract env, request, and context
755
- const envelope = parsedBody;
756
- requestEnv = envelope.env ?? {};
757
- // Parse the original request body
758
- let originalParsedBody = envelope.request.body;
759
- const originalContentType = envelope.request.headers['content-type'] ?? '';
760
- if (originalContentType.includes('application/json')) {
761
- try {
762
- originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
763
- }
764
- catch {
765
- // Keep as string if JSON parsing fails
766
- }
767
- }
768
- webhookRequest = {
769
- method: envelope.request.method,
770
- url: envelope.request.url,
771
- path: envelope.request.path,
772
- headers: envelope.request.headers,
773
- query: envelope.request.query,
774
- body: originalParsedBody,
775
- rawBody: envelope.request.body ? Buffer.from(envelope.request.body, 'utf-8') : undefined,
776
- };
777
- const envVars = { ...process.env, ...requestEnv };
778
- const app = envelope.context.app;
809
+ if (envelope && 'context' in envelope && envelope.context) {
810
+ // Platform envelope format - use shared helpers
811
+ const context = envelope.context;
812
+ requestEnv = envelope.env;
813
+ // Convert raw request to rich request using shared helper
814
+ webhookRequest = buildRequestFromRaw(envelope.request);
815
+ const envVars = { ...process.env, ...envelope.env };
816
+ const app = context.app;
779
817
  // Build webhook context based on whether we have installation context
780
- if (envelope.context.appInstallationId && envelope.context.workplace) {
818
+ if (context.appInstallationId && context.workplace) {
781
819
  // Runtime webhook context
782
820
  webhookContext = {
783
821
  env: envVars,
784
822
  app,
785
- appInstallationId: envelope.context.appInstallationId,
786
- workplace: envelope.context.workplace,
787
- registration: envelope.context.registration ?? {},
823
+ appInstallationId: context.appInstallationId,
824
+ workplace: context.workplace,
825
+ registration: context.registration ?? {},
788
826
  };
789
827
  }
790
828
  else {
@@ -823,10 +861,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
823
861
  Object.assign(process.env, requestEnv);
824
862
  // Build request-scoped config for the skedyul client
825
863
  // This uses AsyncLocalStorage to override the global config (same pattern as tools)
826
- const requestConfig = {
827
- baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
828
- apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
829
- };
864
+ const requestConfig = buildRequestScopedConfig(requestEnv);
830
865
  // Invoke the handler with request-scoped config
831
866
  let webhookResponse;
832
867
  try {
@@ -905,9 +940,9 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
905
940
  sendJSON(res, 404, { error: 'OAuth callback handler not configured' });
906
941
  return;
907
942
  }
908
- let oauthCallbackBody = {};
943
+ let parsedBody;
909
944
  try {
910
- oauthCallbackBody = (await parseJSONBody(req));
945
+ parsedBody = await parseJSONBody(req);
911
946
  }
912
947
  catch {
913
948
  sendJSON(res, 400, {
@@ -915,20 +950,20 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
915
950
  });
916
951
  return;
917
952
  }
918
- if (!oauthCallbackBody.query) {
953
+ // Parse envelope using shared helper
954
+ const envelope = parseHandlerEnvelope(parsedBody);
955
+ if (!envelope) {
919
956
  sendJSON(res, 400, {
920
- error: { code: -32602, message: 'Missing query parameter' },
957
+ error: { code: -32602, message: 'Missing envelope format: expected { env, request }' },
921
958
  });
922
959
  return;
923
960
  }
924
- // Build request-scoped config for SDK access
925
- // Prefer fresh token from request body (generated by workflow), fall back to baked-in env
926
- const oauthCallbackRequestConfig = {
927
- baseUrl: oauthCallbackBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
928
- apiToken: oauthCallbackBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
929
- };
961
+ // Convert raw request to rich request using shared helper
962
+ const oauthRequest = buildRequestFromRaw(envelope.request);
963
+ // Build request-scoped config using shared helper
964
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
930
965
  const oauthCallbackContext = {
931
- query: oauthCallbackBody.query,
966
+ request: oauthRequest,
932
967
  };
933
968
  try {
934
969
  const oauthCallbackHook = config.hooks.oauth_callback;
@@ -1576,24 +1611,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1576
1611
  if (!config.hooks?.oauth_callback) {
1577
1612
  return createResponse(404, { error: 'OAuth callback handler not configured' }, headers);
1578
1613
  }
1579
- let oauthCallbackBody = {};
1614
+ let parsedBody;
1580
1615
  try {
1581
- oauthCallbackBody = event.body ? JSON.parse(event.body) : {};
1616
+ parsedBody = event.body ? JSON.parse(event.body) : {};
1582
1617
  }
1583
1618
  catch {
1584
1619
  return createResponse(400, { error: { code: -32700, message: 'Parse error' } }, headers);
1585
1620
  }
1586
- if (!oauthCallbackBody.query) {
1587
- return createResponse(400, { error: { code: -32602, message: 'Missing query parameter' } }, headers);
1621
+ // Parse envelope using shared helper
1622
+ const envelope = parseHandlerEnvelope(parsedBody);
1623
+ if (!envelope) {
1624
+ return createResponse(400, { error: { code: -32602, message: 'Missing envelope format: expected { env, request }' } }, headers);
1588
1625
  }
1589
- // Build request-scoped config for SDK access
1590
- // Prefer fresh token from request body (generated by workflow), fall back to baked-in env
1591
- const oauthCallbackRequestConfig = {
1592
- baseUrl: oauthCallbackBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
1593
- apiToken: oauthCallbackBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
1594
- };
1626
+ // Convert raw request to rich request using shared helper
1627
+ const oauthRequest = buildRequestFromRaw(envelope.request);
1628
+ // Build request-scoped config using shared helper
1629
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
1595
1630
  const oauthCallbackContext = {
1596
- query: oauthCallbackBody.query,
1631
+ request: oauthRequest,
1597
1632
  };
1598
1633
  try {
1599
1634
  const oauthCallbackHook = config.hooks.oauth_callback;
package/dist/types.d.ts CHANGED
@@ -274,7 +274,8 @@ export type HasOAuthCallback<Hooks extends ServerHooks> = Hooks extends {
274
274
  export type InstallHandlerResult<Hooks extends ServerHooks = ServerHooks> = HasOAuthCallback<Hooks> extends true ? InstallHandlerResponseOAuth : InstallHandlerResponseStandard;
275
275
  export type InstallHandler<Hooks extends ServerHooks = ServerHooks> = (ctx: InstallHandlerContext) => Promise<InstallHandlerResult<Hooks>>;
276
276
  export interface OAuthCallbackContext {
277
- query: Record<string, string>;
277
+ /** Full HTTP request from the OAuth provider */
278
+ request: WebhookRequest;
278
279
  }
279
280
  export interface OAuthCallbackResult {
280
281
  env?: Record<string, string>;
@@ -322,6 +323,19 @@ export interface ServerlessServerInstance {
322
323
  getHealthStatus(): HealthStatus;
323
324
  }
324
325
  export type SkedyulServerInstance = DedicatedServerInstance | ServerlessServerInstance;
326
+ /**
327
+ * Raw HTTP request shape sent over the wire in handler envelopes.
328
+ * This is the wire format used by both webhooks and OAuth callbacks.
329
+ * It gets converted to the rich WebhookRequest type at parse time.
330
+ */
331
+ export interface HandlerRawRequest {
332
+ method: string;
333
+ url: string;
334
+ path: string;
335
+ headers: Record<string, string>;
336
+ query: Record<string, string>;
337
+ body: string;
338
+ }
325
339
  /** Raw HTTP request received by webhooks */
326
340
  export interface WebhookRequest {
327
341
  method: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "0.2.151",
3
+ "version": "0.2.152",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",