






















@@ -0,0 +1,244 @@
1+import type { SlackChannelResolution } from "../resolve-channels.js";
2+import type { SlackUserResolution } from "../resolve-users.js";
3+import { formatUnknownError, waitForSlackSocketDisconnect } from "./reconnect-policy.js";
4+5+type SlackAppConstructor = typeof import("@slack/bolt").App;
6+type SlackHttpReceiverConstructor = typeof import("@slack/bolt").HTTPReceiver;
7+type SlackSocketModeReceiverConstructor = typeof import("@slack/bolt").SocketModeReceiver;
8+9+export type SlackBoltResolvedExports = {
10+App: SlackAppConstructor;
11+HTTPReceiver: SlackHttpReceiverConstructor;
12+SocketModeReceiver: SlackSocketModeReceiverConstructor;
13+};
14+15+type SlackSocketShutdownClient = {
16+shuttingDown?: boolean;
17+};
18+type Constructor = abstract new (...args: never[]) => unknown;
19+20+function isConstructorFunction<
21+// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Constructor guard preserves the requested concrete Slack constructor type.
22+T extends Constructor,
23+>(value: unknown): value is T {
24+return typeof value === "function";
25+}
26+27+function resolveSlackBoltModule(value: unknown): SlackBoltResolvedExports | null {
28+if (!value || typeof value !== "object") {
29+return null;
30+}
31+const app = Reflect.get(value, "App");
32+const httpReceiver = Reflect.get(value, "HTTPReceiver");
33+const socketModeReceiver = Reflect.get(value, "SocketModeReceiver");
34+if (
35+!isConstructorFunction<SlackAppConstructor>(app) ||
36+!isConstructorFunction<SlackHttpReceiverConstructor>(httpReceiver) ||
37+!isConstructorFunction<SlackSocketModeReceiverConstructor>(socketModeReceiver)
38+) {
39+return null;
40+}
41+return {
42+App: app,
43+HTTPReceiver: httpReceiver,
44+SocketModeReceiver: socketModeReceiver,
45+};
46+}
47+48+export function resolveSlackBoltInterop(params: {
49+defaultImport: unknown;
50+namespaceImport: unknown;
51+}): SlackBoltResolvedExports {
52+const { defaultImport, namespaceImport } = params;
53+const nestedDefault =
54+defaultImport && typeof defaultImport === "object"
55+ ? Reflect.get(defaultImport, "default")
56+ : undefined;
57+const namespaceDefault =
58+namespaceImport && typeof namespaceImport === "object"
59+ ? Reflect.get(namespaceImport, "default")
60+ : undefined;
61+const namespaceReceiver =
62+namespaceImport && typeof namespaceImport === "object"
63+ ? Reflect.get(namespaceImport, "HTTPReceiver")
64+ : undefined;
65+const namespaceSocketModeReceiver =
66+namespaceImport && typeof namespaceImport === "object"
67+ ? Reflect.get(namespaceImport, "SocketModeReceiver")
68+ : undefined;
69+const directModule =
70+resolveSlackBoltModule(defaultImport) ??
71+resolveSlackBoltModule(nestedDefault) ??
72+resolveSlackBoltModule(namespaceDefault) ??
73+resolveSlackBoltModule(namespaceImport);
74+if (directModule) {
75+return directModule;
76+}
77+if (
78+isConstructorFunction<SlackAppConstructor>(defaultImport) &&
79+isConstructorFunction<SlackHttpReceiverConstructor>(namespaceReceiver) &&
80+isConstructorFunction<SlackSocketModeReceiverConstructor>(namespaceSocketModeReceiver)
81+) {
82+return {
83+App: defaultImport,
84+HTTPReceiver: namespaceReceiver,
85+SocketModeReceiver: namespaceSocketModeReceiver,
86+};
87+}
88+throw new TypeError("Unable to resolve @slack/bolt App/HTTPReceiver exports");
89+}
90+91+export function publishSlackConnectedStatus(setStatus?: (next: Record<string, unknown>) => void) {
92+if (!setStatus) {
93+return;
94+}
95+const now = Date.now();
96+setStatus({
97+connected: true,
98+lastConnectedAt: now,
99+healthState: "healthy",
100+lastError: null,
101+});
102+}
103+104+export function publishSlackDisconnectedStatus(
105+setStatus?: (next: Record<string, unknown>) => void,
106+error?: unknown,
107+) {
108+if (!setStatus) {
109+return;
110+}
111+const at = Date.now();
112+const message = error ? formatUnknownError(error) : undefined;
113+setStatus({
114+connected: false,
115+healthState: "disconnected",
116+lastDisconnect: message ? { at, error: message } : { at },
117+lastError: message ?? null,
118+});
119+}
120+121+export function createSlackBoltApp(params: {
122+interop: SlackBoltResolvedExports;
123+slackMode: "socket" | "http";
124+botToken: string;
125+appToken?: string;
126+signingSecret?: string;
127+slackWebhookPath: string;
128+clientOptions: Record<string, unknown>;
129+}) {
130+const receiver =
131+params.slackMode === "socket"
132+ ? new params.interop.SocketModeReceiver({
133+appToken: params.appToken ?? "",
134+autoReconnectEnabled: false,
135+installerOptions: {
136+clientOptions: params.clientOptions,
137+},
138+})
139+ : new params.interop.HTTPReceiver({
140+signingSecret: params.signingSecret ?? "",
141+endpoints: params.slackWebhookPath,
142+});
143+const app = new params.interop.App({
144+token: params.botToken,
145+ receiver,
146+clientOptions: params.clientOptions,
147+});
148+return { app, receiver };
149+}
150+151+export function createSlackSocketDisconnectWaiter(app: unknown, abortSignal?: AbortSignal) {
152+const waiterAbortController = new AbortController();
153+const relayAbort = () => waiterAbortController.abort();
154+abortSignal?.addEventListener("abort", relayAbort, { once: true });
155+return {
156+promise: waitForSlackSocketDisconnect(app, waiterAbortController.signal),
157+cancel: () => {
158+waiterAbortController.abort();
159+abortSignal?.removeEventListener("abort", relayAbort);
160+},
161+complete: () => {
162+abortSignal?.removeEventListener("abort", relayAbort);
163+},
164+};
165+}
166+167+export async function startSlackSocketAndWaitForDisconnect(params: {
168+app: { start: () => unknown };
169+abortSignal?: AbortSignal;
170+onStarted?: () => void;
171+}) {
172+const disconnectWaiter = createSlackSocketDisconnectWaiter(params.app, params.abortSignal);
173+try {
174+await Promise.resolve(params.app.start());
175+if (params.abortSignal?.aborted) {
176+disconnectWaiter.cancel();
177+return null;
178+}
179+params.onStarted?.();
180+const disconnect = await disconnectWaiter.promise;
181+disconnectWaiter.complete();
182+return disconnect;
183+} catch (err) {
184+disconnectWaiter.cancel();
185+throw err;
186+}
187+}
188+189+export function resolveSlackSocketShutdownClient(
190+app: unknown,
191+): SlackSocketShutdownClient | undefined {
192+if (!app || typeof app !== "object") {
193+return undefined;
194+}
195+const receiver = Reflect.get(app, "receiver");
196+if (!receiver || typeof receiver !== "object") {
197+return undefined;
198+}
199+const client = Reflect.get(receiver, "client");
200+if (!client || typeof client !== "object") {
201+return undefined;
202+}
203+return client as SlackSocketShutdownClient;
204+}
205+206+export async function gracefulStopSlackApp(app: { stop: () => unknown }) {
207+const socketClient = resolveSlackSocketShutdownClient(app);
208+if (socketClient) {
209+socketClient.shuttingDown = true;
210+}
211+await Promise.resolve(app.stop()).catch(() => undefined);
212+}
213+214+function formatSlackResolvedLabel(params: {
215+input: string;
216+id: string;
217+name?: string;
218+extra?: string[];
219+}): string {
220+const extras = params.extra?.filter(Boolean) ?? [];
221+const suffix =
222+extras.length > 0 ? ` (id:${params.id}, ${extras.join(", ")})` : ` (id:${params.id})`;
223+return `${params.input}→${params.name ?? params.id}${suffix}`;
224+}
225+226+export function formatSlackChannelResolved(entry: SlackChannelResolution): string {
227+const id = entry.id ?? entry.input;
228+return formatSlackResolvedLabel({
229+input: entry.input,
230+ id,
231+name: entry.name,
232+extra: entry.archived ? ["archived"] : [],
233+});
234+}
235+236+export function formatSlackUserResolved(entry: SlackUserResolution): string {
237+const id = entry.id ?? entry.input;
238+return formatSlackResolvedLabel({
239+input: entry.input,
240+ id,
241+name: entry.name,
242+extra: entry.note ? [entry.note] : [],
243+});
244+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。