@strav/notification 1.0.0-alpha.33 → 1.0.0-alpha.35
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/notification",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.35",
|
|
4
4
|
"description": "Strav multi-channel notifications — NotificationManager fan-out across channel drivers (mail / database / log / webhook / broadcast / discord / sse). Manager+drivers shape; new channels register via `manager.extend(name, factory)`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./webhook": "./src/drivers/webhook/index.ts",
|
|
14
14
|
"./broadcast": "./src/drivers/broadcast/index.ts",
|
|
15
15
|
"./discord": "./src/drivers/discord/index.ts",
|
|
16
|
+
"./line": "./src/drivers/line/index.ts",
|
|
16
17
|
"./sse": "./src/drivers/sse/index.ts",
|
|
17
18
|
"./tenanted": "./src/drivers/database/tenanted/index.ts"
|
|
18
19
|
},
|
|
@@ -27,19 +28,21 @@
|
|
|
27
28
|
"access": "public"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@strav/kernel": "1.0.0-alpha.
|
|
31
|
+
"@strav/kernel": "1.0.0-alpha.35"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@strav/broadcast": "1.0.0-alpha.
|
|
34
|
-
"@strav/database": "1.0.0-alpha.
|
|
35
|
-
"@strav/http": "1.0.0-alpha.
|
|
36
|
-
"@strav/
|
|
34
|
+
"@strav/broadcast": "1.0.0-alpha.35",
|
|
35
|
+
"@strav/database": "1.0.0-alpha.35",
|
|
36
|
+
"@strav/http": "1.0.0-alpha.35",
|
|
37
|
+
"@strav/instant": "1.0.0-alpha.35",
|
|
38
|
+
"@strav/mail": "1.0.0-alpha.35"
|
|
37
39
|
},
|
|
38
40
|
"peerDependencies": {
|
|
39
|
-
"@strav/broadcast": "1.0.0-alpha.
|
|
40
|
-
"@strav/database": "1.0.0-alpha.
|
|
41
|
-
"@strav/http": "1.0.0-alpha.
|
|
42
|
-
"@strav/
|
|
41
|
+
"@strav/broadcast": "1.0.0-alpha.35",
|
|
42
|
+
"@strav/database": "1.0.0-alpha.35",
|
|
43
|
+
"@strav/http": "1.0.0-alpha.35",
|
|
44
|
+
"@strav/instant": "1.0.0-alpha.35",
|
|
45
|
+
"@strav/mail": "1.0.0-alpha.35",
|
|
43
46
|
"@types/bun": ">=1.3.14"
|
|
44
47
|
},
|
|
45
48
|
"peerDependenciesMeta": {
|
|
@@ -52,6 +55,9 @@
|
|
|
52
55
|
"@strav/http": {
|
|
53
56
|
"optional": true
|
|
54
57
|
},
|
|
58
|
+
"@strav/instant": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
55
61
|
"@strav/mail": {
|
|
56
62
|
"optional": true
|
|
57
63
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { LineChannelConfig } from './line_config.ts'
|
|
2
|
+
export {
|
|
3
|
+
LineNotificationDriver,
|
|
4
|
+
type LineNotificationDriverOptions,
|
|
5
|
+
type LineNotificationMessage,
|
|
6
|
+
} from './line_notification_driver.ts'
|
|
7
|
+
export { LineNotificationProvider } from './line_notification_provider.ts'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor-specific config shape for the LINE notification channel.
|
|
3
|
+
* Discriminator `driver: 'line'` selects this factory at
|
|
4
|
+
* `manager.use(...)` time.
|
|
5
|
+
*
|
|
6
|
+
* The LINE channel delegates to `@strav/instant`'s LINE driver under
|
|
7
|
+
* the hood — apps only configure which `config.instant.providers`
|
|
8
|
+
* entry to route through. Authentication / channel access tokens /
|
|
9
|
+
* webhook signature handling stay on `@strav/instant`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ChannelConfig } from '../../notification_config.ts'
|
|
13
|
+
|
|
14
|
+
export interface LineChannelConfig extends ChannelConfig {
|
|
15
|
+
driver: 'line'
|
|
16
|
+
/**
|
|
17
|
+
* Name of the `config.instant.providers` entry whose driver this
|
|
18
|
+
* channel should push through. Defaults to `'line'`. Apps running
|
|
19
|
+
* one LINE bot for support + another for marketing point each
|
|
20
|
+
* notification channel at the matching instance.
|
|
21
|
+
*/
|
|
22
|
+
instantProvider?: string
|
|
23
|
+
/**
|
|
24
|
+
* Default display name shown when the hook returns a plain `string`.
|
|
25
|
+
* Apps almost never set this — LINE messages don't carry sender
|
|
26
|
+
* overrides the way Discord does — but it's surfaced to the hook in
|
|
27
|
+
* `defaults` so apps can branch on it.
|
|
28
|
+
*/
|
|
29
|
+
sender?: string
|
|
30
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LineNotificationDriver` — sends notifications via the LINE
|
|
3
|
+
* Messaging API by delegating to `@strav/instant`'s LINE driver.
|
|
4
|
+
*
|
|
5
|
+
* The notification system stays out of the LINE specifics — auth,
|
|
6
|
+
* webhook signature, Flex builder, rich menus all live in
|
|
7
|
+
* `@strav/instant/line`. This channel is the thin adapter from
|
|
8
|
+
* `BaseNotification` → `instant.use('line').push(...)` (single
|
|
9
|
+
* recipient) or `.multicast(...)` (≤500 recipients per LINE limit).
|
|
10
|
+
*
|
|
11
|
+
* Reads `notification.toLine(notifiable, defaults)` for the message.
|
|
12
|
+
* The hook can return:
|
|
13
|
+
*
|
|
14
|
+
* - A `string` — shorthand for `{ text: <string> }` text-only push.
|
|
15
|
+
* - An `OutgoingMessage` (from `@strav/instant`) — full envelope
|
|
16
|
+
* with attachments / quick replies / Flex via `raw`.
|
|
17
|
+
* - A `LineNotificationMessage` envelope with an optional `to`
|
|
18
|
+
* override (single id or array — array triggers multicast).
|
|
19
|
+
*
|
|
20
|
+
* Recipient resolution order: hook `to` → `notifiable.lineUserIds`
|
|
21
|
+
* (multicast) → `notifiable.lineUserId` (single push). None resolved
|
|
22
|
+
* → `{ delivered: false }` with no error (same opt-out semantics as
|
|
23
|
+
* the mail / discord channels).
|
|
24
|
+
*
|
|
25
|
+
* Flex shortcut: pass a `flex` field on the envelope — the driver
|
|
26
|
+
* lifts it into `OutgoingMessage.raw` so apps don't have to know the
|
|
27
|
+
* `raw` plumbing. The Flex object itself comes from
|
|
28
|
+
* `@strav/instant/line`'s typed `flex.*` factories.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { InstantManager } from '@strav/instant'
|
|
32
|
+
import type { Notifiable } from '../../notifiable.ts'
|
|
33
|
+
import type { BaseNotification } from '../../notification.ts'
|
|
34
|
+
import type { NotificationDriver } from '../../notification_driver.ts'
|
|
35
|
+
import { NotificationDeliveryError } from '../../notification_error.ts'
|
|
36
|
+
import type { NotificationContext, NotificationDeliveryResult } from '../../types.ts'
|
|
37
|
+
|
|
38
|
+
/** Shape returned by `notification.toLine(notifiable, defaults)`. */
|
|
39
|
+
export interface LineNotificationMessage {
|
|
40
|
+
/** Plain-text body. */
|
|
41
|
+
text?: string
|
|
42
|
+
/** Native `@strav/instant` attachments (image / video / audio / location / sticker). */
|
|
43
|
+
attachments?: import('@strav/instant').Attachment[]
|
|
44
|
+
/** Native quick replies. */
|
|
45
|
+
quickReplies?: import('@strav/instant').QuickReply[]
|
|
46
|
+
/**
|
|
47
|
+
* Flex object — shortcut for `raw`. Build via `@strav/instant/line`'s
|
|
48
|
+
* `flex.bubble(...)` / `flex.carousel(...)` factories. The driver
|
|
49
|
+
* wraps it into a LINE Flex message envelope.
|
|
50
|
+
*/
|
|
51
|
+
flex?: unknown
|
|
52
|
+
/**
|
|
53
|
+
* Override the recipient(s). A single string → push; an array of
|
|
54
|
+
* strings → multicast. When set, the driver ignores
|
|
55
|
+
* `notifiable.lineUserId(s)`.
|
|
56
|
+
*/
|
|
57
|
+
to?: string | readonly string[]
|
|
58
|
+
/**
|
|
59
|
+
* Provider-native message object. When set, forwarded verbatim and
|
|
60
|
+
* supersedes the LCD fields above.
|
|
61
|
+
*/
|
|
62
|
+
raw?: unknown
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Hook surface — apps add `toLine(notifiable, defaults)` on their notification. */
|
|
66
|
+
interface LineCapableNotification extends BaseNotification {
|
|
67
|
+
toLine?(
|
|
68
|
+
notifiable: Notifiable,
|
|
69
|
+
defaults: { sender?: string },
|
|
70
|
+
):
|
|
71
|
+
| string
|
|
72
|
+
| LineNotificationMessage
|
|
73
|
+
| import('@strav/instant').OutgoingMessage
|
|
74
|
+
| Promise<string | LineNotificationMessage | import('@strav/instant').OutgoingMessage>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface NotifiableWithLine extends Notifiable {
|
|
78
|
+
lineUserId?: string
|
|
79
|
+
lineUserIds?: readonly string[]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface LineNotificationDriverOptions {
|
|
83
|
+
name: string
|
|
84
|
+
manager: InstantManager
|
|
85
|
+
instantProvider?: string
|
|
86
|
+
sender?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class LineNotificationDriver implements NotificationDriver {
|
|
90
|
+
readonly name: string
|
|
91
|
+
private readonly manager: InstantManager
|
|
92
|
+
private readonly instantProvider: string
|
|
93
|
+
private readonly sender: string | undefined
|
|
94
|
+
|
|
95
|
+
constructor(options: LineNotificationDriverOptions) {
|
|
96
|
+
this.name = options.name
|
|
97
|
+
this.manager = options.manager
|
|
98
|
+
this.instantProvider = options.instantProvider ?? 'line'
|
|
99
|
+
this.sender = options.sender
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async send(
|
|
103
|
+
notifiable: Notifiable,
|
|
104
|
+
notification: BaseNotification,
|
|
105
|
+
context: NotificationContext,
|
|
106
|
+
): Promise<NotificationDeliveryResult> {
|
|
107
|
+
const hook = (notification as LineCapableNotification).toLine
|
|
108
|
+
if (typeof hook !== 'function') {
|
|
109
|
+
return { channel: this.name, delivered: false }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const defaults = this.sender !== undefined ? { sender: this.sender } : {}
|
|
113
|
+
const raw = await hook.call(notification, notifiable, defaults)
|
|
114
|
+
const envelope = normaliseEnvelope(raw)
|
|
115
|
+
|
|
116
|
+
const to = resolveRecipients(envelope.to, notifiable)
|
|
117
|
+
if (to === null) {
|
|
118
|
+
return { channel: this.name, delivered: false }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const outgoing = toOutgoing(envelope)
|
|
122
|
+
const driver = this.manager.use(this.instantProvider)
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const result =
|
|
126
|
+
typeof to === 'string'
|
|
127
|
+
? await driver.push!(to, outgoing)
|
|
128
|
+
: await driver.multicast!(to, outgoing)
|
|
129
|
+
return {
|
|
130
|
+
channel: this.name,
|
|
131
|
+
delivered: !!result.accepted,
|
|
132
|
+
...(result.messageId ? { reference: result.messageId } : { reference: context.id }),
|
|
133
|
+
}
|
|
134
|
+
} catch (cause) {
|
|
135
|
+
throw new NotificationDeliveryError(
|
|
136
|
+
`LineNotificationDriver: delivery via instant provider "${this.instantProvider}" failed.`,
|
|
137
|
+
{
|
|
138
|
+
context: {
|
|
139
|
+
channel: this.name,
|
|
140
|
+
notifiableId: notifiable.id,
|
|
141
|
+
notification: notification.constructor.name,
|
|
142
|
+
instantProvider: this.instantProvider,
|
|
143
|
+
retryable: true,
|
|
144
|
+
},
|
|
145
|
+
cause,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function normaliseEnvelope(
|
|
153
|
+
raw: string | LineNotificationMessage | import('@strav/instant').OutgoingMessage,
|
|
154
|
+
): LineNotificationMessage {
|
|
155
|
+
if (typeof raw === 'string') return { text: raw }
|
|
156
|
+
return raw as LineNotificationMessage
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveRecipients(
|
|
160
|
+
override: string | readonly string[] | undefined,
|
|
161
|
+
notifiable: Notifiable,
|
|
162
|
+
): string | readonly string[] | null {
|
|
163
|
+
if (override !== undefined) {
|
|
164
|
+
if (Array.isArray(override)) return override.length > 0 ? override : null
|
|
165
|
+
return override || null
|
|
166
|
+
}
|
|
167
|
+
const n = notifiable as NotifiableWithLine
|
|
168
|
+
if (n.lineUserIds && n.lineUserIds.length > 0) return n.lineUserIds
|
|
169
|
+
if (n.lineUserId) return n.lineUserId
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toOutgoing(
|
|
174
|
+
envelope: LineNotificationMessage,
|
|
175
|
+
): import('@strav/instant').OutgoingMessage {
|
|
176
|
+
// Flex shortcut: lift the flex object into a LINE Flex message
|
|
177
|
+
// envelope on `raw`, where `@strav/instant/line`'s mapper forwards
|
|
178
|
+
// it verbatim to the wire.
|
|
179
|
+
if (envelope.flex !== undefined && envelope.raw === undefined) {
|
|
180
|
+
return {
|
|
181
|
+
...(envelope.text !== undefined ? { text: envelope.text } : {}),
|
|
182
|
+
raw: [{ type: 'flex', altText: envelope.text ?? 'Notification', contents: envelope.flex }],
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
...(envelope.text !== undefined ? { text: envelope.text } : {}),
|
|
187
|
+
...(envelope.attachments !== undefined ? { attachments: envelope.attachments } : {}),
|
|
188
|
+
...(envelope.quickReplies !== undefined ? { quickReplies: envelope.quickReplies } : {}),
|
|
189
|
+
...(envelope.raw !== undefined ? { raw: envelope.raw } : {}),
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceProvider that registers the LINE channel factory on the
|
|
3
|
+
* `NotificationManager`. Apps include this in their provider list
|
|
4
|
+
* AFTER `NotificationProvider` AND `InstantProvider` — the factory
|
|
5
|
+
* resolves whenever `config.notification.channels.<name>.driver ===
|
|
6
|
+
* 'line'` and forwards through the matching `InstantManager` instance.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { InstantManager } from '@strav/instant'
|
|
10
|
+
import { type Application, ServiceProvider } from '@strav/kernel'
|
|
11
|
+
import { NotificationManager } from '../../notification_manager.ts'
|
|
12
|
+
import type { LineChannelConfig } from './line_config.ts'
|
|
13
|
+
import { LineNotificationDriver } from './line_notification_driver.ts'
|
|
14
|
+
|
|
15
|
+
export class LineNotificationProvider extends ServiceProvider {
|
|
16
|
+
override readonly name = 'notification.line'
|
|
17
|
+
override readonly dependencies = ['notification', 'instant']
|
|
18
|
+
|
|
19
|
+
override async boot(app: Application): Promise<void> {
|
|
20
|
+
const manager = app.resolve(NotificationManager)
|
|
21
|
+
const instant = app.resolve(InstantManager)
|
|
22
|
+
manager.extend('line', ({ instanceName, config }) => {
|
|
23
|
+
const cfg = config as LineChannelConfig
|
|
24
|
+
return new LineNotificationDriver({
|
|
25
|
+
name: instanceName,
|
|
26
|
+
manager: instant,
|
|
27
|
+
...(cfg.instantProvider !== undefined ? { instantProvider: cfg.instantProvider } : {}),
|
|
28
|
+
...(cfg.sender !== undefined ? { sender: cfg.sender } : {}),
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|