skedyul 0.2.151 → 0.2.153

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
+ 1770699014045
package/dist/server.js CHANGED
@@ -690,6 +690,79 @@ 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
+ // Check if parsedBody is an object with env and request properties
703
+ if (typeof parsedBody !== 'object' ||
704
+ parsedBody === null ||
705
+ Array.isArray(parsedBody) ||
706
+ !('env' in parsedBody) ||
707
+ !('request' in parsedBody)) {
708
+ return null;
709
+ }
710
+ const envelope = parsedBody;
711
+ // Validate env is an object (not null, not array)
712
+ if (typeof envelope.env !== 'object' ||
713
+ envelope.env === null ||
714
+ Array.isArray(envelope.env)) {
715
+ return null;
716
+ }
717
+ // Validate request is an object (structure validation happens in buildRequestFromRaw)
718
+ if (typeof envelope.request !== 'object' ||
719
+ envelope.request === null ||
720
+ Array.isArray(envelope.request)) {
721
+ return null;
722
+ }
723
+ return {
724
+ env: envelope.env,
725
+ request: envelope.request,
726
+ context: envelope.context,
727
+ };
728
+ }
729
+ /**
730
+ * Converts a raw HandlerRawRequest (wire format) to a rich WebhookRequest.
731
+ * Parses JSON body if content-type is application/json, creates Buffer rawBody.
732
+ */
733
+ function buildRequestFromRaw(raw) {
734
+ // Parse the original request body
735
+ let parsedBody = raw.body;
736
+ const contentType = raw.headers['content-type'] ?? '';
737
+ if (contentType.includes('application/json')) {
738
+ try {
739
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
740
+ }
741
+ catch {
742
+ // Keep as string if JSON parsing fails
743
+ parsedBody = raw.body;
744
+ }
745
+ }
746
+ return {
747
+ method: raw.method,
748
+ url: raw.url,
749
+ path: raw.path,
750
+ headers: raw.headers,
751
+ query: raw.query,
752
+ body: parsedBody,
753
+ rawBody: raw.body ? Buffer.from(raw.body, 'utf-8') : undefined,
754
+ };
755
+ }
756
+ /**
757
+ * Builds request-scoped config by merging env from envelope with process.env fallbacks.
758
+ * Used for SKEDYUL_API_TOKEN and SKEDYUL_API_URL overrides.
759
+ */
760
+ function buildRequestScopedConfig(env) {
761
+ return {
762
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? '',
763
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
764
+ };
765
+ }
693
766
  function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, webhookRegistry) {
694
767
  const port = getListeningPort(config);
695
768
  const httpServer = http_1.default.createServer(async (req, res) => {
@@ -742,49 +815,27 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
742
815
  }
743
816
  // Check if this is an envelope format from the platform
744
817
  // 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);
818
+ const envelope = parseHandlerEnvelope(parsedBody);
750
819
  let webhookRequest;
751
820
  let webhookContext;
752
821
  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;
822
+ if (envelope && 'context' in envelope && envelope.context) {
823
+ // Platform envelope format - use shared helpers
824
+ const context = envelope.context;
825
+ requestEnv = envelope.env;
826
+ // Convert raw request to rich request using shared helper
827
+ webhookRequest = buildRequestFromRaw(envelope.request);
828
+ const envVars = { ...process.env, ...envelope.env };
829
+ const app = context.app;
779
830
  // Build webhook context based on whether we have installation context
780
- if (envelope.context.appInstallationId && envelope.context.workplace) {
831
+ if (context.appInstallationId && context.workplace) {
781
832
  // Runtime webhook context
782
833
  webhookContext = {
783
834
  env: envVars,
784
835
  app,
785
- appInstallationId: envelope.context.appInstallationId,
786
- workplace: envelope.context.workplace,
787
- registration: envelope.context.registration ?? {},
836
+ appInstallationId: context.appInstallationId,
837
+ workplace: context.workplace,
838
+ registration: context.registration ?? {},
788
839
  };
789
840
  }
790
841
  else {
@@ -823,10 +874,7 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
823
874
  Object.assign(process.env, requestEnv);
824
875
  // Build request-scoped config for the skedyul client
825
876
  // 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
- };
877
+ const requestConfig = buildRequestScopedConfig(requestEnv);
830
878
  // Invoke the handler with request-scoped config
831
879
  let webhookResponse;
832
880
  try {
@@ -905,9 +953,9 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
905
953
  sendJSON(res, 404, { error: 'OAuth callback handler not configured' });
906
954
  return;
907
955
  }
908
- let oauthCallbackBody = {};
956
+ let parsedBody;
909
957
  try {
910
- oauthCallbackBody = (await parseJSONBody(req));
958
+ parsedBody = await parseJSONBody(req);
911
959
  }
912
960
  catch {
913
961
  sendJSON(res, 400, {
@@ -915,20 +963,20 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
915
963
  });
916
964
  return;
917
965
  }
918
- if (!oauthCallbackBody.query) {
966
+ // Parse envelope using shared helper
967
+ const envelope = parseHandlerEnvelope(parsedBody);
968
+ if (!envelope) {
919
969
  sendJSON(res, 400, {
920
- error: { code: -32602, message: 'Missing query parameter' },
970
+ error: { code: -32602, message: 'Missing envelope format: expected { env, request }' },
921
971
  });
922
972
  return;
923
973
  }
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
- };
974
+ // Convert raw request to rich request using shared helper
975
+ const oauthRequest = buildRequestFromRaw(envelope.request);
976
+ // Build request-scoped config using shared helper
977
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
930
978
  const oauthCallbackContext = {
931
- query: oauthCallbackBody.query,
979
+ request: oauthRequest,
932
980
  };
933
981
  try {
934
982
  const oauthCallbackHook = config.hooks.oauth_callback;
@@ -1576,24 +1624,24 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1576
1624
  if (!config.hooks?.oauth_callback) {
1577
1625
  return createResponse(404, { error: 'OAuth callback handler not configured' }, headers);
1578
1626
  }
1579
- let oauthCallbackBody = {};
1627
+ let parsedBody;
1580
1628
  try {
1581
- oauthCallbackBody = event.body ? JSON.parse(event.body) : {};
1629
+ parsedBody = event.body ? JSON.parse(event.body) : {};
1582
1630
  }
1583
1631
  catch {
1584
1632
  return createResponse(400, { error: { code: -32700, message: 'Parse error' } }, headers);
1585
1633
  }
1586
- if (!oauthCallbackBody.query) {
1587
- return createResponse(400, { error: { code: -32602, message: 'Missing query parameter' } }, headers);
1634
+ // Parse envelope using shared helper
1635
+ const envelope = parseHandlerEnvelope(parsedBody);
1636
+ if (!envelope) {
1637
+ return createResponse(400, { error: { code: -32602, message: 'Missing envelope format: expected { env, request }' } }, headers);
1588
1638
  }
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
- };
1639
+ // Convert raw request to rich request using shared helper
1640
+ const oauthRequest = buildRequestFromRaw(envelope.request);
1641
+ // Build request-scoped config using shared helper
1642
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
1595
1643
  const oauthCallbackContext = {
1596
- query: oauthCallbackBody.query,
1644
+ request: oauthRequest,
1597
1645
  };
1598
1646
  try {
1599
1647
  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.153",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",