skedyul 0.2.144 → 0.2.145

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
- 1770547343130
1
+ 1770604138552
package/dist/server.js CHANGED
@@ -445,6 +445,10 @@ function sendJSON(res, statusCode, data) {
445
445
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
446
446
  res.end(JSON.stringify(data));
447
447
  }
448
+ function sendHTML(res, statusCode, html) {
449
+ res.writeHead(statusCode, { 'Content-Type': 'text/html; charset=utf-8' });
450
+ res.end(html);
451
+ }
448
452
  function getDefaultHeaders(options) {
449
453
  return {
450
454
  'Content-Type': 'application/json',
@@ -947,6 +951,83 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
947
951
  }
948
952
  return;
949
953
  }
954
+ // Handle /oauth_callback endpoint for OAuth callbacks
955
+ if (pathname === '/oauth_callback' && req.method === 'GET') {
956
+ if (!config.hooks?.oauth_callback) {
957
+ sendHTML(res, 404, '<html><body><h1>OAuth callback handler not configured</h1></body></html>');
958
+ return;
959
+ }
960
+ // Parse query parameters
961
+ const url = new URL(req.url || '', `http://${req.headers.host}`);
962
+ const query = {};
963
+ url.searchParams.forEach((value, key) => {
964
+ query[key] = value;
965
+ });
966
+ // Decode state parameter (contains appInstallationId, workplace, app info)
967
+ let stateData = {};
968
+ if (query.state) {
969
+ try {
970
+ stateData = JSON.parse(Buffer.from(query.state, 'base64').toString('utf-8'));
971
+ }
972
+ catch {
973
+ sendHTML(res, 400, '<html><body><h1>Invalid state parameter</h1></body></html>');
974
+ return;
975
+ }
976
+ }
977
+ if (!stateData.appInstallationId || !stateData.workplace || !stateData.app) {
978
+ sendHTML(res, 400, '<html><body><h1>Missing required state data</h1></body></html>');
979
+ return;
980
+ }
981
+ // Get current env vars from request (if any)
982
+ // For OAuth callbacks, we might not have env vars yet, so use empty object
983
+ const env = {};
984
+ const oauthCallbackContext = {
985
+ query,
986
+ env,
987
+ workplace: stateData.workplace,
988
+ appInstallationId: stateData.appInstallationId,
989
+ app: stateData.app,
990
+ };
991
+ // Build request-scoped config for SDK access
992
+ // Use env from process or request if available
993
+ const oauthCallbackRequestConfig = {
994
+ baseUrl: process.env.SKEDYUL_API_URL ?? '',
995
+ apiToken: process.env.SKEDYUL_API_TOKEN ?? '',
996
+ };
997
+ try {
998
+ const oauthCallbackHook = config.hooks.oauth_callback;
999
+ const oauthCallbackHandler = typeof oauthCallbackHook === 'function'
1000
+ ? oauthCallbackHook
1001
+ : oauthCallbackHook.handler;
1002
+ const result = await (0, client_1.runWithConfig)(oauthCallbackRequestConfig, async () => {
1003
+ return await oauthCallbackHandler(oauthCallbackContext);
1004
+ });
1005
+ // Return custom HTML if provided, otherwise default success page
1006
+ const html = result.html ?? `
1007
+ <html>
1008
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
1009
+ <h1 style="color: #38a169;">✓ Authorization Successful</h1>
1010
+ <p>You can close this window and return to the app.</p>
1011
+ </body>
1012
+ </html>
1013
+ `;
1014
+ sendHTML(res, 200, html);
1015
+ }
1016
+ catch (err) {
1017
+ const errorMessage = err instanceof Error ? err.message : String(err ?? 'Unknown error');
1018
+ const errorHtml = `
1019
+ <html>
1020
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
1021
+ <h1 style="color: #e53e3e;">Authorization Failed</h1>
1022
+ <p>${errorMessage}</p>
1023
+ <p>You can close this window.</p>
1024
+ </body>
1025
+ </html>
1026
+ `;
1027
+ sendHTML(res, 500, errorHtml);
1028
+ }
1029
+ return;
1030
+ }
950
1031
  // Handle /provision endpoint for provision handlers
951
1032
  if (pathname === '/provision' && req.method === 'POST') {
952
1033
  if (!config.hooks?.provision) {
@@ -1491,6 +1572,81 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1491
1572
  }, headers);
1492
1573
  }
1493
1574
  }
1575
+ // Handle /oauth_callback endpoint for OAuth callbacks
1576
+ if (path === '/oauth_callback' && method === 'GET') {
1577
+ if (!config.hooks?.oauth_callback) {
1578
+ return createResponse(404, { error: 'OAuth callback handler not configured' }, { ...headers, 'Content-Type': 'text/html; charset=utf-8' });
1579
+ }
1580
+ // Parse query parameters from event
1581
+ const query = {};
1582
+ if (event.queryStringParameters) {
1583
+ Object.entries(event.queryStringParameters).forEach(([key, value]) => {
1584
+ if (value)
1585
+ query[key] = value;
1586
+ });
1587
+ }
1588
+ // Decode state parameter (contains appInstallationId, workplace, app info)
1589
+ let stateData = {};
1590
+ if (query.state) {
1591
+ try {
1592
+ stateData = JSON.parse(Buffer.from(query.state, 'base64').toString('utf-8'));
1593
+ }
1594
+ catch {
1595
+ return createResponse(400, { error: 'Invalid state parameter' }, { ...headers, 'Content-Type': 'text/html; charset=utf-8' });
1596
+ }
1597
+ }
1598
+ if (!stateData.appInstallationId || !stateData.workplace || !stateData.app) {
1599
+ return createResponse(400, { error: 'Missing required state data' }, { ...headers, 'Content-Type': 'text/html; charset=utf-8' });
1600
+ }
1601
+ // Get current env vars from request (if any)
1602
+ // For OAuth callbacks, we might not have env vars yet, so use empty object
1603
+ const env = {};
1604
+ const oauthCallbackContext = {
1605
+ query,
1606
+ env,
1607
+ workplace: stateData.workplace,
1608
+ appInstallationId: stateData.appInstallationId,
1609
+ app: stateData.app,
1610
+ };
1611
+ // Build request-scoped config for SDK access
1612
+ // Use env from process or request if available
1613
+ const oauthCallbackRequestConfig = {
1614
+ baseUrl: process.env.SKEDYUL_API_URL ?? '',
1615
+ apiToken: process.env.SKEDYUL_API_TOKEN ?? '',
1616
+ };
1617
+ try {
1618
+ const oauthCallbackHook = config.hooks.oauth_callback;
1619
+ const oauthCallbackHandler = typeof oauthCallbackHook === 'function'
1620
+ ? oauthCallbackHook
1621
+ : oauthCallbackHook.handler;
1622
+ const result = await (0, client_1.runWithConfig)(oauthCallbackRequestConfig, async () => {
1623
+ return await oauthCallbackHandler(oauthCallbackContext);
1624
+ });
1625
+ // Return custom HTML if provided, otherwise default success page
1626
+ const html = result.html ?? `
1627
+ <html>
1628
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
1629
+ <h1 style="color: #38a169;">✓ Authorization Successful</h1>
1630
+ <p>You can close this window and return to the app.</p>
1631
+ </body>
1632
+ </html>
1633
+ `;
1634
+ return createResponse(200, html, { ...headers, 'Content-Type': 'text/html; charset=utf-8' });
1635
+ }
1636
+ catch (err) {
1637
+ const errorMessage = err instanceof Error ? err.message : String(err ?? 'Unknown error');
1638
+ const errorHtml = `
1639
+ <html>
1640
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
1641
+ <h1 style="color: #e53e3e;">Authorization Failed</h1>
1642
+ <p>${errorMessage}</p>
1643
+ <p>You can close this window.</p>
1644
+ </body>
1645
+ </html>
1646
+ `;
1647
+ return createResponse(500, errorHtml, { ...headers, 'Content-Type': 'text/html; charset=utf-8' });
1648
+ }
1649
+ }
1494
1650
  if (path === '/health' && method === 'GET') {
1495
1651
  return createResponse(200, state.getHealthStatus(), headers);
1496
1652
  }
package/dist/types.d.ts CHANGED
@@ -192,6 +192,12 @@ export interface ServerHooks {
192
192
  /** Timeout in milliseconds. Defaults to 300000 (5 minutes) if not specified. */
193
193
  timeout?: number;
194
194
  };
195
+ /** Called when OAuth provider redirects back with authorization code */
196
+ oauth_callback?: OAuthCallbackHandler | {
197
+ handler: OAuthCallbackHandler;
198
+ /** Timeout in milliseconds. Defaults to 60000 (1 minute) if not specified. */
199
+ timeout?: number;
200
+ };
195
201
  }
196
202
  export interface SkedyulServerConfig {
197
203
  computeLayer: ComputeLayer;
@@ -221,6 +227,24 @@ export interface InstallHandlerResult {
221
227
  redirect?: string;
222
228
  }
223
229
  export type InstallHandler = (ctx: InstallHandlerContext) => Promise<InstallHandlerResult>;
230
+ export interface OAuthCallbackContext {
231
+ query: Record<string, string>;
232
+ env: Record<string, string>;
233
+ workplace: {
234
+ id: string;
235
+ subdomain: string;
236
+ };
237
+ appInstallationId: string;
238
+ app: {
239
+ id: string;
240
+ versionId: string;
241
+ };
242
+ }
243
+ export interface OAuthCallbackResult {
244
+ env?: Record<string, string>;
245
+ html?: string;
246
+ }
247
+ export type OAuthCallbackHandler = (ctx: OAuthCallbackContext) => Promise<OAuthCallbackResult>;
224
248
  export interface ProvisionHandlerContext {
225
249
  env: Record<string, string>;
226
250
  app: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "0.2.144",
3
+ "version": "0.2.145",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",