@cuylabs/channel-slack 0.1.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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/dist/activity-ByrD9Ftr.d.ts +66 -0
  4. package/dist/assistant.d.ts +58 -0
  5. package/dist/assistant.js +188 -0
  6. package/dist/bolt.d.ts +344 -0
  7. package/dist/bolt.js +705 -0
  8. package/dist/chunk-BODPT4I6.js +322 -0
  9. package/dist/chunk-FPCE5V5Y.js +292 -0
  10. package/dist/chunk-FX2JOVX5.js +405 -0
  11. package/dist/chunk-JZG4IETE.js +141 -0
  12. package/dist/chunk-NE57BLLU.js +0 -0
  13. package/dist/chunk-TWJGVDA2.js +108 -0
  14. package/dist/core.d.ts +425 -0
  15. package/dist/core.js +42 -0
  16. package/dist/diagnostics.d.ts +105 -0
  17. package/dist/diagnostics.js +8 -0
  18. package/dist/feedback.d.ts +137 -0
  19. package/dist/feedback.js +128 -0
  20. package/dist/history.d.ts +266 -0
  21. package/dist/history.js +747 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +57 -0
  24. package/dist/logging-Bl3HfcC8.d.ts +8 -0
  25. package/dist/policy.d.ts +130 -0
  26. package/dist/policy.js +16 -0
  27. package/dist/setup.d.ts +165 -0
  28. package/dist/setup.js +453 -0
  29. package/dist/shared.d.ts +2 -0
  30. package/dist/shared.js +43 -0
  31. package/dist/targets.d.ts +113 -0
  32. package/dist/targets.js +484 -0
  33. package/dist/users.d.ts +109 -0
  34. package/dist/users.js +240 -0
  35. package/docs/concepts/activity.md +33 -0
  36. package/docs/concepts/bolt-runtime.md +30 -0
  37. package/docs/concepts/message-policy.md +49 -0
  38. package/docs/concepts/setup-requirements.md +44 -0
  39. package/docs/concepts/supplemental-history.md +55 -0
  40. package/docs/recipes/app-mention-handler.md +34 -0
  41. package/docs/recipes/assistant-thread-handler.md +28 -0
  42. package/docs/recipes/generate-slack-manifest.md +28 -0
  43. package/docs/recipes/history-visibility.md +36 -0
  44. package/docs/recipes/socket-mode-app.md +29 -0
  45. package/docs/reference/channel-slack-boundary.md +50 -0
  46. package/docs/reference/exports.md +32 -0
  47. package/package.json +130 -0
package/dist/bolt.js ADDED
@@ -0,0 +1,705 @@
1
+ // src/bolt/auth.ts
2
+ function resolveDirectAuth(options) {
3
+ const provided = options.auth;
4
+ if (!provided || provided.mode === void 0 || provided.mode === "single-workspace") {
5
+ const singleWorkspace = provided;
6
+ const botToken = firstNonEmptyString(
7
+ singleWorkspace?.botToken,
8
+ options.botToken,
9
+ process.env.SLACK_BOT_TOKEN
10
+ );
11
+ if (!botToken) {
12
+ throw new Error(
13
+ 'Slack bot token is required for single-workspace mode. Pass `botToken`, use `auth: { mode: "oauth", ... }`, or use `auth: { mode: "authorize", ... }`.'
14
+ );
15
+ }
16
+ return {
17
+ mode: "single-workspace",
18
+ botToken,
19
+ botId: singleWorkspace?.botId,
20
+ botUserId: singleWorkspace?.botUserId
21
+ };
22
+ }
23
+ if (provided.mode === "authorize") {
24
+ return provided;
25
+ }
26
+ const oauth = provided;
27
+ const clientId = firstNonEmptyString(
28
+ oauth.clientId,
29
+ process.env.SLACK_CLIENT_ID
30
+ );
31
+ const clientSecret = firstNonEmptyString(
32
+ oauth.clientSecret,
33
+ process.env.SLACK_CLIENT_SECRET
34
+ );
35
+ if (!clientId || !clientSecret) {
36
+ throw new Error(
37
+ "Slack OAuth mode requires `clientId` and `clientSecret` or SLACK_CLIENT_ID / SLACK_CLIENT_SECRET."
38
+ );
39
+ }
40
+ if (oauth.stateVerification !== false && !trimToUndefined(oauth.stateSecret) && !oauth.stateStore && !trimToUndefined(process.env.SLACK_STATE_SECRET)) {
41
+ throw new Error(
42
+ "Slack OAuth mode requires `stateSecret` or a custom `stateStore` when state verification is enabled."
43
+ );
44
+ }
45
+ return {
46
+ ...oauth,
47
+ mode: "oauth",
48
+ clientId,
49
+ clientSecret,
50
+ stateSecret: firstNonEmptyString(
51
+ oauth.stateSecret,
52
+ process.env.SLACK_STATE_SECRET
53
+ )
54
+ };
55
+ }
56
+ function normalizeSlackEventsPath(path2) {
57
+ const trimmed = path2.trim();
58
+ if (!trimmed) {
59
+ throw new Error("Slack events path must not be empty.");
60
+ }
61
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
62
+ }
63
+ function trimToUndefined(value) {
64
+ const trimmed = value?.trim();
65
+ return trimmed || void 0;
66
+ }
67
+ function firstNonEmptyString(...values) {
68
+ for (const value of values) {
69
+ const trimmed = trimToUndefined(value);
70
+ if (trimmed) {
71
+ return trimmed;
72
+ }
73
+ }
74
+ return void 0;
75
+ }
76
+
77
+ // src/bolt/app.ts
78
+ async function createSlackBoltApp(options = {}) {
79
+ const boltModule = await import("@slack/bolt");
80
+ const routePath = normalizeSlackEventsPath(options.path ?? "/slack/events");
81
+ const signingSecret = trimToUndefined(
82
+ options.signingSecret ?? process.env.SLACK_SIGNING_SECRET
83
+ );
84
+ if (!signingSecret) {
85
+ throw new Error(
86
+ "Slack signing secret is required. Pass `signingSecret` or set SLACK_SIGNING_SECRET."
87
+ );
88
+ }
89
+ const auth = resolveDirectAuth(options);
90
+ const receiver = new boltModule.ExpressReceiver({
91
+ ...options.receiverOptions,
92
+ signingSecret,
93
+ endpoints: routePath,
94
+ processBeforeResponse: options.processBeforeResponse,
95
+ signatureVerification: options.signatureVerification,
96
+ // Bolt is typed against Express 4 while this repo uses Express 5 types.
97
+ // The runtime contract is compatible; keep the cast localized here.
98
+ app: options.app,
99
+ ...auth.mode === "oauth" ? {
100
+ clientId: auth.clientId,
101
+ clientSecret: auth.clientSecret,
102
+ stateSecret: auth.stateSecret,
103
+ installationStore: auth.installationStore,
104
+ redirectUri: auth.redirectUri,
105
+ scopes: auth.scopes,
106
+ installerOptions: {
107
+ stateStore: auth.stateStore,
108
+ stateVerification: auth.stateVerification,
109
+ legacyStateVerification: auth.legacyStateVerification,
110
+ stateCookieName: auth.stateCookieName,
111
+ stateCookieExpirationSeconds: auth.stateCookieExpirationSeconds,
112
+ metadata: auth.metadata,
113
+ userScopes: auth.userScopes,
114
+ installPath: auth.installPath,
115
+ redirectUriPath: auth.callbackPath,
116
+ renderHtmlForInstallPath: auth.renderHtmlForInstallPath,
117
+ installPathOptions: auth.installPathOptions,
118
+ callbackOptions: auth.callbackOptions,
119
+ directInstall: auth.directInstall,
120
+ authVersion: auth.authVersion,
121
+ authorizationUrl: auth.authorizationUrl
122
+ }
123
+ } : {}
124
+ });
125
+ const boltApp = new boltModule.App({
126
+ ...options.boltAppOptions,
127
+ receiver,
128
+ ...auth.mode === "single-workspace" ? {
129
+ token: auth.botToken,
130
+ botId: auth.botId,
131
+ botUserId: auth.botUserId
132
+ } : {},
133
+ ...auth.mode === "authorize" ? { authorize: auth.authorize } : {}
134
+ });
135
+ return {
136
+ boltApp,
137
+ receiver,
138
+ app: options.app ?? receiver.app,
139
+ authMode: auth.mode,
140
+ routePath
141
+ };
142
+ }
143
+
144
+ // src/bolt/socket-app.ts
145
+ async function createSlackSocketBoltApp(options = {}) {
146
+ const appToken = trimToUndefined(
147
+ options.appToken ?? process.env.SLACK_APP_TOKEN
148
+ );
149
+ if (!appToken) {
150
+ throw new Error(
151
+ "Slack app token is required for Socket Mode. Pass `appToken` or set SLACK_APP_TOKEN."
152
+ );
153
+ }
154
+ const auth = resolveDirectAuth({
155
+ auth: options.auth,
156
+ botToken: options.botToken
157
+ });
158
+ const boltModule = await import("@slack/bolt");
159
+ const receiverInstallerOptions = {
160
+ clientOptions: options.boltAppOptions?.clientOptions,
161
+ ...auth.mode === "oauth" ? {
162
+ port: options.boltAppOptions?.port,
163
+ stateStore: auth.stateStore,
164
+ stateVerification: auth.stateVerification,
165
+ legacyStateVerification: auth.legacyStateVerification,
166
+ stateCookieName: auth.stateCookieName,
167
+ stateCookieExpirationSeconds: auth.stateCookieExpirationSeconds,
168
+ metadata: auth.metadata,
169
+ userScopes: auth.userScopes,
170
+ installPath: auth.installPath,
171
+ redirectUriPath: auth.callbackPath,
172
+ renderHtmlForInstallPath: auth.renderHtmlForInstallPath,
173
+ installPathOptions: auth.installPathOptions,
174
+ callbackOptions: auth.callbackOptions,
175
+ directInstall: auth.directInstall,
176
+ authVersion: auth.authVersion,
177
+ authorizationUrl: auth.authorizationUrl
178
+ } : {}
179
+ };
180
+ const receiver = new boltModule.SocketModeReceiver({
181
+ ...options.socketModeReceiverOptions,
182
+ appToken,
183
+ customRoutes: options.socketModeReceiverOptions?.customRoutes ?? options.boltAppOptions?.customRoutes,
184
+ installerOptions: receiverInstallerOptions,
185
+ logger: options.boltAppOptions?.logger,
186
+ logLevel: options.boltAppOptions?.logLevel,
187
+ ...auth.mode === "oauth" ? {
188
+ clientId: auth.clientId,
189
+ clientSecret: auth.clientSecret,
190
+ stateSecret: auth.stateSecret,
191
+ installationStore: auth.installationStore,
192
+ redirectUri: auth.redirectUri,
193
+ scopes: auth.scopes
194
+ } : {}
195
+ });
196
+ const boltApp = new boltModule.App({
197
+ ...options.boltAppOptions,
198
+ receiver,
199
+ ...auth.mode === "single-workspace" ? {
200
+ token: auth.botToken,
201
+ botId: auth.botId,
202
+ botUserId: auth.botUserId
203
+ } : {},
204
+ ...auth.mode === "authorize" ? { authorize: auth.authorize } : {}
205
+ });
206
+ return { boltApp, authMode: auth.mode };
207
+ }
208
+
209
+ // src/bolt/socket-lock.ts
210
+ import crypto from "crypto";
211
+ import fs from "fs";
212
+ import os from "os";
213
+ import path from "path";
214
+ function acquireSlackSocketModeProcessLock({
215
+ appSlug,
216
+ appToken,
217
+ enabled = true,
218
+ lockDir,
219
+ logger
220
+ }) {
221
+ if (!enabled) {
222
+ logger?.debug?.("Slack Socket Mode process lock skipped", {
223
+ reason: "disabled"
224
+ });
225
+ return void 0;
226
+ }
227
+ if (!appToken?.trim()) {
228
+ throw new Error(
229
+ "Slack app token is required when the Slack Socket Mode process lock is enabled."
230
+ );
231
+ }
232
+ const resolvedLockDir = lockDir ?? path.join(os.tmpdir(), "channel-slack");
233
+ const tokenHash = crypto.createHash("sha256").update(appToken).digest("hex").slice(0, 16);
234
+ const lockPath = path.join(
235
+ resolvedLockDir,
236
+ `${appSlug}-${tokenHash}.socket-mode.lock`
237
+ );
238
+ fs.mkdirSync(resolvedLockDir, { recursive: true, mode: 448 });
239
+ return createLockFile({ appSlug, lockPath, logger, tokenHash });
240
+ }
241
+ function createLockFile({
242
+ appSlug,
243
+ lockPath,
244
+ logger,
245
+ tokenHash
246
+ }) {
247
+ let fd = tryOpenLockFile({ lockPath, logger });
248
+ if (fd === void 0) {
249
+ removeStaleLockOrThrow({ lockPath, logger });
250
+ fd = tryOpenLockFile({ lockPath, logger });
251
+ }
252
+ if (fd === void 0) {
253
+ throw new Error(`Unable to acquire Slack Socket Mode lock at ${lockPath}`);
254
+ }
255
+ const record = {
256
+ appSlug,
257
+ hostname: os.hostname(),
258
+ pid: process.pid,
259
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
260
+ tokenHash
261
+ };
262
+ fs.writeFileSync(fd, `${JSON.stringify(record, null, 2)}
263
+ `);
264
+ logger?.info?.("Slack Socket Mode process lock acquired", {
265
+ lockPath,
266
+ pid: record.pid
267
+ });
268
+ let released = false;
269
+ return {
270
+ path: lockPath,
271
+ release() {
272
+ if (released) {
273
+ return;
274
+ }
275
+ released = true;
276
+ try {
277
+ fs.closeSync(fd);
278
+ } catch {
279
+ }
280
+ releaseLockFile({ lockPath, logger });
281
+ }
282
+ };
283
+ }
284
+ function tryOpenLockFile({
285
+ lockPath,
286
+ logger
287
+ }) {
288
+ try {
289
+ return fs.openSync(lockPath, "wx", 384);
290
+ } catch (error) {
291
+ const code = error.code;
292
+ if (code === "EEXIST") {
293
+ return void 0;
294
+ }
295
+ logger?.error("Failed to create Slack Socket Mode process lock", {
296
+ lockPath,
297
+ error: formatLockError(error)
298
+ });
299
+ throw error;
300
+ }
301
+ }
302
+ function removeStaleLockOrThrow({
303
+ lockPath,
304
+ logger
305
+ }) {
306
+ const record = readLockRecord(lockPath);
307
+ if (record?.pid && isProcessAlive(record.pid)) {
308
+ throw new Error(
309
+ `Another Slack Socket Mode process appears to be active (pid=${record.pid}, lock=${lockPath})`
310
+ );
311
+ }
312
+ logger?.warn?.("Removing stale Slack Socket Mode process lock", {
313
+ lockPath,
314
+ previousPid: record?.pid
315
+ });
316
+ fs.rmSync(lockPath, { force: true });
317
+ }
318
+ function releaseLockFile({
319
+ lockPath,
320
+ logger
321
+ }) {
322
+ const record = readLockRecord(lockPath);
323
+ if (record?.pid !== process.pid) {
324
+ logger?.warn?.("Slack Socket Mode process lock not released", {
325
+ lockPath,
326
+ reason: "lock-owned-by-another-process",
327
+ ownerPid: record?.pid
328
+ });
329
+ return;
330
+ }
331
+ fs.rmSync(lockPath, { force: true });
332
+ logger?.debug?.("Slack Socket Mode process lock released", {
333
+ lockPath
334
+ });
335
+ }
336
+ function readLockRecord(lockPath) {
337
+ try {
338
+ const raw = fs.readFileSync(lockPath, "utf8");
339
+ const parsed = JSON.parse(raw);
340
+ return {
341
+ appSlug: String(parsed.appSlug ?? ""),
342
+ hostname: String(parsed.hostname ?? ""),
343
+ pid: Number(parsed.pid),
344
+ startedAt: String(parsed.startedAt ?? ""),
345
+ tokenHash: String(parsed.tokenHash ?? "")
346
+ };
347
+ } catch {
348
+ return void 0;
349
+ }
350
+ }
351
+ function isProcessAlive(pid) {
352
+ if (!Number.isInteger(pid) || pid < 1) {
353
+ return false;
354
+ }
355
+ try {
356
+ process.kill(pid, 0);
357
+ return true;
358
+ } catch (error) {
359
+ return error.code !== "ESRCH";
360
+ }
361
+ }
362
+ function formatLockError(error) {
363
+ return error instanceof Error ? error.message : String(error);
364
+ }
365
+
366
+ // src/bolt/socket-runtime.ts
367
+ var DEFAULT_CLIENT_PING_TIMEOUT_MS = 3e4;
368
+ var DEFAULT_RESTART_GUARD_MAX_WARNINGS = 3;
369
+ var DEFAULT_RESTART_GUARD_WINDOW_MS = 6e4;
370
+ var DEFAULT_SERVER_PING_TIMEOUT_MS = 3e4;
371
+ var SOCKET_MODE_RECOVERY_WARNING_PATTERNS = [
372
+ "A pong wasn't received",
373
+ "A ping wasn't received",
374
+ "Failed to send ping",
375
+ "WebSocket error",
376
+ "BrokenPipeError",
377
+ "broken pipe"
378
+ ];
379
+ function createSlackSocketModeRuntime({
380
+ appSlug,
381
+ appToken,
382
+ autoReconnectEnabled = true,
383
+ clientPingTimeoutMs = DEFAULT_CLIENT_PING_TIMEOUT_MS,
384
+ exitProcess = process.exit,
385
+ lockDir,
386
+ lockEnabled = true,
387
+ logger,
388
+ logLevel = "info",
389
+ pingPongLoggingEnabled = false,
390
+ restartGuardEnabled = false,
391
+ restartGuardMaxWarnings = DEFAULT_RESTART_GUARD_MAX_WARNINGS,
392
+ restartGuardWindowMs = DEFAULT_RESTART_GUARD_WINDOW_MS,
393
+ runtimePolicy = "single-instance",
394
+ serverPingTimeoutMs = DEFAULT_SERVER_PING_TIMEOUT_MS
395
+ }) {
396
+ const lock = acquireSlackSocketModeProcessLock({
397
+ appSlug,
398
+ appToken,
399
+ enabled: lockEnabled && runtimePolicy === "single-instance",
400
+ ...lockDir ? { lockDir } : {},
401
+ ...logger ? { logger } : {}
402
+ });
403
+ const restartGuard = createSlackSocketModeRestartGuard({
404
+ enabled: restartGuardEnabled,
405
+ exitProcess,
406
+ logger,
407
+ maxWarnings: restartGuardMaxWarnings,
408
+ windowMs: restartGuardWindowMs
409
+ });
410
+ logger?.info?.("Slack Socket Mode runtime guard configured", {
411
+ autoReconnectEnabled,
412
+ clientPingTimeoutMs,
413
+ lockEnabled: Boolean(lock),
414
+ pingPongLoggingEnabled,
415
+ restartGuardEnabled,
416
+ restartGuardMaxWarnings,
417
+ restartGuardWindowMs,
418
+ runtimePolicy,
419
+ serverPingTimeoutMs
420
+ });
421
+ return {
422
+ boltAppOptions: {
423
+ logger: createSlackSdkLogger({
424
+ level: logLevel,
425
+ logger,
426
+ restartGuard
427
+ })
428
+ },
429
+ close() {
430
+ lock?.release();
431
+ },
432
+ socketModeReceiverOptions: {
433
+ autoReconnectEnabled,
434
+ clientPingTimeout: clientPingTimeoutMs,
435
+ pingPongLoggingEnabled,
436
+ serverPingTimeout: serverPingTimeoutMs
437
+ }
438
+ };
439
+ }
440
+ function createSlackSocketModeRestartGuard({
441
+ enabled,
442
+ exitProcess,
443
+ logger,
444
+ maxWarnings,
445
+ windowMs
446
+ }) {
447
+ const warningTimes = [];
448
+ let tripped = false;
449
+ return {
450
+ record(message) {
451
+ if (!enabled || tripped || !SOCKET_MODE_RECOVERY_WARNING_PATTERNS.some(
452
+ (pattern) => message.includes(pattern)
453
+ )) {
454
+ return;
455
+ }
456
+ const now = Date.now();
457
+ const cutoff = now - windowMs;
458
+ while (warningTimes.length > 0 && warningTimes[0] < cutoff) {
459
+ warningTimes.shift();
460
+ }
461
+ warningTimes.push(now);
462
+ if (warningTimes.length < maxWarnings) {
463
+ return;
464
+ }
465
+ tripped = true;
466
+ logger?.error("Slack Socket Mode restart guard tripped", {
467
+ maxWarnings,
468
+ windowMs,
469
+ reason: "repeated-websocket-health-warnings",
470
+ lastWarning: message
471
+ });
472
+ const timer = setTimeout(() => exitProcess(1), 50);
473
+ timer.unref?.();
474
+ }
475
+ };
476
+ }
477
+ function createSlackSdkLogger({
478
+ level,
479
+ logger,
480
+ restartGuard
481
+ }) {
482
+ let currentLevel = toSlackLogLevel(level);
483
+ let name = "slack-sdk";
484
+ function attributes(message) {
485
+ return {
486
+ component: name,
487
+ message
488
+ };
489
+ }
490
+ return {
491
+ debug(...msg) {
492
+ logger?.debug?.("Slack SDK debug", attributes(formatSlackLogArgs(msg)));
493
+ },
494
+ error(...msg) {
495
+ const message = formatSlackLogArgs(msg);
496
+ restartGuard?.record(message);
497
+ logger?.error("Slack SDK error", attributes(message));
498
+ },
499
+ getLevel() {
500
+ return currentLevel;
501
+ },
502
+ info(...msg) {
503
+ logger?.debug?.("Slack SDK info", attributes(formatSlackLogArgs(msg)));
504
+ },
505
+ setLevel(nextLevel) {
506
+ currentLevel = nextLevel;
507
+ },
508
+ setName(nextName) {
509
+ name = nextName || name;
510
+ },
511
+ warn(...msg) {
512
+ const message = formatSlackLogArgs(msg);
513
+ restartGuard?.record(message);
514
+ logger?.warn?.("Slack SDK warning", attributes(message));
515
+ }
516
+ };
517
+ }
518
+ function toSlackLogLevel(level) {
519
+ switch (level) {
520
+ case "debug":
521
+ return "debug";
522
+ case "warn":
523
+ return "warn";
524
+ case "error":
525
+ return "error";
526
+ case "info":
527
+ return "info";
528
+ }
529
+ }
530
+ function formatSlackLogArgs(args) {
531
+ return args.map(formatSlackLogArg).join(" ");
532
+ }
533
+ function formatSlackLogArg(value) {
534
+ if (value instanceof Error) {
535
+ return value.message;
536
+ }
537
+ if (typeof value === "string") {
538
+ return value;
539
+ }
540
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
541
+ return String(value);
542
+ }
543
+ try {
544
+ return JSON.stringify(redactSlackSocketModeLogValue(value));
545
+ } catch {
546
+ return "[unserializable]";
547
+ }
548
+ }
549
+ function redactSlackSocketModeLogValue(value, depth = 0) {
550
+ if (depth > 4 || value === null || typeof value !== "object") {
551
+ return value;
552
+ }
553
+ if (Array.isArray(value)) {
554
+ return value.map((item) => redactSlackSocketModeLogValue(item, depth + 1));
555
+ }
556
+ const result = {};
557
+ for (const [key, item] of Object.entries(value)) {
558
+ result[key] = isSensitiveKey(key) ? "[redacted]" : redactSlackSocketModeLogValue(item, depth + 1);
559
+ }
560
+ return result;
561
+ }
562
+ function isSensitiveKey(key) {
563
+ const normalized = key.toLowerCase();
564
+ return normalized.includes("token") || normalized.includes("secret") || normalized === "authorization" || normalized === "cookie";
565
+ }
566
+
567
+ // src/bolt/installation-store.ts
568
+ import fs2 from "fs/promises";
569
+ import { dirname } from "path";
570
+ var InMemorySlackInstallationStore = class {
571
+ devDB = {};
572
+ async storeInstallation(installation) {
573
+ const key = getSlackInstallationKey(installation);
574
+ this.devDB[key] = installation;
575
+ }
576
+ async fetchInstallation(query) {
577
+ const key = getSlackInstallationKey(query);
578
+ const installation = this.devDB[key];
579
+ if (!installation) {
580
+ throw new Error(`No Slack installation found for key: ${key}`);
581
+ }
582
+ return installation;
583
+ }
584
+ async deleteInstallation(query) {
585
+ const key = getSlackInstallationKey(query);
586
+ delete this.devDB[key];
587
+ }
588
+ };
589
+ function createInMemorySlackInstallationStore() {
590
+ return new InMemorySlackInstallationStore();
591
+ }
592
+ var JsonFileSlackInstallationStore = class {
593
+ constructor(filePath) {
594
+ this.filePath = filePath;
595
+ }
596
+ filePath;
597
+ pending = Promise.resolve();
598
+ async storeInstallation(installation) {
599
+ await this.withLock(async () => {
600
+ const installations = await this.readInstallations();
601
+ installations[getSlackInstallationKey(installation)] = installation;
602
+ await this.writeInstallations(installations);
603
+ });
604
+ }
605
+ async fetchInstallation(query) {
606
+ return await this.withLock(async () => {
607
+ const key = getSlackInstallationKey(query);
608
+ const installations = await this.readInstallations();
609
+ const installation = installations[key];
610
+ if (!installation) {
611
+ throw new Error(`No Slack installation found for key: ${key}`);
612
+ }
613
+ return installation;
614
+ });
615
+ }
616
+ async deleteInstallation(query) {
617
+ await this.withLock(async () => {
618
+ const key = getSlackInstallationKey(query);
619
+ const installations = await this.readInstallations();
620
+ delete installations[key];
621
+ await this.writeInstallations(installations);
622
+ });
623
+ }
624
+ async withLock(operation) {
625
+ const run = this.pending.then(operation, operation);
626
+ this.pending = run.then(
627
+ () => void 0,
628
+ () => void 0
629
+ );
630
+ return await run;
631
+ }
632
+ async readInstallations() {
633
+ let contents;
634
+ try {
635
+ contents = await fs2.readFile(this.filePath, "utf8");
636
+ } catch (error) {
637
+ if (isNodeErrorCode(error, "ENOENT")) {
638
+ return {};
639
+ }
640
+ throw error;
641
+ }
642
+ if (!contents.trim()) {
643
+ return {};
644
+ }
645
+ const parsed = JSON.parse(contents);
646
+ const record = asRecord(parsed);
647
+ const installations = asRecord(record?.installations) ?? record;
648
+ if (!installations) {
649
+ return {};
650
+ }
651
+ const output = {};
652
+ for (const [key, value] of Object.entries(installations)) {
653
+ if (asRecord(value)) {
654
+ output[key] = value;
655
+ }
656
+ }
657
+ return output;
658
+ }
659
+ async writeInstallations(installations) {
660
+ await fs2.mkdir(dirname(this.filePath), { recursive: true });
661
+ const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
662
+ const payload = JSON.stringify({ version: 1, installations }, null, 2);
663
+ await fs2.writeFile(tempPath, `${payload}
664
+ `, "utf8");
665
+ await fs2.rename(tempPath, this.filePath);
666
+ }
667
+ };
668
+ function createJsonFileSlackInstallationStore(filePath) {
669
+ return new JsonFileSlackInstallationStore(filePath);
670
+ }
671
+ function getSlackInstallationKey(input) {
672
+ if (input.isEnterpriseInstall && "enterprise" in input && input.enterprise?.id) {
673
+ return `enterprise:${input.enterprise.id}`;
674
+ }
675
+ if (input.isEnterpriseInstall && "enterpriseId" in input && input.enterpriseId) {
676
+ return `enterprise:${input.enterpriseId}`;
677
+ }
678
+ if ("team" in input && input.team?.id) {
679
+ return `team:${input.team.id}`;
680
+ }
681
+ if ("teamId" in input && input.teamId) {
682
+ return `team:${input.teamId}`;
683
+ }
684
+ throw new Error("Slack installation is missing a team or enterprise id.");
685
+ }
686
+ function asRecord(value) {
687
+ return value && typeof value === "object" ? value : void 0;
688
+ }
689
+ function isNodeErrorCode(error, code) {
690
+ return error instanceof Error && "code" in error && error.code === code;
691
+ }
692
+ export {
693
+ InMemorySlackInstallationStore,
694
+ JsonFileSlackInstallationStore,
695
+ acquireSlackSocketModeProcessLock,
696
+ createInMemorySlackInstallationStore,
697
+ createJsonFileSlackInstallationStore,
698
+ createSlackBoltApp,
699
+ createSlackSdkLogger,
700
+ createSlackSocketBoltApp,
701
+ createSlackSocketModeRestartGuard,
702
+ createSlackSocketModeRuntime,
703
+ getSlackInstallationKey,
704
+ redactSlackSocketModeLogValue
705
+ };