@fluojs/slack 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.ko.md +246 -0
- package/README.md +246 -0
- package/dist/channel.d.ts +24 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +59 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/module.d.ts +49 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +133 -0
- package/dist/service.d.ts +79 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +261 -0
- package/dist/status.d.ts +27 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +82 -0
- package/dist/tokens.d.ts +10 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +6 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/webhook.d.ts +19 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +166 -0
- package/package.json +54 -0
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Compatibility token for the facade returned by {@link SlackModule.forRoot}. */
|
|
2
|
+
export const SLACK = Symbol.for('fluo.slack');
|
|
3
|
+
/** Injection token for the channel implementation consumed by `@fluojs/notifications`. */
|
|
4
|
+
export const SLACK_CHANNEL = Symbol.for('fluo.slack.channel');
|
|
5
|
+
/** Injection token for normalized Slack module options consumed internally by providers. */
|
|
6
|
+
export const SLACK_OPTIONS = Symbol.for('fluo.slack.options');
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { AsyncModuleOptions, MaybePromise } from '@fluojs/core';
|
|
2
|
+
import type { NotificationDispatchRequest } from '@fluojs/notifications';
|
|
3
|
+
/** Opaque Slack Block Kit object forwarded to one transport implementation. */
|
|
4
|
+
export type SlackBlock = Readonly<Record<string, unknown>>;
|
|
5
|
+
/** Opaque Slack attachment object forwarded to one transport implementation. */
|
|
6
|
+
export type SlackAttachment = Readonly<Record<string, unknown>>;
|
|
7
|
+
/** Caller-supplied Slack message shape used for standalone delivery. */
|
|
8
|
+
export interface SlackMessage {
|
|
9
|
+
attachments?: readonly SlackAttachment[];
|
|
10
|
+
blocks?: readonly SlackBlock[];
|
|
11
|
+
channel?: string;
|
|
12
|
+
iconEmoji?: string;
|
|
13
|
+
iconUrl?: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
mrkdwn?: boolean;
|
|
16
|
+
replyBroadcast?: boolean;
|
|
17
|
+
text?: string;
|
|
18
|
+
threadTs?: string;
|
|
19
|
+
unfurlLinks?: boolean;
|
|
20
|
+
unfurlMedia?: boolean;
|
|
21
|
+
username?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Normalized Slack message passed to one transport implementation. */
|
|
24
|
+
export interface NormalizedSlackMessage {
|
|
25
|
+
attachments: readonly SlackAttachment[];
|
|
26
|
+
blocks: readonly SlackBlock[];
|
|
27
|
+
channel?: string;
|
|
28
|
+
iconEmoji?: string;
|
|
29
|
+
iconUrl?: string;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
mrkdwn?: boolean;
|
|
32
|
+
replyBroadcast?: boolean;
|
|
33
|
+
text?: string;
|
|
34
|
+
threadTs?: string;
|
|
35
|
+
unfurlLinks?: boolean;
|
|
36
|
+
unfurlMedia?: boolean;
|
|
37
|
+
username?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Context object forwarded to transport implementations per delivery attempt. */
|
|
40
|
+
export interface SlackTransportContext {
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
/** Provider-specific receipt returned by one Slack transport. */
|
|
44
|
+
export interface SlackTransportReceipt {
|
|
45
|
+
channel?: string;
|
|
46
|
+
messageTs?: string;
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
ok?: boolean;
|
|
49
|
+
response?: string;
|
|
50
|
+
statusCode?: number;
|
|
51
|
+
warnings?: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
/** Transport contract implemented by runtime-specific or provider-specific Slack adapters. */
|
|
54
|
+
export interface SlackTransport {
|
|
55
|
+
/**
|
|
56
|
+
* Sends one normalized Slack message.
|
|
57
|
+
*
|
|
58
|
+
* @param message Normalized message with resolved defaults and one target channel.
|
|
59
|
+
* @param context Optional abort context propagated from the caller.
|
|
60
|
+
* @returns Provider-specific receipt details normalized for the Fluo Slack contract.
|
|
61
|
+
*/
|
|
62
|
+
send(message: NormalizedSlackMessage, context: SlackTransportContext): Promise<SlackTransportReceipt>;
|
|
63
|
+
/**
|
|
64
|
+
* Verifies transport readiness during bootstrap when configured.
|
|
65
|
+
*
|
|
66
|
+
* @returns A promise that resolves when the transport is ready for delivery.
|
|
67
|
+
*/
|
|
68
|
+
verify?(): MaybePromise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Closes underlying transport resources during application shutdown.
|
|
71
|
+
*
|
|
72
|
+
* @returns A promise that resolves when resource cleanup completes.
|
|
73
|
+
*/
|
|
74
|
+
close?(): MaybePromise<void>;
|
|
75
|
+
}
|
|
76
|
+
/** Factory used to construct a transport lazily during module bootstrap. */
|
|
77
|
+
export interface SlackTransportFactory {
|
|
78
|
+
/**
|
|
79
|
+
* Creates the transport instance used by {@link SlackService}.
|
|
80
|
+
*
|
|
81
|
+
* @returns The transport implementation that will own Slack delivery.
|
|
82
|
+
*/
|
|
83
|
+
create(): MaybePromise<SlackTransport>;
|
|
84
|
+
/**
|
|
85
|
+
* Stable diagnostic label describing the injected transport kind.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* This value is surfaced through platform status snapshots so applications can
|
|
89
|
+
* tell which adapter is currently wired without the package hard-coding a
|
|
90
|
+
* provider-specific runtime dependency.
|
|
91
|
+
*/
|
|
92
|
+
kind?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Declares whether the factory-created transport owns resources that the package should close.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* Factories default to `true` because they typically allocate the transport instance.
|
|
98
|
+
* Directly injected transport instances default to `false` because the caller owns them.
|
|
99
|
+
*/
|
|
100
|
+
ownsResources?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** Minimal fetch-compatible response contract used by the built-in webhook transport helper. */
|
|
103
|
+
export interface SlackFetchResponse {
|
|
104
|
+
ok: boolean;
|
|
105
|
+
status: number;
|
|
106
|
+
statusText?: string;
|
|
107
|
+
text(): MaybePromise<string>;
|
|
108
|
+
}
|
|
109
|
+
/** Minimal fetch-compatible function signature used by the built-in webhook transport helper. */
|
|
110
|
+
export interface SlackFetchLike {
|
|
111
|
+
(input: string, init?: {
|
|
112
|
+
body?: string;
|
|
113
|
+
headers?: Readonly<Record<string, string>>;
|
|
114
|
+
method?: string;
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
}): MaybePromise<SlackFetchResponse>;
|
|
117
|
+
}
|
|
118
|
+
/** Options accepted by {@link createSlackWebhookTransport}. */
|
|
119
|
+
export interface SlackWebhookTransportOptions {
|
|
120
|
+
fetch?: SlackFetchLike;
|
|
121
|
+
webhookUrl: string;
|
|
122
|
+
}
|
|
123
|
+
/** Template render input used for `NotificationDispatchRequest.template` integration. */
|
|
124
|
+
export interface SlackTemplateRenderInput<TPayload extends SlackNotificationPayload = SlackNotificationPayload> {
|
|
125
|
+
locale?: string;
|
|
126
|
+
metadata?: Record<string, unknown>;
|
|
127
|
+
payload: TPayload;
|
|
128
|
+
subject?: string;
|
|
129
|
+
template: string;
|
|
130
|
+
}
|
|
131
|
+
/** Render result returned by an optional Slack template renderer. */
|
|
132
|
+
export interface SlackTemplateRenderResult {
|
|
133
|
+
attachments?: readonly SlackAttachment[];
|
|
134
|
+
blocks?: readonly SlackBlock[];
|
|
135
|
+
text?: string;
|
|
136
|
+
}
|
|
137
|
+
/** Optional renderer used to turn notification templates into concrete Slack content. */
|
|
138
|
+
export interface SlackTemplateRenderer {
|
|
139
|
+
/**
|
|
140
|
+
* Renders one notification template into Slack text and/or Block Kit content.
|
|
141
|
+
*
|
|
142
|
+
* @typeParam TPayload Payload shape carried by the notification request.
|
|
143
|
+
* @param input Template render input including the template key and opaque payload.
|
|
144
|
+
* @returns Rendered text or block fragments that are merged with explicit payload overrides.
|
|
145
|
+
*/
|
|
146
|
+
render<TPayload extends SlackNotificationPayload = SlackNotificationPayload>(input: SlackTemplateRenderInput<TPayload>): MaybePromise<SlackTemplateRenderResult>;
|
|
147
|
+
}
|
|
148
|
+
/** Notification payload understood by {@link SlackChannel} and {@link SlackService.sendNotification}. */
|
|
149
|
+
export interface SlackNotificationPayload extends Record<string, unknown> {
|
|
150
|
+
attachments?: readonly SlackAttachment[];
|
|
151
|
+
blocks?: readonly SlackBlock[];
|
|
152
|
+
channel?: string;
|
|
153
|
+
iconEmoji?: string;
|
|
154
|
+
iconUrl?: string;
|
|
155
|
+
metadata?: Record<string, unknown>;
|
|
156
|
+
mrkdwn?: boolean;
|
|
157
|
+
replyBroadcast?: boolean;
|
|
158
|
+
text?: string;
|
|
159
|
+
threadTs?: string;
|
|
160
|
+
unfurlLinks?: boolean;
|
|
161
|
+
unfurlMedia?: boolean;
|
|
162
|
+
username?: string;
|
|
163
|
+
}
|
|
164
|
+
/** Shared notification request subtype consumed by the Slack channel implementation. */
|
|
165
|
+
export interface SlackNotificationDispatchRequest extends NotificationDispatchRequest<SlackNotificationPayload> {
|
|
166
|
+
channel: string;
|
|
167
|
+
}
|
|
168
|
+
/** Caller-visible result returned by standalone and notification-backed Slack delivery. */
|
|
169
|
+
export interface SlackSendResult extends SlackTransportReceipt {
|
|
170
|
+
ok: boolean;
|
|
171
|
+
warnings: readonly string[];
|
|
172
|
+
}
|
|
173
|
+
/** Failure entry returned by tolerant batch delivery. */
|
|
174
|
+
export interface SlackSendFailure {
|
|
175
|
+
error: Error;
|
|
176
|
+
message: SlackMessage;
|
|
177
|
+
}
|
|
178
|
+
/** Summary returned by {@link SlackService.sendMany}. */
|
|
179
|
+
export interface SlackSendBatchResult {
|
|
180
|
+
failed: number;
|
|
181
|
+
failures: readonly SlackSendFailure[];
|
|
182
|
+
results: readonly SlackSendResult[];
|
|
183
|
+
succeeded: number;
|
|
184
|
+
}
|
|
185
|
+
/** Additional send controls applied to one Slack delivery attempt. */
|
|
186
|
+
export interface SlackSendOptions {
|
|
187
|
+
signal?: AbortSignal;
|
|
188
|
+
}
|
|
189
|
+
/** Additional controls applied to one batch send operation. */
|
|
190
|
+
export interface SlackSendManyOptions extends SlackSendOptions {
|
|
191
|
+
continueOnError?: boolean;
|
|
192
|
+
}
|
|
193
|
+
/** Module options accepted by {@link SlackModule.forRoot} and `forRootAsync`. */
|
|
194
|
+
export interface SlackModuleOptions {
|
|
195
|
+
defaultChannel?: string;
|
|
196
|
+
notifications?: {
|
|
197
|
+
channel?: string;
|
|
198
|
+
};
|
|
199
|
+
renderer?: SlackTemplateRenderer;
|
|
200
|
+
transport: SlackTransport | SlackTransportFactory;
|
|
201
|
+
verifyOnModuleInit?: boolean;
|
|
202
|
+
}
|
|
203
|
+
/** Async registration options for Slack modules that derive config through DI. */
|
|
204
|
+
export type SlackAsyncModuleOptions = AsyncModuleOptions<SlackModuleOptions>;
|
|
205
|
+
/** Normalized module options resolved once during module registration. */
|
|
206
|
+
export interface NormalizedSlackModuleOptions {
|
|
207
|
+
defaultChannel?: string;
|
|
208
|
+
notifications: {
|
|
209
|
+
channel: string;
|
|
210
|
+
};
|
|
211
|
+
renderer?: SlackTemplateRenderer;
|
|
212
|
+
transport: {
|
|
213
|
+
create: () => Promise<SlackTransport>;
|
|
214
|
+
kind: string;
|
|
215
|
+
ownsResources: boolean;
|
|
216
|
+
};
|
|
217
|
+
verifyOnModuleInit: boolean;
|
|
218
|
+
}
|
|
219
|
+
/** Slack facade exposed to application code and the compatibility token. */
|
|
220
|
+
export interface Slack {
|
|
221
|
+
/**
|
|
222
|
+
* Sends one Slack message directly through the configured transport.
|
|
223
|
+
*
|
|
224
|
+
* @param message Caller-supplied Slack message with text and/or block content.
|
|
225
|
+
* @param options Optional abort signal propagated to the transport.
|
|
226
|
+
* @returns A normalized delivery receipt describing the transport response.
|
|
227
|
+
*/
|
|
228
|
+
send(message: SlackMessage, options?: SlackSendOptions): Promise<SlackSendResult>;
|
|
229
|
+
/**
|
|
230
|
+
* Sends multiple Slack messages in input order with optional tolerant failure handling.
|
|
231
|
+
*
|
|
232
|
+
* @param messages Ordered message list to deliver through the configured transport.
|
|
233
|
+
* @param options Optional tolerant batch controls such as `continueOnError`.
|
|
234
|
+
* @returns A batch summary containing successes and any captured failures.
|
|
235
|
+
*/
|
|
236
|
+
sendMany(messages: readonly SlackMessage[], options?: SlackSendManyOptions): Promise<SlackSendBatchResult>;
|
|
237
|
+
/**
|
|
238
|
+
* Converts one notifications foundation request into a concrete Slack delivery.
|
|
239
|
+
*
|
|
240
|
+
* @param notification Shared notification envelope interpreted by the Slack package.
|
|
241
|
+
* @param options Optional abort signal propagated to rendering and transport work.
|
|
242
|
+
* @returns A normalized delivery receipt for the resulting Slack message.
|
|
243
|
+
*/
|
|
244
|
+
sendNotification(notification: SlackNotificationDispatchRequest, options?: SlackSendOptions): Promise<SlackSendResult>;
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SlackTransport, SlackWebhookTransportOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a webhook-first Slack transport backed by an explicit fetch-compatible boundary.
|
|
4
|
+
*
|
|
5
|
+
* @param options Webhook URL plus an optional injected fetch implementation for portable runtimes.
|
|
6
|
+
* @returns A Slack transport that posts JSON payloads to one Slack incoming webhook endpoint.
|
|
7
|
+
* @throws {SlackConfigurationError} When the webhook url is empty or no fetch implementation is available.
|
|
8
|
+
* @throws {SlackTransportError} When Slack responds with a non-success HTTP status.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const transport = createSlackWebhookTransport({
|
|
13
|
+
* fetch: runtime.fetch,
|
|
14
|
+
* webhookUrl: 'https://hooks.slack.com/services/XXX/YYY/ZZZ',
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function createSlackWebhookTransport(options: SlackWebhookTransportOptions): SlackTransport;
|
|
19
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,cAAc,EAEd,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAwFpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,cAAc,CA6DjG"}
|
package/dist/webhook.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { SlackConfigurationError, SlackTransportError } from './errors.js';
|
|
2
|
+
const DEFAULT_RETRY_ATTEMPTS = 3;
|
|
3
|
+
const DEFAULT_RETRY_DELAY_MS = 250;
|
|
4
|
+
function normalizeOptionalString(value) {
|
|
5
|
+
const trimmed = value?.trim();
|
|
6
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
7
|
+
}
|
|
8
|
+
function createWebhookPayload(message) {
|
|
9
|
+
return {
|
|
10
|
+
...(message.attachments.length > 0 ? {
|
|
11
|
+
attachments: message.attachments
|
|
12
|
+
} : {}),
|
|
13
|
+
...(message.blocks.length > 0 ? {
|
|
14
|
+
blocks: message.blocks
|
|
15
|
+
} : {}),
|
|
16
|
+
...(message.channel ? {
|
|
17
|
+
channel: message.channel
|
|
18
|
+
} : {}),
|
|
19
|
+
...(message.iconEmoji ? {
|
|
20
|
+
icon_emoji: message.iconEmoji
|
|
21
|
+
} : {}),
|
|
22
|
+
...(message.iconUrl ? {
|
|
23
|
+
icon_url: message.iconUrl
|
|
24
|
+
} : {}),
|
|
25
|
+
...(message.metadata ? {
|
|
26
|
+
metadata: message.metadata
|
|
27
|
+
} : {}),
|
|
28
|
+
...(typeof message.mrkdwn === 'boolean' ? {
|
|
29
|
+
mrkdwn: message.mrkdwn
|
|
30
|
+
} : {}),
|
|
31
|
+
...(typeof message.replyBroadcast === 'boolean' ? {
|
|
32
|
+
reply_broadcast: message.replyBroadcast
|
|
33
|
+
} : {}),
|
|
34
|
+
...(message.text ? {
|
|
35
|
+
text: message.text
|
|
36
|
+
} : {}),
|
|
37
|
+
...(message.threadTs ? {
|
|
38
|
+
thread_ts: message.threadTs
|
|
39
|
+
} : {}),
|
|
40
|
+
...(typeof message.unfurlLinks === 'boolean' ? {
|
|
41
|
+
unfurl_links: message.unfurlLinks
|
|
42
|
+
} : {}),
|
|
43
|
+
...(typeof message.unfurlMedia === 'boolean' ? {
|
|
44
|
+
unfurl_media: message.unfurlMedia
|
|
45
|
+
} : {}),
|
|
46
|
+
...(message.username ? {
|
|
47
|
+
username: message.username
|
|
48
|
+
} : {})
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveFetch(fetchLike) {
|
|
52
|
+
if (fetchLike) {
|
|
53
|
+
return fetchLike;
|
|
54
|
+
}
|
|
55
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
56
|
+
throw new SlackConfigurationError('Slack webhook transport requires an explicit fetch implementation when `globalThis.fetch` is unavailable.');
|
|
57
|
+
}
|
|
58
|
+
return (input, init) => globalThis.fetch(input, init);
|
|
59
|
+
}
|
|
60
|
+
async function readResponseBody(response) {
|
|
61
|
+
try {
|
|
62
|
+
return await response.text();
|
|
63
|
+
} catch {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function createStatusFailureMessage(response, attempt) {
|
|
68
|
+
return `Slack webhook delivery failed with status ${response.status}${response.statusText ? ` ${response.statusText}` : ''} after ${String(attempt)} attempt(s). Upstream response body was omitted from the caller-visible error.`;
|
|
69
|
+
}
|
|
70
|
+
function createTransportFailureMessage(attempt) {
|
|
71
|
+
return `Slack webhook delivery failed after ${String(attempt)} attempt(s). Upstream response details were omitted from the caller-visible error.`;
|
|
72
|
+
}
|
|
73
|
+
function isAbortError(error) {
|
|
74
|
+
return error instanceof Error && error.name === 'AbortError';
|
|
75
|
+
}
|
|
76
|
+
function isTransientStatus(status) {
|
|
77
|
+
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
78
|
+
}
|
|
79
|
+
async function waitForRetry(delayMs, signal) {
|
|
80
|
+
if (delayMs <= 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await new Promise((resolve, reject) => {
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
signal?.removeEventListener('abort', onAbort);
|
|
86
|
+
resolve();
|
|
87
|
+
}, delayMs);
|
|
88
|
+
function onAbort() {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
|
|
91
|
+
}
|
|
92
|
+
if (signal) {
|
|
93
|
+
signal.addEventListener('abort', onAbort, {
|
|
94
|
+
once: true
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a webhook-first Slack transport backed by an explicit fetch-compatible boundary.
|
|
102
|
+
*
|
|
103
|
+
* @param options Webhook URL plus an optional injected fetch implementation for portable runtimes.
|
|
104
|
+
* @returns A Slack transport that posts JSON payloads to one Slack incoming webhook endpoint.
|
|
105
|
+
* @throws {SlackConfigurationError} When the webhook url is empty or no fetch implementation is available.
|
|
106
|
+
* @throws {SlackTransportError} When Slack responds with a non-success HTTP status.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const transport = createSlackWebhookTransport({
|
|
111
|
+
* fetch: runtime.fetch,
|
|
112
|
+
* webhookUrl: 'https://hooks.slack.com/services/XXX/YYY/ZZZ',
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function createSlackWebhookTransport(options) {
|
|
117
|
+
const webhookUrl = normalizeOptionalString(options.webhookUrl);
|
|
118
|
+
if (!webhookUrl) {
|
|
119
|
+
throw new SlackConfigurationError('Slack webhook transport requires a non-empty `webhookUrl`.');
|
|
120
|
+
}
|
|
121
|
+
const fetchLike = resolveFetch(options.fetch);
|
|
122
|
+
return {
|
|
123
|
+
async send(message, context) {
|
|
124
|
+
for (let attempt = 1; attempt <= DEFAULT_RETRY_ATTEMPTS; attempt += 1) {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetchLike(webhookUrl, {
|
|
127
|
+
body: JSON.stringify(createWebhookPayload(message)),
|
|
128
|
+
headers: {
|
|
129
|
+
'content-type': 'application/json; charset=utf-8'
|
|
130
|
+
},
|
|
131
|
+
method: 'POST',
|
|
132
|
+
signal: context.signal
|
|
133
|
+
});
|
|
134
|
+
const body = await readResponseBody(response);
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
if (attempt < DEFAULT_RETRY_ATTEMPTS && isTransientStatus(response.status)) {
|
|
137
|
+
await waitForRetry(DEFAULT_RETRY_DELAY_MS * 2 ** (attempt - 1), context.signal);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
throw new SlackTransportError(createStatusFailureMessage(response, attempt));
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
channel: message.channel,
|
|
144
|
+
ok: true,
|
|
145
|
+
response: body,
|
|
146
|
+
statusCode: response.status,
|
|
147
|
+
warnings: body && body !== 'ok' ? ['Slack webhook returned a non-standard success body.'] : []
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (isAbortError(error) || context.signal?.aborted) {
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
if (attempt < DEFAULT_RETRY_ATTEMPTS) {
|
|
154
|
+
await waitForRetry(DEFAULT_RETRY_DELAY_MS * 2 ** (attempt - 1), context.signal);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (error instanceof SlackTransportError) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
throw new SlackTransportError(createTransportFailureMessage(attempt));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw new SlackTransportError(createTransportFailureMessage(DEFAULT_RETRY_ATTEMPTS));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluojs/slack",
|
|
3
|
+
"description": "Webhook-first, transport-agnostic Slack delivery core for Fluo with notifications integration.",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"fluo",
|
|
6
|
+
"slack",
|
|
7
|
+
"webhook",
|
|
8
|
+
"notifications",
|
|
9
|
+
"portable",
|
|
10
|
+
"fetch"
|
|
11
|
+
],
|
|
12
|
+
"version": "1.0.0-beta.1",
|
|
13
|
+
"private": false,
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/fluojs/fluo.git",
|
|
18
|
+
"directory": "packages/slack"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.0.0"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@fluojs/core": "^1.0.0-beta.1",
|
|
40
|
+
"@fluojs/runtime": "^1.0.0-beta.1",
|
|
41
|
+
"@fluojs/di": "^1.0.0-beta.1",
|
|
42
|
+
"@fluojs/notifications": "^1.0.0-beta.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"vitest": "^3.2.4"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|
|
49
|
+
"build": "pnpm exec babel src --extensions .ts --ignore 'src/**/*.test.ts' --out-dir dist --config-file ../../tooling/babel/babel.config.cjs && pnpm exec tsc -p tsconfig.build.json",
|
|
50
|
+
"typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
51
|
+
"test": "pnpm exec vitest run -c vitest.config.ts",
|
|
52
|
+
"test:watch": "pnpm exec vitest -c vitest.config.ts"
|
|
53
|
+
}
|
|
54
|
+
}
|