@interactive-inc/flume 0.10.0 → 0.11.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.
- package/README.md +93 -32
- package/dist/discord.d.ts +15 -1
- package/dist/discord.js +304 -35
- package/dist/flume-source.d.ts +58 -14
- package/dist/flume-source.js +142 -87
- package/dist/github.d.ts +5 -1
- package/dist/github.js +217 -69
- package/dist/index.d.ts +223 -46
- package/dist/index.js +466 -149
- package/dist/parse-error.d.ts +9 -0
- package/dist/safe-json-parse.js +1 -1
- package/dist/safe-read-text.js +22 -5
- package/dist/safe-stringify.js +34 -134
- package/dist/schedule-reconnect.js +153 -0
- package/dist/slack.d.ts +8 -1
- package/dist/slack.js +230 -34
- package/dist/time.d.ts +81 -2
- package/dist/time.js +625 -2
- package/package.json +11 -4
- package/dist/connection-error.js +0 -16
- package/dist/http-error.js +0 -12
- package/dist/parse-cron.d.ts +0 -55
- package/dist/time-source.js +0 -427
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ if (running instanceof Error) throw running
|
|
|
48
48
|
|
|
49
49
|
// later
|
|
50
50
|
await running.close()
|
|
51
|
+
await running.drain() // outside callbacks: finish accepted callback work and diagnostics
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
`new Flume({ sources, ...options })` — a single options object; `sources` is the only required field, everything else is optional. There is one unified firehose: `onEvent` (push) and `FlumeRunning.stream()` (pull) both deliver the same `FlumeStreamItem` — received events **and** every log (status transitions, errors, debug) merged into one stream. The consumer filters by `item.kind` (`"event"` / `"log"`) and `item.log.level`. This is built for piping the whole picture into an agent (Claude / Codex) so it notices disconnects on its own. `onError` is a convenience filter that additionally receives only the `level: "error"` logs (route it straight to Sentry).
|
|
@@ -70,8 +71,10 @@ Flume ──open()──▶ FlumeRunning ──close()──▶ FlumeClosed
|
|
|
70
71
|
```
|
|
71
72
|
|
|
72
73
|
- `Flume.open()` returns `FlumeRunning | FlumeStartError`. Branch with `instanceof Error`. On partial failure (one source fails while another succeeds), the already-opened sources are rolled back and a `FlumeStartError` is returned with per-source detail in `.message`. Calling `open()` a second time on the same `Flume` instance returns `FlumeStartError` at runtime — the type system also rejects calling `open()` on the returned `FlumeRunning`/`FlumeClosed` handles.
|
|
73
|
-
- `FlumeRunning.close()` returns a `FlumeClosed` snapshot.
|
|
74
|
-
- `
|
|
74
|
+
- `FlumeRunning.close()` waits for sources to stop and returns a `FlumeClosed` snapshot. It is idempotent and concurrent-safe, and can be awaited inside `onEvent` or `onError`.
|
|
75
|
+
- `FlumeRunning.drain()` waits for accepted callbacks and their failure diagnostics. Call it **outside callbacks**, normally after `close()`, when shutdown must also finish callback work. Calling it inside a callback would wait for that callback itself.
|
|
76
|
+
- `open()` and `close()` do not wait for observation callbacks. A slow callback cannot hold startup or source shutdown open. Callback delivery remains serial; accepted callbacks may finish after `close()` returns.
|
|
77
|
+
- `FlumeClosed` exposes `statuses()` and `errors()` — frozen snapshots of final source states and disconnect failures. No `open`, no `close`, no leaking source references.
|
|
75
78
|
- An `AbortSignal` on `Flume` drives an automatic transition to `FlumeClosed`.
|
|
76
79
|
- `FlumeRunning.kind === "running"` and `FlumeClosed.kind === "closed"` provide a runtime discriminator when generic code holds the union.
|
|
77
80
|
|
|
@@ -89,8 +92,11 @@ const closed = await running.close()
|
|
|
89
92
|
closed.close() // type error
|
|
90
93
|
closed.open() // type error
|
|
91
94
|
closed.statuses() // [{ source: "discord", status: "disconnected" }, ...]
|
|
95
|
+
await running.drain() // finish callback delivery before exiting the host process
|
|
92
96
|
```
|
|
93
97
|
|
|
98
|
+
Migration from 0.10.1: shutdown code that relied on `close()` waiting for `onEvent` / `onError` should now use `await running.close(); await running.drain()` outside those callbacks. A callback may use `await running.close()` on its own. Pull streams finish after the accepted callbacks and their failure diagnostics have drained. A callback that never settles can therefore keep `drain()` and stream completion pending, while source shutdown still completes.
|
|
99
|
+
|
|
94
100
|
## Dynamic groups
|
|
95
101
|
|
|
96
102
|
A `Flume` is single-use: its source set is fixed at construction and the FSM is terminal once closed. To add or drop sources at runtime, compose at a higher level with `FlumeConfluence` — it holds many `Flume` instances, merges all their firehoses into one `onEvent`, and lets you `add` / `remove` groups while the others keep running.
|
|
@@ -100,7 +106,7 @@ import { FlumeConfluence } from "@interactive-inc/flume"
|
|
|
100
106
|
|
|
101
107
|
const confluence = new FlumeConfluence({
|
|
102
108
|
onEvent: (item) => {
|
|
103
|
-
if (item.kind === "event") feedToAgent(item.event)
|
|
109
|
+
if (item.kind === "event") feedToAgent(item.groupId, item.event)
|
|
104
110
|
if (item.kind === "log" && item.log.action === "status") noticeDisconnect(item.log)
|
|
105
111
|
},
|
|
106
112
|
reconnect: { maxAttempts: 10 },
|
|
@@ -112,7 +118,9 @@ await confluence.remove("team-a") // stops only team-a
|
|
|
112
118
|
await confluence.closeAll()
|
|
113
119
|
```
|
|
114
120
|
|
|
115
|
-
`add(id, sources)` starts a fresh `Flume` for that group and returns `Error | null` (a duplicate id or a failed start is returned, never thrown).
|
|
121
|
+
`add(id, sources)` starts a fresh `Flume` for that group and returns `Error | null` (a duplicate id or a failed start is returned, never thrown). Every merged item is stamped with that id as `groupId`, while `item.event.source` / `item.log.source` identifies the protocol source inside the group. Each group is an independent `Flume`, so a failure in one group never rolls back another. `replace(id, sources)` opens a replacement first and closes the previous group only after the new one is ready; a failed replacement leaves the previous group running.
|
|
122
|
+
|
|
123
|
+
Source instances are single-use. Reusing an existing instance returns a `FlumeStartError` and preserves its current owner's connection. `remove()` / `closeAll()` wait for source shutdown; callbacks already accepted by those groups may finish afterward.
|
|
116
124
|
|
|
117
125
|
## Sub-entries
|
|
118
126
|
|
|
@@ -126,24 +134,32 @@ import { FlumeGitHubSource } from "@interactive-inc/flume/github"
|
|
|
126
134
|
import { FlumeTimeSource } from "@interactive-inc/flume/time"
|
|
127
135
|
```
|
|
128
136
|
|
|
129
|
-
- `@interactive-inc/flume` — `Flume`, `FlumeConfluence`, `FlumeRunning`, `FlumeClosed`, `FlumeSource` (abstract base for third-party sources), `createFlumeDefaultDeps`, errors, types
|
|
137
|
+
- `@interactive-inc/flume` — `Flume`, `FlumeConfluence`, `FlumeRunning`, `FlumeClosed`, `FlumeSource` (abstract base for third-party sources), `createFlumeDefaultDeps`, errors, types, and the common `attempt` / `safe*` utilities used by custom sources
|
|
130
138
|
- `@interactive-inc/flume/discord` — `FlumeDiscordSource`, `FlumeDiscordGatewayIntents`, `flumeExtractDiscordMeta`
|
|
131
139
|
- `@interactive-inc/flume/slack` — `FlumeSlackSource`, `flumeExtractSlackMeta`
|
|
132
140
|
- `@interactive-inc/flume/github` — `FlumeGitHubSource`, `flumeExtractGitHubMeta`
|
|
133
|
-
- `@interactive-inc/flume/time` — `FlumeTimeSource`, `parseCron`, `flumeCronNext`
|
|
141
|
+
- `@interactive-inc/flume/time` — `FlumeTimeSource`, `parseCron`, `flumeCronNext`, `flumeCollectCatchupMatches`
|
|
134
142
|
|
|
135
143
|
## Custom sources
|
|
136
144
|
|
|
137
145
|
Extend `FlumeSource` to plug in any protocol. The base class owns `start`/`stop`/`status`, the per-source event queue, status emission (logged on every transition), and consumed/stopped guards. You implement `connect` (open the protocol, emit events, set status) and `disconnect` (tear it down).
|
|
138
146
|
|
|
139
147
|
```ts
|
|
140
|
-
import {
|
|
141
|
-
|
|
148
|
+
import {
|
|
149
|
+
attempt,
|
|
150
|
+
FlumeSource,
|
|
151
|
+
safeErrorMessage,
|
|
152
|
+
safeInvokeCallback,
|
|
153
|
+
safeJsonParse,
|
|
154
|
+
safeNow,
|
|
155
|
+
safeReadText,
|
|
156
|
+
} from "@interactive-inc/flume"
|
|
157
|
+
import type { FlumeSourceStartContext, FlumeTimerHandle } from "@interactive-inc/flume"
|
|
142
158
|
|
|
143
159
|
class MyWebhookSource extends FlumeSource {
|
|
144
160
|
readonly name = "my-webhook"
|
|
145
161
|
|
|
146
|
-
private timer:
|
|
162
|
+
private timer: FlumeTimerHandle | null = null
|
|
147
163
|
|
|
148
164
|
constructor(private readonly options: { url: string; pollInterval?: number }) {
|
|
149
165
|
super()
|
|
@@ -152,33 +168,73 @@ class MyWebhookSource extends FlumeSource {
|
|
|
152
168
|
protected async connect(ctx: FlumeSourceStartContext): Promise<Error | null> {
|
|
153
169
|
this.setStatus("connecting")
|
|
154
170
|
const interval = (this.options.pollInterval ?? 30) * 1000
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
const timer = attempt(() => ctx.deps.setInterval(() => this.runPoll(ctx), interval))
|
|
172
|
+
if (timer instanceof Error) return timer
|
|
173
|
+
this.timer = timer
|
|
158
174
|
this.setStatus("connected")
|
|
159
175
|
return null
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
protected disconnect(): void {
|
|
163
|
-
|
|
179
|
+
const timer = this.timer
|
|
164
180
|
this.timer = null
|
|
181
|
+
const ctx = this.context
|
|
182
|
+
if (timer === null || ctx === null) return
|
|
183
|
+
|
|
184
|
+
const result = attempt(() => ctx.deps.clearInterval(timer))
|
|
185
|
+
if (result instanceof Error) {
|
|
186
|
+
ctx.log.error({
|
|
187
|
+
action: "timer.clear.error",
|
|
188
|
+
message: safeErrorMessage({ error: result }),
|
|
189
|
+
error: result,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private runPoll(ctx: FlumeSourceStartContext): void {
|
|
195
|
+
safeInvokeCallback({
|
|
196
|
+
fn: async () => {
|
|
197
|
+
const result = await this.poll(ctx)
|
|
198
|
+
if (result instanceof Error) {
|
|
199
|
+
ctx.log.error({
|
|
200
|
+
action: "poll.error",
|
|
201
|
+
message: safeErrorMessage({ error: result }),
|
|
202
|
+
error: result,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
onError: (error) => {
|
|
207
|
+
ctx.log.error({
|
|
208
|
+
action: "poll.unhandled",
|
|
209
|
+
message: safeErrorMessage({ error }),
|
|
210
|
+
error,
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
})
|
|
165
214
|
}
|
|
166
215
|
|
|
167
|
-
private async poll(ctx: FlumeSourceStartContext): Promise<
|
|
168
|
-
const
|
|
169
|
-
|
|
216
|
+
private async poll(ctx: FlumeSourceStartContext): Promise<Error | null> {
|
|
217
|
+
const response = await attempt(() => ctx.deps.fetch(this.options.url))
|
|
218
|
+
if (response instanceof Error) return response
|
|
219
|
+
const raw = await safeReadText({ response, context: this.name })
|
|
220
|
+
if (raw instanceof Error) return raw
|
|
221
|
+
const payload = safeJsonParse(raw)
|
|
222
|
+
if (payload instanceof Error) return payload
|
|
223
|
+
|
|
170
224
|
this.emit({
|
|
171
|
-
source:
|
|
225
|
+
source: "custom",
|
|
226
|
+
sourceName: this.name,
|
|
172
227
|
type: "webhook",
|
|
173
|
-
data: payload,
|
|
228
|
+
data: { payload },
|
|
174
229
|
meta: { event_type: "webhook" },
|
|
175
|
-
receivedAt: ctx.deps
|
|
230
|
+
receivedAt: safeNow({ deps: ctx.deps }),
|
|
176
231
|
})
|
|
232
|
+
return null
|
|
177
233
|
}
|
|
178
234
|
}
|
|
179
235
|
```
|
|
180
236
|
|
|
181
|
-
`this.emit({...})` queues events through the base's serial queue and routes them to `ctx.onEvent` with `attempt()` isolation. `this.setStatus(...)` deduplicates idempotent transitions and logs the change (
|
|
237
|
+
`this.emit({...})` queues events through the base's serial queue and routes them to `ctx.onEvent` with `attempt()` isolation. `this.setStatus(...)` deduplicates idempotent transitions and logs the change. IO started after `connect()` returns, such as timer callbacks, must use `attempt()` or `safeInvokeCallback()` as shown above; both are exported from the root entry so custom sources do not need raw try/catch.
|
|
182
238
|
|
|
183
239
|
## Time source
|
|
184
240
|
|
|
@@ -210,6 +266,8 @@ new FlumeTimeSource({
|
|
|
210
266
|
|
|
211
267
|
`tick.firedAt` is the scheduled wall-clock time (epoch ms), not the exact `setTimeout` firing instant. A throwing `message()` is isolated and falls back to the defaults. Parse the cron up front with `parseCron(expr)` (returns `FlumeCron | FlumeParseError`) or compute the next fire with `flumeCronNext(cron, afterMs)`.
|
|
212
268
|
|
|
269
|
+
With `statePersister`, state writes are serialized in tick order and source shutdown waits for queued writes to settle. A failed write is logged and later writes still run. Keep the persister's IO bounded and avoid waiting for Flume lifecycle completion inside it. Stopping during `load()` releases startup immediately and ignores a late result; cancellation of the host's underlying read remains the persister's responsibility. Use `catchupPolicy` (`off`, `lastOnly`, or `missed`) to control replay on the next start. When multiple sources share a persistence key, the host must also serialize or atomically order their writes.
|
|
270
|
+
|
|
213
271
|
## Pull stream
|
|
214
272
|
|
|
215
273
|
`FlumeRunning.stream()` is the pull form of the same firehose as `onEvent` — an async iterator over `FlumeStreamItem`, handy for feeding an agent (Claude / Codex) with `for await`, where backpressure falls out naturally from how fast you pull.
|
|
@@ -224,10 +282,10 @@ for await (const item of running.stream()) {
|
|
|
224
282
|
if (item.kind === "event") await handleWithAgent(item.event)
|
|
225
283
|
if (item.kind === "log" && item.log.action === "status") noticeDisconnect(item.log)
|
|
226
284
|
}
|
|
227
|
-
//
|
|
285
|
+
// after close / signal abort, the loop ends once accepted callback diagnostics are delivered
|
|
228
286
|
```
|
|
229
287
|
|
|
230
|
-
The iterator ends cleanly
|
|
288
|
+
The iterator ends cleanly after source shutdown and accepted callback work; `break`ing out unsubscribes automatically. When a slow consumer lets the buffer overflow, the oldest items are dropped by default:
|
|
231
289
|
|
|
232
290
|
```ts
|
|
233
291
|
running.stream({ buffer: 5000, onOverflow: "drop-newest" })
|
|
@@ -283,7 +341,7 @@ type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent | Flume
|
|
|
283
341
|
|
|
284
342
|
## Observability
|
|
285
343
|
|
|
286
|
-
Flume never calls a third-party service. Every internal action is reported through the firehose (`onEvent` push / `stream()` pull) as `{ kind: "log", log }` items — there are no silent paths. Each `FlumeLog` is tagged with `source: "flume"`, `source: "discord"`, `source: "slack"`, `source: "github"`, `source: "time"`, or your custom source's `name`. `onError` is a convenience filter that additionally receives only the `level: "error"` subset (route it straight to Sentry).
|
|
344
|
+
Flume never calls a third-party service. Every internal action is reported through the firehose (`onEvent` push / `stream()` pull) as `{ kind: "log", log }` items — there are no silent internal paths. Each `FlumeLog` is tagged with `source: "flume"`, `source: "discord"`, `source: "slack"`, `source: "github"`, `source: "time"`, or your custom source's `name`. `onError` is a convenience filter that additionally receives only the `level: "error"` subset (route it straight to Sentry).
|
|
287
345
|
|
|
288
346
|
```ts
|
|
289
347
|
type FlumeLog = {
|
|
@@ -306,6 +364,9 @@ What gets logged:
|
|
|
306
364
|
- Reconnect — `reconnect.scheduled` (with delay in ms), `reconnect.exhausted` (with attempt count), `reconnect.reset` (on successful connect after retries), `reconnect.cancel` (on stop).
|
|
307
365
|
- Status transitions — `status` action with `previous → next`.
|
|
308
366
|
- Errors — `level: "error"` carries the `error` field; use `onError` for a pre-filtered error sink.
|
|
367
|
+
- Observation sink failures — a failed `onEvent` emits `onEvent.error` to `onError` and the pull stream; a failed `onError` emits `onError.error` to `onEvent` and the pull stream. `detail` identifies the callback plus the failed item's kind, source, and event type or log action. `FlumeConfluence` also stamps `groupId` onto logs sent to its `onError`. The peer notification is attempted once, without recursive reporting.
|
|
368
|
+
|
|
369
|
+
For maximum diagnostics, configure both callbacks or keep a pull stream consumer. If both push callbacks fail, both failure logs remain available to the pull stream; startup logs are replayed to its first subscriber. `FlumeConfluence` has no pull iterator, so configure its `onError` as the independent diagnostic sink.
|
|
309
370
|
|
|
310
371
|
Route it anywhere — Sentry, Datadog, `console`, a file — your choice. Filter the log items out of the firehose:
|
|
311
372
|
|
|
@@ -395,9 +456,9 @@ new Flume({
|
|
|
395
456
|
|
|
396
457
|
## Safety
|
|
397
458
|
|
|
398
|
-
- Ordering — each source has its own `FlumeSerialQueue` and per-source events are
|
|
459
|
+
- Ordering — each source has its own `FlumeSerialQueue` and per-source events are published FIFO. The public `onEvent` and `onError` callbacks share one global queue, so callback invocations never race across sources. `close()` stops the sources; a subsequent `drain()` waits for callbacks and their failure diagnostics. Pull subscribers receive items when they are published and have independent bounded buffers. The push callback queue is unbounded; use pull-only consumption for a bounded slow consumer.
|
|
399
460
|
- Duplicate suppression — Slack envelopes are deduped by `envelope_id` (`FlumeSlackSeenCache`) to absorb ack retries. GitHub notifications are deduped by `id + updated_at` (`FlumeGitHubSeenCache`). Discord uses session resume so the Gateway does not re-emit dispatches.
|
|
400
|
-
- Partial-failure rollback — if any source fails during `Flume.open()`,
|
|
461
|
+
- Partial-failure rollback — if any source fails during `Flume.open()`, sources acquired by that open are stopped, including partially connected failures. A source that refused reuse remains with its existing owner. A `FlumeStartError` is returned with per-source detail.
|
|
401
462
|
- Idempotent close — `FlumeRunning.close()` is safe to call concurrently; the first call wins and subsequent callers receive the same `FlumeClosed` snapshot. The same guard exists at source level: a double-`close()` (e.g. via signal abort racing a manual close) does not re-invoke `disconnect()`.
|
|
402
463
|
|
|
403
464
|
## Errors
|
|
@@ -409,9 +470,9 @@ Flume does not throw on protocol/network failures. Every entry point returns `T
|
|
|
409
470
|
- `FlumeHttpError` — HTTP call returned an error payload (e.g. Slack `ok: false`)
|
|
410
471
|
- `FlumeParseError` — Unparseable WebSocket frame
|
|
411
472
|
|
|
412
|
-
Exceptions thrown from `onEvent` are
|
|
473
|
+
Exceptions thrown or rejected from `onEvent` / `onError` are isolated and emitted as `onEvent.error` / `onError.error` diagnostics; they are never rethrown into the protocol loop.
|
|
413
474
|
|
|
414
|
-
|
|
475
|
+
With type-valid constructor input, the library guarantees that protocol IO and user callback failures do not escape `open()`, `close()`, the `onEvent` invocation path, the `stream()` iterator, the abort-signal path, or the `onError` callback. These boundaries route work through internal `safe*` wrappers and the generic `attempt()` helper.
|
|
415
476
|
|
|
416
477
|
## Supported sources
|
|
417
478
|
|
|
@@ -441,11 +502,11 @@ const github = new FlumeGitHubSource({ token })
|
|
|
441
502
|
|
|
442
503
|
```bash
|
|
443
504
|
bun install
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
505
|
+
vp pack # build dist/
|
|
506
|
+
vp run typecheck # typecheck
|
|
507
|
+
vp test # tests
|
|
508
|
+
vp lint # lint
|
|
509
|
+
vp fmt # format
|
|
449
510
|
```
|
|
450
511
|
|
|
451
512
|
The library itself is runtime-agnostic. The dev toolchain (build / test / lint) uses Bun + `vite-plus` + `vitest`, but the published `dist/` is plain ESM with TypeScript declarations and runs on Node 22+, Bun, Deno, Cloudflare Workers, or modern browsers. Runtimes without a global `WebSocket` (e.g. fetch-only edge functions) can still use `FlumeGitHubSource`; `FlumeDiscordSource` / `FlumeSlackSource` will return `FlumeStartError` at `open()` time if `deps.WebSocket` is `null`.
|
package/dist/discord.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as FlumeSourceStartContext, s as FlumeDiscordSourceOptions, t as FlumeSource } from "./flume-source.js";
|
|
2
2
|
|
|
3
3
|
//#region lib/discord/discord-source.d.ts
|
|
4
4
|
declare class FlumeDiscordSource extends FlumeSource {
|
|
@@ -10,10 +10,24 @@ declare class FlumeDiscordSource extends FlumeSource {
|
|
|
10
10
|
protected connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
11
11
|
protected disconnect(): void;
|
|
12
12
|
private hasWebSocket;
|
|
13
|
+
/**
|
|
14
|
+
* gateway を 1 接続 = 1 インスタンスで作り直す。`session` は前回接続から引き継いだ
|
|
15
|
+
* resume 可能な session (無ければ IDENTIFY)。await 後は `this.gateway` でなく local な
|
|
16
|
+
* `gateway` を参照する (並行する close() が `this.gateway` を null 化しても壊れない)
|
|
17
|
+
*/
|
|
13
18
|
private connectInternal;
|
|
14
19
|
private dispatch;
|
|
15
20
|
private safeExtractMeta;
|
|
21
|
+
/**
|
|
22
|
+
* status は発火元 gateway に束縛して受ける。交換済み (stale) な gateway からの通知は無視し、
|
|
23
|
+
* 現行 gateway の状態を誤って上書きしない
|
|
24
|
+
*/
|
|
16
25
|
private handleGatewayStatus;
|
|
26
|
+
/**
|
|
27
|
+
* resume 可能な session はこの時点で捕捉して次の gateway へ引き継ぐ (gateway インスタンスは
|
|
28
|
+
* 接続ごとに破棄されるため)。resume できない = IDENTIFY し直す再接続には identify rate limit
|
|
29
|
+
* (1 回 / 5 秒) を守る下限 delay を敷く
|
|
30
|
+
*/
|
|
17
31
|
private scheduleReconnect;
|
|
18
32
|
}
|
|
19
33
|
//#endregion
|