@shipfox/api-integration-slack 4.0.0

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.
Files changed (158) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +22 -0
  5. package/LICENSE +21 -0
  6. package/dist/api/client.d.ts +28 -0
  7. package/dist/api/client.d.ts.map +1 -0
  8. package/dist/api/client.js +163 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/config.d.ts +8 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +21 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core/agent-tools-provider.d.ts +75 -0
  15. package/dist/core/agent-tools-provider.d.ts.map +1 -0
  16. package/dist/core/agent-tools-provider.js +111 -0
  17. package/dist/core/agent-tools-provider.js.map +1 -0
  18. package/dist/core/agent-tools.d.ts +65 -0
  19. package/dist/core/agent-tools.d.ts.map +1 -0
  20. package/dist/core/agent-tools.js +177 -0
  21. package/dist/core/agent-tools.js.map +1 -0
  22. package/dist/core/disconnect.d.ts +18 -0
  23. package/dist/core/disconnect.d.ts.map +1 -0
  24. package/dist/core/disconnect.js +23 -0
  25. package/dist/core/disconnect.js.map +1 -0
  26. package/dist/core/errors.d.ts +44 -0
  27. package/dist/core/errors.d.ts.map +1 -0
  28. package/dist/core/errors.js +71 -0
  29. package/dist/core/errors.js.map +1 -0
  30. package/dist/core/install.d.ts +48 -0
  31. package/dist/core/install.d.ts.map +1 -0
  32. package/dist/core/install.js +88 -0
  33. package/dist/core/install.js.map +1 -0
  34. package/dist/core/scopes.d.ts +4 -0
  35. package/dist/core/scopes.d.ts.map +1 -0
  36. package/dist/core/scopes.js +24 -0
  37. package/dist/core/scopes.js.map +1 -0
  38. package/dist/core/signature.d.ts +10 -0
  39. package/dist/core/signature.d.ts.map +1 -0
  40. package/dist/core/signature.js +18 -0
  41. package/dist/core/signature.js.map +1 -0
  42. package/dist/core/state.d.ts +12 -0
  43. package/dist/core/state.d.ts.map +1 -0
  44. package/dist/core/state.js +54 -0
  45. package/dist/core/state.js.map +1 -0
  46. package/dist/core/tokens.d.ts +35 -0
  47. package/dist/core/tokens.d.ts.map +1 -0
  48. package/dist/core/tokens.js +51 -0
  49. package/dist/core/tokens.js.map +1 -0
  50. package/dist/core/webhook.d.ts +25 -0
  51. package/dist/core/webhook.d.ts.map +1 -0
  52. package/dist/core/webhook.js +160 -0
  53. package/dist/core/webhook.js.map +1 -0
  54. package/dist/db/db.d.ts +403 -0
  55. package/dist/db/db.d.ts.map +1 -0
  56. package/dist/db/db.js +18 -0
  57. package/dist/db/db.js.map +1 -0
  58. package/dist/db/installations.d.ts +40 -0
  59. package/dist/db/installations.d.ts.map +1 -0
  60. package/dist/db/installations.js +73 -0
  61. package/dist/db/installations.js.map +1 -0
  62. package/dist/db/migrations.d.ts +2 -0
  63. package/dist/db/migrations.d.ts.map +1 -0
  64. package/dist/db/migrations.js +5 -0
  65. package/dist/db/migrations.js.map +1 -0
  66. package/dist/db/schema/common.d.ts +2 -0
  67. package/dist/db/schema/common.d.ts.map +1 -0
  68. package/dist/db/schema/common.js +4 -0
  69. package/dist/db/schema/common.js.map +1 -0
  70. package/dist/db/schema/installations.d.ts +203 -0
  71. package/dist/db/schema/installations.d.ts.map +1 -0
  72. package/dist/db/schema/installations.js +42 -0
  73. package/dist/db/schema/installations.js.map +1 -0
  74. package/dist/index.d.ts +59 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +73 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/presentation/dto/integrations.d.ts +2 -0
  79. package/dist/presentation/dto/integrations.d.ts.map +1 -0
  80. package/dist/presentation/dto/integrations.js +3 -0
  81. package/dist/presentation/dto/integrations.js.map +1 -0
  82. package/dist/presentation/e2eRoutes/create-connection.d.ts +16 -0
  83. package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -0
  84. package/dist/presentation/e2eRoutes/create-connection.js +65 -0
  85. package/dist/presentation/e2eRoutes/create-connection.js.map +1 -0
  86. package/dist/presentation/e2eRoutes/index.d.ts +5 -0
  87. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  88. package/dist/presentation/e2eRoutes/index.js +11 -0
  89. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  90. package/dist/presentation/routes/errors.d.ts +2 -0
  91. package/dist/presentation/routes/errors.d.ts.map +1 -0
  92. package/dist/presentation/routes/errors.js +75 -0
  93. package/dist/presentation/routes/errors.js.map +1 -0
  94. package/dist/presentation/routes/install.d.ts +19 -0
  95. package/dist/presentation/routes/install.d.ts.map +1 -0
  96. package/dist/presentation/routes/install.js +98 -0
  97. package/dist/presentation/routes/install.js.map +1 -0
  98. package/dist/presentation/routes/webhooks.d.ts +16 -0
  99. package/dist/presentation/routes/webhooks.d.ts.map +1 -0
  100. package/dist/presentation/routes/webhooks.js +166 -0
  101. package/dist/presentation/routes/webhooks.js.map +1 -0
  102. package/dist/tsconfig.test.tsbuildinfo +1 -0
  103. package/drizzle/0000_calm_fantastic_four.sql +16 -0
  104. package/drizzle/meta/0000_snapshot.json +132 -0
  105. package/drizzle/meta/_journal.json +13 -0
  106. package/drizzle.config.ts +7 -0
  107. package/package.json +71 -0
  108. package/src/api/client.test.ts +239 -0
  109. package/src/api/client.ts +243 -0
  110. package/src/config.test.ts +32 -0
  111. package/src/config.ts +20 -0
  112. package/src/connection-external-url.test.ts +25 -0
  113. package/src/core/agent-tools-provider.test.ts +256 -0
  114. package/src/core/agent-tools-provider.ts +147 -0
  115. package/src/core/agent-tools.test.ts +90 -0
  116. package/src/core/agent-tools.ts +189 -0
  117. package/src/core/disconnect.ts +26 -0
  118. package/src/core/errors.ts +92 -0
  119. package/src/core/install.test.ts +155 -0
  120. package/src/core/install.ts +137 -0
  121. package/src/core/scopes.test.ts +22 -0
  122. package/src/core/scopes.ts +25 -0
  123. package/src/core/signature.test.ts +45 -0
  124. package/src/core/signature.ts +41 -0
  125. package/src/core/state.test.ts +48 -0
  126. package/src/core/state.ts +82 -0
  127. package/src/core/tokens.test.ts +163 -0
  128. package/src/core/tokens.ts +98 -0
  129. package/src/core/webhook.test.ts +269 -0
  130. package/src/core/webhook.ts +223 -0
  131. package/src/db/db.ts +16 -0
  132. package/src/db/installations.test.ts +93 -0
  133. package/src/db/installations.ts +141 -0
  134. package/src/db/migrations.ts +4 -0
  135. package/src/db/schema/common.ts +3 -0
  136. package/src/db/schema/installations.ts +44 -0
  137. package/src/index.test.ts +25 -0
  138. package/src/index.ts +196 -0
  139. package/src/presentation/dto/integrations.ts +1 -0
  140. package/src/presentation/e2eRoutes/create-connection.ts +82 -0
  141. package/src/presentation/e2eRoutes/index.test.ts +96 -0
  142. package/src/presentation/e2eRoutes/index.ts +11 -0
  143. package/src/presentation/routes/errors.ts +68 -0
  144. package/src/presentation/routes/install.test.ts +229 -0
  145. package/src/presentation/routes/install.ts +117 -0
  146. package/src/presentation/routes/webhooks.test.ts +266 -0
  147. package/src/presentation/routes/webhooks.ts +170 -0
  148. package/test/api-secrets.d.ts +26 -0
  149. package/test/env.ts +13 -0
  150. package/test/factories/index.ts +1 -0
  151. package/test/globalSetup.ts +23 -0
  152. package/test/index.ts +1 -0
  153. package/test/setup.ts +17 -0
  154. package/tsconfig.build.json +9 -0
  155. package/tsconfig.build.tsbuildinfo +1 -0
  156. package/tsconfig.json +3 -0
  157. package/tsconfig.test.json +9 -0
  158. package/vitest.config.ts +12 -0
@@ -0,0 +1,12 @@
1
+ export interface SlackInstallStateClaims {
2
+ workspaceId: string;
3
+ userId: string;
4
+ }
5
+ export declare function signSlackInstallState(params: {
6
+ workspaceId: string;
7
+ userId: string;
8
+ nonce?: string | undefined;
9
+ now?: Date | undefined;
10
+ }): string;
11
+ export declare function verifySlackInstallState(state: string, now?: Date): SlackInstallStateClaims;
12
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACxB,GAAG,MAAM,CAUT;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,IAAiB,GACrB,uBAAuB,CAazB"}
@@ -0,0 +1,54 @@
1
+ import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { config } from '#config.js';
3
+ import { SlackInstallStateError } from './errors.js';
4
+ const STATE_TTL_SECONDS = 30 * 60;
5
+ export function signSlackInstallState(params) {
6
+ const now = params.now ?? new Date();
7
+ const payload = {
8
+ workspaceId: params.workspaceId,
9
+ userId: params.userId,
10
+ nonce: params.nonce ?? randomUUID(),
11
+ expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS
12
+ };
13
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
14
+ return `${encodedPayload}.${sign(encodedPayload)}`;
15
+ }
16
+ export function verifySlackInstallState(state, now = new Date()) {
17
+ const [encodedPayload, signature, extra] = state.split('.');
18
+ if (!encodedPayload || !signature || extra !== undefined) {
19
+ throw new SlackInstallStateError('Invalid Slack install state');
20
+ }
21
+ if (!constantTimeEqual(signature, sign(encodedPayload))) {
22
+ throw new SlackInstallStateError('Invalid Slack install state signature');
23
+ }
24
+ const payload = parsePayload(encodedPayload);
25
+ if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {
26
+ throw new SlackInstallStateError('Expired Slack install state');
27
+ }
28
+ return {
29
+ workspaceId: payload.workspaceId,
30
+ userId: payload.userId
31
+ };
32
+ }
33
+ function sign(encodedPayload) {
34
+ return createHmac('sha256', config.SLACK_OAUTH_CLIENT_SECRET).update(encodedPayload).digest('base64url');
35
+ }
36
+ function constantTimeEqual(a, b) {
37
+ const left = Buffer.from(a);
38
+ const right = Buffer.from(b);
39
+ if (left.length !== right.length) return false;
40
+ return timingSafeEqual(left, right);
41
+ }
42
+ function parsePayload(encodedPayload) {
43
+ try {
44
+ const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
45
+ if (typeof parsed.workspaceId !== 'string' || typeof parsed.userId !== 'string' || typeof parsed.nonce !== 'string' || typeof parsed.expiresAt !== 'number') {
46
+ throw new Error('Invalid payload shape');
47
+ }
48
+ return parsed;
49
+ } catch (_error) {
50
+ throw new SlackInstallStateError('Invalid Slack install state payload');
51
+ }
52
+ }
53
+
54
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/state.ts"],"sourcesContent":["import {createHmac, randomUUID, timingSafeEqual} from 'node:crypto';\nimport {config} from '#config.js';\nimport {SlackInstallStateError} from './errors.js';\n\nconst STATE_TTL_SECONDS = 30 * 60;\n\ninterface SlackInstallStatePayload {\n workspaceId: string;\n userId: string;\n nonce: string;\n expiresAt: number;\n}\n\nexport interface SlackInstallStateClaims {\n workspaceId: string;\n userId: string;\n}\n\nexport function signSlackInstallState(params: {\n workspaceId: string;\n userId: string;\n nonce?: string | undefined;\n now?: Date | undefined;\n}): string {\n const now = params.now ?? new Date();\n const payload: SlackInstallStatePayload = {\n workspaceId: params.workspaceId,\n userId: params.userId,\n nonce: params.nonce ?? randomUUID(),\n expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS,\n };\n const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');\n return `${encodedPayload}.${sign(encodedPayload)}`;\n}\n\nexport function verifySlackInstallState(\n state: string,\n now: Date = new Date(),\n): SlackInstallStateClaims {\n const [encodedPayload, signature, extra] = state.split('.');\n if (!encodedPayload || !signature || extra !== undefined) {\n throw new SlackInstallStateError('Invalid Slack install state');\n }\n if (!constantTimeEqual(signature, sign(encodedPayload))) {\n throw new SlackInstallStateError('Invalid Slack install state signature');\n }\n const payload = parsePayload(encodedPayload);\n if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {\n throw new SlackInstallStateError('Expired Slack install state');\n }\n return {workspaceId: payload.workspaceId, userId: payload.userId};\n}\n\nfunction sign(encodedPayload: string): string {\n return createHmac('sha256', config.SLACK_OAUTH_CLIENT_SECRET)\n .update(encodedPayload)\n .digest('base64url');\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a);\n const right = Buffer.from(b);\n if (left.length !== right.length) return false;\n return timingSafeEqual(left, right);\n}\n\nfunction parsePayload(encodedPayload: string): SlackInstallStatePayload {\n try {\n const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));\n if (\n typeof parsed.workspaceId !== 'string' ||\n typeof parsed.userId !== 'string' ||\n typeof parsed.nonce !== 'string' ||\n typeof parsed.expiresAt !== 'number'\n ) {\n throw new Error('Invalid payload shape');\n }\n return parsed;\n } catch (_error) {\n throw new SlackInstallStateError('Invalid Slack install state payload');\n }\n}\n"],"names":["createHmac","randomUUID","timingSafeEqual","config","SlackInstallStateError","STATE_TTL_SECONDS","signSlackInstallState","params","now","Date","payload","workspaceId","userId","nonce","expiresAt","Math","floor","getTime","encodedPayload","Buffer","from","JSON","stringify","toString","sign","verifySlackInstallState","state","signature","extra","split","undefined","constantTimeEqual","parsePayload","SLACK_OAUTH_CLIENT_SECRET","update","digest","a","b","left","right","length","parsed","parse","Error","_error"],"mappings":"AAAA,SAAQA,UAAU,EAAEC,UAAU,EAAEC,eAAe,QAAO,cAAc;AACpE,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,sBAAsB,QAAO,cAAc;AAEnD,MAAMC,oBAAoB,KAAK;AAc/B,OAAO,SAASC,sBAAsBC,MAKrC;IACC,MAAMC,MAAMD,OAAOC,GAAG,IAAI,IAAIC;IAC9B,MAAMC,UAAoC;QACxCC,aAAaJ,OAAOI,WAAW;QAC/BC,QAAQL,OAAOK,MAAM;QACrBC,OAAON,OAAOM,KAAK,IAAIZ;QACvBa,WAAWC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,QAAQZ;IAChD;IACA,MAAMa,iBAAiBC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACZ,UAAUa,QAAQ,CAAC;IACrE,OAAO,GAAGL,eAAe,CAAC,EAAEM,KAAKN,iBAAiB;AACpD;AAEA,OAAO,SAASO,wBACdC,KAAa,EACblB,MAAY,IAAIC,MAAM;IAEtB,MAAM,CAACS,gBAAgBS,WAAWC,MAAM,GAAGF,MAAMG,KAAK,CAAC;IACvD,IAAI,CAACX,kBAAkB,CAACS,aAAaC,UAAUE,WAAW;QACxD,MAAM,IAAI1B,uBAAuB;IACnC;IACA,IAAI,CAAC2B,kBAAkBJ,WAAWH,KAAKN,kBAAkB;QACvD,MAAM,IAAId,uBAAuB;IACnC;IACA,MAAMM,UAAUsB,aAAad;IAC7B,IAAIR,QAAQI,SAAS,GAAGC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,OAAO;QACxD,MAAM,IAAIb,uBAAuB;IACnC;IACA,OAAO;QAACO,aAAaD,QAAQC,WAAW;QAAEC,QAAQF,QAAQE,MAAM;IAAA;AAClE;AAEA,SAASY,KAAKN,cAAsB;IAClC,OAAOlB,WAAW,UAAUG,OAAO8B,yBAAyB,EACzDC,MAAM,CAAChB,gBACPiB,MAAM,CAAC;AACZ;AAEA,SAASJ,kBAAkBK,CAAS,EAAEC,CAAS;IAC7C,MAAMC,OAAOnB,OAAOC,IAAI,CAACgB;IACzB,MAAMG,QAAQpB,OAAOC,IAAI,CAACiB;IAC1B,IAAIC,KAAKE,MAAM,KAAKD,MAAMC,MAAM,EAAE,OAAO;IACzC,OAAOtC,gBAAgBoC,MAAMC;AAC/B;AAEA,SAASP,aAAad,cAAsB;IAC1C,IAAI;QACF,MAAMuB,SAASpB,KAAKqB,KAAK,CAACvB,OAAOC,IAAI,CAACF,gBAAgB,aAAaK,QAAQ,CAAC;QAC5E,IACE,OAAOkB,OAAO9B,WAAW,KAAK,YAC9B,OAAO8B,OAAO7B,MAAM,KAAK,YACzB,OAAO6B,OAAO5B,KAAK,KAAK,YACxB,OAAO4B,OAAO3B,SAAS,KAAK,UAC5B;YACA,MAAM,IAAI6B,MAAM;QAClB;QACA,OAAOF;IACT,EAAE,OAAOG,QAAQ;QACf,MAAM,IAAIxC,uBAAuB;IACnC;AACF"}
@@ -0,0 +1,35 @@
1
+ export interface SlackConnectionResolverResult {
2
+ workspaceId: string;
3
+ }
4
+ export interface SlackSecretsStore {
5
+ getSecret(params: {
6
+ workspaceId: string;
7
+ namespace: string;
8
+ key: string;
9
+ }): Promise<string | null>;
10
+ setSecrets(params: {
11
+ workspaceId: string;
12
+ namespace: string;
13
+ values: Record<string, string>;
14
+ editedBy?: string | null | undefined;
15
+ }): Promise<void>;
16
+ }
17
+ export interface CreateSlackTokenStoreParams {
18
+ resolveConnection(connectionId: string): Promise<SlackConnectionResolverResult | undefined>;
19
+ secrets: SlackSecretsStore;
20
+ }
21
+ export interface StoreSlackTokensParams {
22
+ connectionId: string;
23
+ botToken: string;
24
+ editedBy?: string | null | undefined;
25
+ }
26
+ export interface GetSlackAccessTokenParams {
27
+ connectionId: string;
28
+ }
29
+ export interface SlackTokenStore {
30
+ storeTokens(params: StoreSlackTokensParams): Promise<void>;
31
+ getAccessToken(params: GetSlackAccessTokenParams): Promise<string>;
32
+ }
33
+ export declare function slackSecretsNamespace(connectionId: string): string;
34
+ export declare function createSlackTokenStore(params: CreateSlackTokenStoreParams): SlackTokenStore;
35
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/core/tokens.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjG,UAAU,CAAC,MAAM,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;KACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC,CAAC;IAC5F,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,cAAc,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAqBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,eAAe,CA+B1F"}
@@ -0,0 +1,51 @@
1
+ import { SlackAccessTokenUnavailableError, SlackBotTokenMissingError, SlackConnectionNotFoundError } from '#core/errors.js';
2
+ import { getSlackInstallationByConnectionId } from '#db/installations.js';
3
+ const BOT_TOKEN_KEY = 'BOT_TOKEN';
4
+ export function slackSecretsNamespace(connectionId) {
5
+ return `system/integrations/slack/${connectionId}`;
6
+ }
7
+ function assertSlackInstallationServesToken(connectionId, installation) {
8
+ if (!installation) {
9
+ throw new SlackAccessTokenUnavailableError(connectionId, 'installation-not-found');
10
+ }
11
+ if (installation.status !== 'installed') {
12
+ throw new SlackAccessTokenUnavailableError(connectionId, 'not-installed');
13
+ }
14
+ if (installation.tokenExpiresAt !== null && installation.tokenExpiresAt.getTime() <= Date.now()) {
15
+ throw new SlackAccessTokenUnavailableError(connectionId, 'expired', installation.tokenExpiresAt);
16
+ }
17
+ }
18
+ export function createSlackTokenStore(params) {
19
+ async function resolveWorkspaceId(connectionId) {
20
+ const connection = await params.resolveConnection(connectionId);
21
+ if (!connection) throw new SlackConnectionNotFoundError(connectionId);
22
+ return connection.workspaceId;
23
+ }
24
+ return {
25
+ async storeTokens (input) {
26
+ const workspaceId = await resolveWorkspaceId(input.connectionId);
27
+ await params.secrets.setSecrets({
28
+ workspaceId,
29
+ namespace: slackSecretsNamespace(input.connectionId),
30
+ values: {
31
+ [BOT_TOKEN_KEY]: input.botToken
32
+ },
33
+ editedBy: input.editedBy
34
+ });
35
+ },
36
+ async getAccessToken (input) {
37
+ const workspaceId = await resolveWorkspaceId(input.connectionId);
38
+ const installation = await getSlackInstallationByConnectionId(input.connectionId);
39
+ assertSlackInstallationServesToken(input.connectionId, installation);
40
+ const token = await params.secrets.getSecret({
41
+ workspaceId,
42
+ namespace: slackSecretsNamespace(input.connectionId),
43
+ key: BOT_TOKEN_KEY
44
+ });
45
+ if (!token) throw new SlackBotTokenMissingError(input.connectionId);
46
+ return token;
47
+ }
48
+ };
49
+ }
50
+
51
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/tokens.ts"],"sourcesContent":["import {\n SlackAccessTokenUnavailableError,\n SlackBotTokenMissingError,\n SlackConnectionNotFoundError,\n} from '#core/errors.js';\nimport {getSlackInstallationByConnectionId, type SlackInstallation} from '#db/installations.js';\n\nconst BOT_TOKEN_KEY = 'BOT_TOKEN';\n\nexport interface SlackConnectionResolverResult {\n workspaceId: string;\n}\n\nexport interface SlackSecretsStore {\n getSecret(params: {workspaceId: string; namespace: string; key: string}): Promise<string | null>;\n setSecrets(params: {\n workspaceId: string;\n namespace: string;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n }): Promise<void>;\n}\n\nexport interface CreateSlackTokenStoreParams {\n resolveConnection(connectionId: string): Promise<SlackConnectionResolverResult | undefined>;\n secrets: SlackSecretsStore;\n}\n\nexport interface StoreSlackTokensParams {\n connectionId: string;\n botToken: string;\n editedBy?: string | null | undefined;\n}\n\nexport interface GetSlackAccessTokenParams {\n connectionId: string;\n}\n\nexport interface SlackTokenStore {\n storeTokens(params: StoreSlackTokensParams): Promise<void>;\n getAccessToken(params: GetSlackAccessTokenParams): Promise<string>;\n}\n\nexport function slackSecretsNamespace(connectionId: string): string {\n return `system/integrations/slack/${connectionId}`;\n}\n\nfunction assertSlackInstallationServesToken(\n connectionId: string,\n installation: SlackInstallation | undefined,\n): asserts installation is SlackInstallation {\n if (!installation) {\n throw new SlackAccessTokenUnavailableError(connectionId, 'installation-not-found');\n }\n if (installation.status !== 'installed') {\n throw new SlackAccessTokenUnavailableError(connectionId, 'not-installed');\n }\n if (installation.tokenExpiresAt !== null && installation.tokenExpiresAt.getTime() <= Date.now()) {\n throw new SlackAccessTokenUnavailableError(\n connectionId,\n 'expired',\n installation.tokenExpiresAt,\n );\n }\n}\n\nexport function createSlackTokenStore(params: CreateSlackTokenStoreParams): SlackTokenStore {\n async function resolveWorkspaceId(connectionId: string): Promise<string> {\n const connection = await params.resolveConnection(connectionId);\n if (!connection) throw new SlackConnectionNotFoundError(connectionId);\n return connection.workspaceId;\n }\n\n return {\n async storeTokens(input) {\n const workspaceId = await resolveWorkspaceId(input.connectionId);\n await params.secrets.setSecrets({\n workspaceId,\n namespace: slackSecretsNamespace(input.connectionId),\n values: {[BOT_TOKEN_KEY]: input.botToken},\n editedBy: input.editedBy,\n });\n },\n\n async getAccessToken(input) {\n const workspaceId = await resolveWorkspaceId(input.connectionId);\n const installation = await getSlackInstallationByConnectionId(input.connectionId);\n assertSlackInstallationServesToken(input.connectionId, installation);\n const token = await params.secrets.getSecret({\n workspaceId,\n namespace: slackSecretsNamespace(input.connectionId),\n key: BOT_TOKEN_KEY,\n });\n if (!token) throw new SlackBotTokenMissingError(input.connectionId);\n return token;\n },\n };\n}\n"],"names":["SlackAccessTokenUnavailableError","SlackBotTokenMissingError","SlackConnectionNotFoundError","getSlackInstallationByConnectionId","BOT_TOKEN_KEY","slackSecretsNamespace","connectionId","assertSlackInstallationServesToken","installation","status","tokenExpiresAt","getTime","Date","now","createSlackTokenStore","params","resolveWorkspaceId","connection","resolveConnection","workspaceId","storeTokens","input","secrets","setSecrets","namespace","values","botToken","editedBy","getAccessToken","token","getSecret","key"],"mappings":"AAAA,SACEA,gCAAgC,EAChCC,yBAAyB,EACzBC,4BAA4B,QACvB,kBAAkB;AACzB,SAAQC,kCAAkC,QAA+B,uBAAuB;AAEhG,MAAMC,gBAAgB;AAoCtB,OAAO,SAASC,sBAAsBC,YAAoB;IACxD,OAAO,CAAC,0BAA0B,EAAEA,cAAc;AACpD;AAEA,SAASC,mCACPD,YAAoB,EACpBE,YAA2C;IAE3C,IAAI,CAACA,cAAc;QACjB,MAAM,IAAIR,iCAAiCM,cAAc;IAC3D;IACA,IAAIE,aAAaC,MAAM,KAAK,aAAa;QACvC,MAAM,IAAIT,iCAAiCM,cAAc;IAC3D;IACA,IAAIE,aAAaE,cAAc,KAAK,QAAQF,aAAaE,cAAc,CAACC,OAAO,MAAMC,KAAKC,GAAG,IAAI;QAC/F,MAAM,IAAIb,iCACRM,cACA,WACAE,aAAaE,cAAc;IAE/B;AACF;AAEA,OAAO,SAASI,sBAAsBC,MAAmC;IACvE,eAAeC,mBAAmBV,YAAoB;QACpD,MAAMW,aAAa,MAAMF,OAAOG,iBAAiB,CAACZ;QAClD,IAAI,CAACW,YAAY,MAAM,IAAIf,6BAA6BI;QACxD,OAAOW,WAAWE,WAAW;IAC/B;IAEA,OAAO;QACL,MAAMC,aAAYC,KAAK;YACrB,MAAMF,cAAc,MAAMH,mBAAmBK,MAAMf,YAAY;YAC/D,MAAMS,OAAOO,OAAO,CAACC,UAAU,CAAC;gBAC9BJ;gBACAK,WAAWnB,sBAAsBgB,MAAMf,YAAY;gBACnDmB,QAAQ;oBAAC,CAACrB,cAAc,EAAEiB,MAAMK,QAAQ;gBAAA;gBACxCC,UAAUN,MAAMM,QAAQ;YAC1B;QACF;QAEA,MAAMC,gBAAeP,KAAK;YACxB,MAAMF,cAAc,MAAMH,mBAAmBK,MAAMf,YAAY;YAC/D,MAAME,eAAe,MAAML,mCAAmCkB,MAAMf,YAAY;YAChFC,mCAAmCc,MAAMf,YAAY,EAAEE;YACvD,MAAMqB,QAAQ,MAAMd,OAAOO,OAAO,CAACQ,SAAS,CAAC;gBAC3CX;gBACAK,WAAWnB,sBAAsBgB,MAAMf,YAAY;gBACnDyB,KAAK3B;YACP;YACA,IAAI,CAACyB,OAAO,MAAM,IAAI5B,0BAA0BoB,MAAMf,YAAY;YAClE,OAAOuB;QACT;IACF;AACF"}
@@ -0,0 +1,25 @@
1
+ import type { GetIntegrationConnectionByIdFn, IntegrationTx, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
2
+ import { type SlackEventBaseEnvelopeDto, type SlackSlashCommandDto } from '@shipfox/api-integration-slack-dto';
3
+ export type SlackWebhookOutcome = 'published' | 'duplicate' | 'unknown-team' | 'revoked-installation' | 'missing-connection' | 'inactive-connection' | 'unsupported-event' | 'self-message';
4
+ interface SlackWebhookParams {
5
+ tx: IntegrationTx;
6
+ deliveryId: string;
7
+ publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
8
+ recordDeliveryOnly: RecordDeliveryOnlyFn;
9
+ getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
10
+ }
11
+ export interface HandleSlackEventParams extends SlackWebhookParams {
12
+ envelope: SlackEventBaseEnvelopeDto;
13
+ }
14
+ export interface HandleSlackCommandParams extends SlackWebhookParams {
15
+ command: SlackSlashCommandDto;
16
+ }
17
+ export declare function handleSlackEvent(params: HandleSlackEventParams): Promise<{
18
+ outcome: SlackWebhookOutcome;
19
+ }>;
20
+ export declare function handleSlackCommand(params: HandleSlackCommandParams): Promise<{
21
+ outcome: Exclude<SlackWebhookOutcome, 'unsupported-event' | 'self-message'>;
22
+ }>;
23
+ export declare function isSelfAuthoredSlackEvent(event: unknown, botUserId: string): boolean;
24
+ export {};
25
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAE9B,aAAa,EACb,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAE1B,MAAM,oCAAoC,CAAC;AAmB5C,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,WAAW,GACX,cAAc,GACd,sBAAsB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,cAAc,CAAC;AAEnB,UAAU,kBAAkB;IAC1B,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,QAAQ,EAAE,yBAAyB,CAAC;CACrC;AAED,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AAYD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAgDzC;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,cAAc,CAAC,CAAA;CAAC,CAAC,CAwBxF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUnF"}
@@ -0,0 +1,160 @@
1
+ import { SLACK_PROVIDER, SLACK_SLASH_COMMAND_EVENT, slackEventEnvelopeSchema } from '@shipfox/api-integration-slack-dto';
2
+ import { logger } from '@shipfox/node-opentelemetry';
3
+ import { z } from 'zod';
4
+ import { getSlackInstallationByTeamId } from '#db/installations.js';
5
+ const slackSelfAuthoredEventSchema = z.object({
6
+ bot_id: z.string().optional(),
7
+ user: z.string().optional(),
8
+ message: z.object({
9
+ bot_id: z.string().optional(),
10
+ user: z.string().optional()
11
+ }).passthrough().optional()
12
+ }).passthrough();
13
+ export async function handleSlackEvent(params) {
14
+ const resolution = await resolveSlackConnection({
15
+ ...params,
16
+ teamId: params.envelope.team_id
17
+ });
18
+ if (resolution.kind === 'drop') return resolution;
19
+ if (isSelfAuthoredSlackEvent(params.envelope.event, resolution.installation.botUserId)) {
20
+ await recordSlackDeliveryOnly(params);
21
+ return {
22
+ outcome: 'self-message'
23
+ };
24
+ }
25
+ const supported = slackEventEnvelopeSchema.safeParse(params.envelope);
26
+ if (!supported.success) {
27
+ logger().info({
28
+ deliveryId: params.deliveryId,
29
+ teamId: params.envelope.team_id,
30
+ type: params.envelope.event.type
31
+ }, 'slack webhook: unsupported event, dropping');
32
+ await recordSlackDeliveryOnly(params);
33
+ return {
34
+ outcome: 'unsupported-event'
35
+ };
36
+ }
37
+ const result = await params.publishIntegrationEventReceived({
38
+ tx: params.tx,
39
+ event: {
40
+ provider: SLACK_PROVIDER,
41
+ source: resolution.connection.slug,
42
+ event: supported.data.event.type,
43
+ workspaceId: resolution.connection.workspaceId,
44
+ connectionId: resolution.connection.id,
45
+ connectionName: resolution.connection.displayName,
46
+ deliveryId: params.deliveryId,
47
+ receivedAt: new Date().toISOString(),
48
+ payload: {
49
+ ...supported.data.event,
50
+ team_id: supported.data.team_id,
51
+ api_app_id: supported.data.api_app_id,
52
+ event_id: supported.data.event_id,
53
+ event_time: supported.data.event_time
54
+ }
55
+ }
56
+ });
57
+ return {
58
+ outcome: result.published ? 'published' : 'duplicate'
59
+ };
60
+ }
61
+ export async function handleSlackCommand(params) {
62
+ const resolution = await resolveSlackConnection({
63
+ ...params,
64
+ teamId: params.command.team_id
65
+ });
66
+ if (resolution.kind === 'drop') return resolution;
67
+ const { token: _token, ...payload } = params.command;
68
+ const result = await params.publishIntegrationEventReceived({
69
+ tx: params.tx,
70
+ event: {
71
+ provider: SLACK_PROVIDER,
72
+ source: resolution.connection.slug,
73
+ event: SLACK_SLASH_COMMAND_EVENT,
74
+ workspaceId: resolution.connection.workspaceId,
75
+ connectionId: resolution.connection.id,
76
+ connectionName: resolution.connection.displayName,
77
+ deliveryId: params.deliveryId,
78
+ receivedAt: new Date().toISOString(),
79
+ payload
80
+ }
81
+ });
82
+ return {
83
+ outcome: result.published ? 'published' : 'duplicate'
84
+ };
85
+ }
86
+ export function isSelfAuthoredSlackEvent(event, botUserId) {
87
+ const parsed = slackSelfAuthoredEventSchema.safeParse(event);
88
+ if (!parsed.success) return false;
89
+ const nestedMessage = parsed.data.message;
90
+ return parsed.data.bot_id !== undefined || parsed.data.user === botUserId || nestedMessage?.bot_id !== undefined || nestedMessage?.user === botUserId;
91
+ }
92
+ async function resolveSlackConnection(params) {
93
+ const installation = await getSlackInstallationByTeamId(params.teamId, {
94
+ tx: params.tx
95
+ });
96
+ if (!installation) {
97
+ logger().warn({
98
+ deliveryId: params.deliveryId,
99
+ teamId: params.teamId
100
+ }, 'slack webhook: unknown team, dropping');
101
+ await recordSlackDeliveryOnly(params);
102
+ return {
103
+ kind: 'drop',
104
+ outcome: 'unknown-team'
105
+ };
106
+ }
107
+ if (installation.status !== 'installed') {
108
+ logger().info({
109
+ deliveryId: params.deliveryId,
110
+ teamId: params.teamId,
111
+ connectionId: installation.connectionId
112
+ }, 'slack webhook: installation is not installed, dropping');
113
+ await recordSlackDeliveryOnly(params);
114
+ return {
115
+ kind: 'drop',
116
+ outcome: 'revoked-installation'
117
+ };
118
+ }
119
+ const connection = await params.getIntegrationConnectionById(installation.connectionId, {
120
+ tx: params.tx
121
+ });
122
+ if (!connection) {
123
+ logger().warn({
124
+ deliveryId: params.deliveryId,
125
+ teamId: params.teamId,
126
+ connectionId: installation.connectionId
127
+ }, 'slack webhook: installation has no connection, dropping');
128
+ await recordSlackDeliveryOnly(params);
129
+ return {
130
+ kind: 'drop',
131
+ outcome: 'missing-connection'
132
+ };
133
+ }
134
+ if (connection.lifecycleStatus !== 'active') {
135
+ logger().info({
136
+ deliveryId: params.deliveryId,
137
+ teamId: params.teamId,
138
+ connectionId: connection.id
139
+ }, 'slack webhook: inactive connection, dropping');
140
+ await recordSlackDeliveryOnly(params);
141
+ return {
142
+ kind: 'drop',
143
+ outcome: 'inactive-connection'
144
+ };
145
+ }
146
+ return {
147
+ kind: 'ok',
148
+ connection,
149
+ installation
150
+ };
151
+ }
152
+ async function recordSlackDeliveryOnly(params) {
153
+ await params.recordDeliveryOnly({
154
+ tx: params.tx,
155
+ provider: SLACK_PROVIDER,
156
+ deliveryId: params.deliveryId
157
+ });
158
+ }
159
+
160
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import type {\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n SLACK_PROVIDER,\n SLACK_SLASH_COMMAND_EVENT,\n type SlackEventBaseEnvelopeDto,\n type SlackSlashCommandDto,\n slackEventEnvelopeSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {z} from 'zod';\nimport {getSlackInstallationByTeamId, type SlackInstallation} from '#db/installations.js';\n\nconst slackSelfAuthoredEventSchema = z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n message: z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n })\n .passthrough()\n .optional(),\n })\n .passthrough();\n\nexport type SlackWebhookOutcome =\n | 'published'\n | 'duplicate'\n | 'unknown-team'\n | 'revoked-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'unsupported-event'\n | 'self-message';\n\ninterface SlackWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface HandleSlackEventParams extends SlackWebhookParams {\n envelope: SlackEventBaseEnvelopeDto;\n}\n\nexport interface HandleSlackCommandParams extends SlackWebhookParams {\n command: SlackSlashCommandDto;\n}\n\ntype SlackConnectionResolution =\n | {kind: 'ok'; connection: IntegrationConnection; installation: SlackInstallation}\n | {\n kind: 'drop';\n outcome: Exclude<\n SlackWebhookOutcome,\n 'published' | 'duplicate' | 'unsupported-event' | 'self-message'\n >;\n };\n\nexport async function handleSlackEvent(\n params: HandleSlackEventParams,\n): Promise<{outcome: SlackWebhookOutcome}> {\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.envelope.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n if (isSelfAuthoredSlackEvent(params.envelope.event, resolution.installation.botUserId)) {\n await recordSlackDeliveryOnly(params);\n return {outcome: 'self-message'};\n }\n\n const supported = slackEventEnvelopeSchema.safeParse(params.envelope);\n if (!supported.success) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n type: params.envelope.event.type,\n },\n 'slack webhook: unsupported event, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {outcome: 'unsupported-event'};\n }\n\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: supported.data.event.type,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: {\n ...supported.data.event,\n team_id: supported.data.team_id,\n api_app_id: supported.data.api_app_id,\n event_id: supported.data.event_id,\n event_time: supported.data.event_time,\n },\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport async function handleSlackCommand(\n params: HandleSlackCommandParams,\n): Promise<{outcome: Exclude<SlackWebhookOutcome, 'unsupported-event' | 'self-message'>}> {\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.command.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n const {token: _token, ...payload} = params.command;\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: SLACK_SLASH_COMMAND_EVENT,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload,\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport function isSelfAuthoredSlackEvent(event: unknown, botUserId: string): boolean {\n const parsed = slackSelfAuthoredEventSchema.safeParse(event);\n if (!parsed.success) return false;\n const nestedMessage = parsed.data.message;\n return (\n parsed.data.bot_id !== undefined ||\n parsed.data.user === botUserId ||\n nestedMessage?.bot_id !== undefined ||\n nestedMessage?.user === botUserId\n );\n}\n\nasync function resolveSlackConnection(\n params: SlackWebhookParams & {teamId: string},\n): Promise<SlackConnectionResolution> {\n const installation = await getSlackInstallationByTeamId(params.teamId, {tx: params.tx});\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, teamId: params.teamId},\n 'slack webhook: unknown team, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'unknown-team'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation is not installed, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'revoked-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation has no connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n logger().info(\n {deliveryId: params.deliveryId, teamId: params.teamId, connectionId: connection.id},\n 'slack webhook: inactive connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'inactive-connection'};\n }\n\n return {kind: 'ok', connection, installation};\n}\n\nasync function recordSlackDeliveryOnly(\n params: Pick<SlackWebhookParams, 'tx' | 'deliveryId' | 'recordDeliveryOnly'>,\n): Promise<void> {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: SLACK_PROVIDER,\n deliveryId: params.deliveryId,\n });\n}\n"],"names":["SLACK_PROVIDER","SLACK_SLASH_COMMAND_EVENT","slackEventEnvelopeSchema","logger","z","getSlackInstallationByTeamId","slackSelfAuthoredEventSchema","object","bot_id","string","optional","user","message","passthrough","handleSlackEvent","params","resolution","resolveSlackConnection","teamId","envelope","team_id","kind","isSelfAuthoredSlackEvent","event","installation","botUserId","recordSlackDeliveryOnly","outcome","supported","safeParse","success","info","deliveryId","type","result","publishIntegrationEventReceived","tx","provider","source","connection","slug","data","workspaceId","connectionId","id","connectionName","displayName","receivedAt","Date","toISOString","payload","api_app_id","event_id","event_time","published","handleSlackCommand","command","token","_token","parsed","nestedMessage","undefined","warn","status","getIntegrationConnectionById","lifecycleStatus","recordDeliveryOnly"],"mappings":"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"}