@krischoichoi/channel-lark 0.6.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 +114 -0
- package/lib/adapter.d.ts +128 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +347 -0
- package/lib/adapter.js.map +1 -0
- package/lib/auth/device-authorization.d.ts +12 -0
- package/lib/auth/device-authorization.d.ts.map +1 -0
- package/lib/auth/device-authorization.js +169 -0
- package/lib/auth/device-authorization.js.map +1 -0
- package/lib/card.d.ts +124 -0
- package/lib/card.d.ts.map +1 -0
- package/lib/card.js +347 -0
- package/lib/card.js.map +1 -0
- package/lib/config.d.ts +103 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +60 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +74 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +190 -0
- package/lib/definition.js.map +1 -0
- package/lib/inbound.d.ts +41 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +102 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +104 -0
- package/lib/index.js.map +1 -0
- package/lib/lark-sdk-upstream.d.ts +128 -0
- package/lib/lark-sdk-upstream.d.ts.map +1 -0
- package/lib/lark-sdk-upstream.js +339 -0
- package/lib/lark-sdk-upstream.js.map +1 -0
- package/lib/manifest.d.ts +23 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +36 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +47 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +223 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +55 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +172 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/openapi-outbound.d.ts +216 -0
- package/lib/openapi-outbound.d.ts.map +1 -0
- package/lib/openapi-outbound.js +384 -0
- package/lib/openapi-outbound.js.map +1 -0
- package/lib/outbound.d.ts +21 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +93 -0
- package/lib/outbound.js.map +1 -0
- package/lib/upstream/media-port.d.ts +154 -0
- package/lib/upstream/media-port.d.ts.map +1 -0
- package/lib/upstream/media-port.js +168 -0
- package/lib/upstream/media-port.js.map +1 -0
- package/lib/upstream.d.ts +119 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +2 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @krischoichoi/channel-lark
|
|
2
|
+
|
|
3
|
+
Lark / Feishu / 飞书 channel adapter for DeepSeek Harness.
|
|
4
|
+
|
|
5
|
+
Maps Lark to the stable Channel Contract through the official
|
|
6
|
+
`@larksuiteoapi/node-sdk` **only**:
|
|
7
|
+
|
|
8
|
+
- **Inbound** — `WSClient` + `EventDispatcher` (WebSocket long-connection):
|
|
9
|
+
`im.message.receive_v1` and `card.action.trigger`.
|
|
10
|
+
- **Outbound** — the official OpenAPI client: `im.v1.message.create` / `patch`,
|
|
11
|
+
image/file uploads, and CardKit 2.0 card entities with **native streaming**
|
|
12
|
+
(`cardkit.v1.card.create` → `cardElement.content` → `card.settings`).
|
|
13
|
+
|
|
14
|
+
There is no self-hosted gateway and no legacy transport. Interactive cards
|
|
15
|
+
use **Card JSON 2.0** only (buttons are direct `body.elements` with
|
|
16
|
+
`behaviors[].value`; no `tag: "action"` container and no `lark_md`).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @krischoichoi/channel-lark
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install the whole bundle:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @deepseek-ai/dsh plugin --profile web add -w @krischoichoi/dsh-channels@latest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- id: channels-lark
|
|
34
|
+
name: '@krischoichoi/channel-lark'
|
|
35
|
+
inject: [channels, credentials, channelControl]
|
|
36
|
+
config:
|
|
37
|
+
enabled: true
|
|
38
|
+
accountId: main
|
|
39
|
+
upstream:
|
|
40
|
+
mode: sdk # fixed — the official SDK is the only upstream driver
|
|
41
|
+
appId: "cli_xxx" # AppId(非机密)
|
|
42
|
+
domain: feishu # feishu(国内)| lark(海外)
|
|
43
|
+
# appSecretRef 默认 DSH_CHANNEL_LARK_MAIN_APP_SECRET
|
|
44
|
+
# 真实 AppSecret 只存 ctx.credentials
|
|
45
|
+
card:
|
|
46
|
+
createOnFirstDelta: true
|
|
47
|
+
typingIndicator: true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The AppSecret is resolved through `ctx.credentials` at startup and injected as
|
|
51
|
+
`deps.appSecret`; it is never written to profile config.
|
|
52
|
+
|
|
53
|
+
> **Config governance**: only `upstream.mode: sdk` and the credential reference
|
|
54
|
+
> `upstream.appSecretRef` are supported. Plaintext `upstream.appSecret` and the
|
|
55
|
+
> former gateway settings are not part of the configuration contract.
|
|
56
|
+
|
|
57
|
+
## Streaming
|
|
58
|
+
|
|
59
|
+
Lark uses `edit` streaming over **CardKit 2.0 native streaming** (the official
|
|
60
|
+
"打字机" typewriter flow — never `im.v1.message.patch` pseudo-streaming):
|
|
61
|
+
|
|
62
|
+
1. `cardkit.v1.card.create` creates a Card JSON 2.0 entity with
|
|
63
|
+
`streaming_mode: true` and a stable `element_id`;
|
|
64
|
+
2. `im.v1.message.create` sends the card reference
|
|
65
|
+
(`{ type: "card", data: { card_id } }`);
|
|
66
|
+
3. every streamed delta replaces the markdown element's full content via
|
|
67
|
+
`cardkit.v1.cardElement.content` with a monotonically increasing `sequence`
|
|
68
|
+
and a stable `uuid`;
|
|
69
|
+
4. `finish()` / `fail()` close the stream by setting `streaming_mode: false`
|
|
70
|
+
(and the final summary) through `cardkit.v1.card.settings`.
|
|
71
|
+
|
|
72
|
+
When a single markdown element approaches the platform's ~30,000-char cap the
|
|
73
|
+
handle rolls over into a fresh card (`chunkIds` records every sent card).
|
|
74
|
+
|
|
75
|
+
## Capabilities
|
|
76
|
+
|
|
77
|
+
| Capability | Value |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| text / image / file / audio | ✅ |
|
|
80
|
+
| markdown / cards / reactions / threads | ✅ |
|
|
81
|
+
| interactive buttons | ✅ (`card.action.trigger`, Card JSON 2.0) |
|
|
82
|
+
| video | ❌ |
|
|
83
|
+
| streaming | `edit` (CardKit 2.0 native streaming) |
|
|
84
|
+
|
|
85
|
+
## Upstream
|
|
86
|
+
|
|
87
|
+
| Field | Value |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| SDK | `@larksuiteoapi/node-sdk` |
|
|
90
|
+
| Tested version | `1.73.1` |
|
|
91
|
+
| Status | `tested` (**offline-tested**: contract + fixture + SDK-mode suites) |
|
|
92
|
+
|
|
93
|
+
> Live gate status: **LIVE-REQUIRED**. `status: tested` reflects the offline
|
|
94
|
+
> Channel Contract + fixture + official-SDK offline suites only. Real-app
|
|
95
|
+
> permission / event subscription / message & card send behavior must still be
|
|
96
|
+
> verified live (see §8 of the alignment plan) before the adapter may be
|
|
97
|
+
> claimed live-tested.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pnpm --filter @krischoichoi/channel-lark build
|
|
103
|
+
pnpm --filter @krischoichoi/channel-lark typecheck
|
|
104
|
+
pnpm --filter @krischoichoi/channel-lark test
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Related
|
|
108
|
+
|
|
109
|
+
- [Repository root](../../README.md)
|
|
110
|
+
- [Adapter authoring guide](../../docs/adapter-authoring.md)
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
[MIT](../../LICENSE)
|
package/lib/adapter.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lark channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* Maps the Lark/Feishu platform (through the OFFICIAL `@larksuiteoapi/node-sdk`
|
|
5
|
+
* driver stack) to the Channel Contract. All network/lifecycle resources live
|
|
6
|
+
* behind the upstream driver and are aborted via the adapter context signal.
|
|
7
|
+
* The adapter never touches Harness Agent APIs.
|
|
8
|
+
*
|
|
9
|
+
* The upstream is ALWAYS the official SDK:
|
|
10
|
+
* - inbound → `LarkSdkUpstream`: WSClient + EventDispatcher
|
|
11
|
+
* (`im.message.receive_v1` + `card.action.trigger`), outbound delegated to
|
|
12
|
+
* the official OpenAPI client (`LarkOpenApiOutbound`). The WS client comes
|
|
13
|
+
* from `deps.sdkClient` / `deps.sdkClientFactory`, the OpenAPI client from
|
|
14
|
+
* `deps.openApiClient` / `deps.openApiClientFactory`, each defaulting to a
|
|
15
|
+
* real client built from the RESOLVED `deps.appId` / `deps.appSecret`,
|
|
16
|
+
* resolved via ctx.credentials (the adapter never reads secrets from config).
|
|
17
|
+
* Missing credentials fail start loudly, not construction.
|
|
18
|
+
*
|
|
19
|
+
* There is no transport injection and no gateway: offline tests inject fake
|
|
20
|
+
* WS + OpenAPI clients instead.
|
|
21
|
+
*
|
|
22
|
+
* Auth is connection-state driven: the upstream driver owns the platform
|
|
23
|
+
* credentials, so the adapter derives its auth state from the connection
|
|
24
|
+
* (connected → authenticated).
|
|
25
|
+
*/
|
|
26
|
+
import type { ChannelAdapter, ChannelAdapterContext, ChannelCapabilities, ChannelHealth, ChannelTarget, CreateReplyOptions, OutboundMessage, ReplyHandle, SendResult } from '@krischoichoi/channel-core';
|
|
27
|
+
import { Domain } from '@larksuiteoapi/node-sdk';
|
|
28
|
+
import type { LarkConfig } from './config.js';
|
|
29
|
+
import { type LarkSdkClient } from './lark-sdk-upstream.js';
|
|
30
|
+
import { type LarkOpenApiClient } from './openapi-outbound.js';
|
|
31
|
+
import { type LarkMediaPort } from './upstream/media-port.js';
|
|
32
|
+
import { type LarkManifest } from './manifest.js';
|
|
33
|
+
export interface LarkAdapterDeps {
|
|
34
|
+
/**
|
|
35
|
+
* Resolved Lark AppId (a plain config string resolved by the plugin and
|
|
36
|
+
* handed in for the default client builders; never a secret).
|
|
37
|
+
*/
|
|
38
|
+
appId?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Resolved Lark AppSecret (resolved via `ctx.credentials` by the plugin;
|
|
41
|
+
* never present in profile config / logs).
|
|
42
|
+
*/
|
|
43
|
+
appSecret?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Pre-built WS long-connection client (offline tests). Overrides
|
|
46
|
+
* `sdkClientFactory`; when neither is given a real `WSClient` is built from
|
|
47
|
+
* `appId`/`appSecret` at start time.
|
|
48
|
+
*/
|
|
49
|
+
sdkClient?: LarkSdkClient;
|
|
50
|
+
/** Lazy WS client factory; overrides the default WSClient. */
|
|
51
|
+
sdkClientFactory?: (config: LarkConfig) => LarkSdkClient;
|
|
52
|
+
/**
|
|
53
|
+
* Pre-built official OpenAPI client (offline tests). Overrides
|
|
54
|
+
* `openApiClientFactory`; when neither is given a real `Client` is built
|
|
55
|
+
* from `appId`/`appSecret` at start time.
|
|
56
|
+
*/
|
|
57
|
+
openApiClient?: LarkOpenApiClient;
|
|
58
|
+
/** Lazy OpenAPI client factory. */
|
|
59
|
+
openApiClientFactory?: (config: LarkConfig) => LarkOpenApiClient;
|
|
60
|
+
/**
|
|
61
|
+
* Injectable media port for inbound media hydration. When absent, the
|
|
62
|
+
* adapter builds a default LarkOpenApiMediaPort from the same resolved
|
|
63
|
+
* OpenAPI client used for outbound.
|
|
64
|
+
*/
|
|
65
|
+
mediaPort?: LarkMediaPort;
|
|
66
|
+
/** Injectable clock (tests). */
|
|
67
|
+
now?: () => number;
|
|
68
|
+
}
|
|
69
|
+
export declare class LarkAdapter implements ChannelAdapter {
|
|
70
|
+
private readonly config;
|
|
71
|
+
readonly id = "lark";
|
|
72
|
+
/** Upstream compatibility manifest (read structurally by `channels doctor`). */
|
|
73
|
+
readonly manifest: LarkManifest;
|
|
74
|
+
readonly capabilities: ChannelCapabilities;
|
|
75
|
+
private ctx?;
|
|
76
|
+
/** Built in `start()` (driver selection needs the resolved deps/credentials). */
|
|
77
|
+
private upstream;
|
|
78
|
+
private readonly deps;
|
|
79
|
+
private inbound;
|
|
80
|
+
private outbound;
|
|
81
|
+
/** Media port used for inbound media hydration (built in start). */
|
|
82
|
+
private mediaPort?;
|
|
83
|
+
private started;
|
|
84
|
+
private stopped;
|
|
85
|
+
private receiveLoop?;
|
|
86
|
+
private connected;
|
|
87
|
+
/** Connection-state-driven auth; the upstream driver owns credentials. */
|
|
88
|
+
private authState;
|
|
89
|
+
private readonly now;
|
|
90
|
+
/** Internal stop signal merged with the context signal for prompt teardown. */
|
|
91
|
+
private stopController?;
|
|
92
|
+
private receiveSignal?;
|
|
93
|
+
constructor(config: LarkConfig, deps?: LarkAdapterDeps);
|
|
94
|
+
start(ctx: ChannelAdapterContext): Promise<void>;
|
|
95
|
+
stop(): Promise<void>;
|
|
96
|
+
send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
|
|
97
|
+
edit(target: ChannelTarget, messageId: string, message: OutboundMessage): Promise<SendResult>;
|
|
98
|
+
startTypingForTarget(target: ChannelTarget): Promise<void>;
|
|
99
|
+
stopTypingForTarget(target: ChannelTarget): Promise<void>;
|
|
100
|
+
createReply(target: ChannelTarget, _options?: CreateReplyOptions): Promise<ReplyHandle>;
|
|
101
|
+
getHealth(): Promise<ChannelHealth>;
|
|
102
|
+
/** WS long-connection receive loop with exponential backoff on failure. */
|
|
103
|
+
startReceiveLoop(): void;
|
|
104
|
+
/** Build the official-SDK upstream driver. */
|
|
105
|
+
private buildUpstream;
|
|
106
|
+
/** Select the official OpenAPI outbound driver. */
|
|
107
|
+
private resolveOpenApiOutbound;
|
|
108
|
+
/**
|
|
109
|
+
* Select the media port for inbound media hydration. An explicit dep wins;
|
|
110
|
+
* otherwise the adapter builds a default `LarkOpenApiMediaPort` from the
|
|
111
|
+
* same resolved OpenAPI client used for outbound.
|
|
112
|
+
*/
|
|
113
|
+
private resolveMediaPort;
|
|
114
|
+
private resolveSdkClient;
|
|
115
|
+
private runReceiveLoop;
|
|
116
|
+
/** Flip connection/auth state once the upstream proves reachable. */
|
|
117
|
+
private markConnected;
|
|
118
|
+
private aborted;
|
|
119
|
+
private setAuth;
|
|
120
|
+
private emitAuth;
|
|
121
|
+
private emitConnection;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a config domain string to the SDK's `Domain` enum, preserving a
|
|
125
|
+
* custom base domain verbatim (the SDK accepts `Domain | string`).
|
|
126
|
+
*/
|
|
127
|
+
export declare function resolveDomain(value: string): Domain | string;
|
|
128
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/lib/adapter.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { ChannelError } from '@krischoichoi/channel-core';
|
|
2
|
+
import { Client, Domain, WSClient } from '@larksuiteoapi/node-sdk';
|
|
3
|
+
import { LarkSdkUpstream, } from './lark-sdk-upstream.js';
|
|
4
|
+
import { LarkOpenApiOutbound } from './openapi-outbound.js';
|
|
5
|
+
import { LarkOpenApiMediaPort } from './upstream/media-port.js';
|
|
6
|
+
import { InboundProcessor } from './inbound.js';
|
|
7
|
+
import { OutboundSender } from './outbound.js';
|
|
8
|
+
import { LarkCardReply } from './card.js';
|
|
9
|
+
import { manifest as larkManifest } from './manifest.js';
|
|
10
|
+
export class LarkAdapter {
|
|
11
|
+
config;
|
|
12
|
+
id = 'lark';
|
|
13
|
+
/** Upstream compatibility manifest (read structurally by `channels doctor`). */
|
|
14
|
+
manifest = larkManifest;
|
|
15
|
+
capabilities = {
|
|
16
|
+
text: true,
|
|
17
|
+
image: true,
|
|
18
|
+
file: true,
|
|
19
|
+
audio: true,
|
|
20
|
+
video: false,
|
|
21
|
+
markdown: true,
|
|
22
|
+
cards: true,
|
|
23
|
+
interactiveActions: true,
|
|
24
|
+
reactions: true,
|
|
25
|
+
threads: true,
|
|
26
|
+
streaming: 'edit',
|
|
27
|
+
// Directional media precision. Inbound: image/file/audio/video are all
|
|
28
|
+
// hydrated to real bytes before emit — the official `message.resource`
|
|
29
|
+
// API documents audio/video downloads, so every binary kind the mapper
|
|
30
|
+
// produces routes its file_key through `messageResource.get` into
|
|
31
|
+
// localData. Outbound: image (`sendMedia` → im.image.create) and file
|
|
32
|
+
// (`sendFile` → im.file.create) upload real bytes; there is no audio/video
|
|
33
|
+
// send path — audio/video outbound parts fall back to `[audio]`/`[video]`
|
|
34
|
+
// text placeholders, so outbound audio/video are 'unsupported'.
|
|
35
|
+
media: {
|
|
36
|
+
inbound: {
|
|
37
|
+
image: 'bytes',
|
|
38
|
+
file: 'bytes',
|
|
39
|
+
audio: 'bytes',
|
|
40
|
+
video: 'bytes',
|
|
41
|
+
},
|
|
42
|
+
outbound: {
|
|
43
|
+
image: 'bytes',
|
|
44
|
+
file: 'bytes',
|
|
45
|
+
audio: 'unsupported',
|
|
46
|
+
video: 'unsupported',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
ctx;
|
|
51
|
+
/** Built in `start()` (driver selection needs the resolved deps/credentials). */
|
|
52
|
+
upstream;
|
|
53
|
+
deps;
|
|
54
|
+
inbound;
|
|
55
|
+
outbound;
|
|
56
|
+
/** Media port used for inbound media hydration (built in start). */
|
|
57
|
+
mediaPort;
|
|
58
|
+
started = false;
|
|
59
|
+
stopped = false;
|
|
60
|
+
receiveLoop;
|
|
61
|
+
connected = false;
|
|
62
|
+
/** Connection-state-driven auth; the upstream driver owns credentials. */
|
|
63
|
+
authState = 'unknown';
|
|
64
|
+
now;
|
|
65
|
+
/** Internal stop signal merged with the context signal for prompt teardown. */
|
|
66
|
+
stopController;
|
|
67
|
+
receiveSignal;
|
|
68
|
+
constructor(config, deps = {}) {
|
|
69
|
+
this.config = config;
|
|
70
|
+
this.now = deps.now ?? Date.now;
|
|
71
|
+
this.deps = deps;
|
|
72
|
+
}
|
|
73
|
+
async start(ctx) {
|
|
74
|
+
if (this.started)
|
|
75
|
+
return;
|
|
76
|
+
this.ctx = ctx;
|
|
77
|
+
this.stopped = false;
|
|
78
|
+
this.buildUpstream();
|
|
79
|
+
this.stopController = new AbortController();
|
|
80
|
+
this.receiveSignal = AbortSignal.any([ctx.signal, this.stopController.signal]);
|
|
81
|
+
this.mediaPort = this.resolveMediaPort();
|
|
82
|
+
this.inbound = new InboundProcessor({
|
|
83
|
+
ctx,
|
|
84
|
+
meta: { channel: this.id, accountId: this.config.accountId },
|
|
85
|
+
dedupEnabled: this.config.dedup.enabled,
|
|
86
|
+
dedupWindowMs: this.config.dedup.windowMs,
|
|
87
|
+
now: this.now,
|
|
88
|
+
mediaPort: this.mediaPort,
|
|
89
|
+
});
|
|
90
|
+
this.outbound = new OutboundSender(this.upstream, ctx.logger);
|
|
91
|
+
this.connected = false;
|
|
92
|
+
this.authState = 'unknown';
|
|
93
|
+
this.emitAuth(this.authState);
|
|
94
|
+
this.emitConnection('connecting');
|
|
95
|
+
this.startReceiveLoop();
|
|
96
|
+
this.started = true;
|
|
97
|
+
}
|
|
98
|
+
async stop() {
|
|
99
|
+
if (!this.started || this.stopped)
|
|
100
|
+
return;
|
|
101
|
+
this.stopped = true;
|
|
102
|
+
this.started = false;
|
|
103
|
+
this.connected = false;
|
|
104
|
+
// The owning fiber aborts the context signal first; the loop also exits
|
|
105
|
+
// on our internal stop signal so stop() never depends on an external
|
|
106
|
+
// abort (contract tests stop without disposing the context).
|
|
107
|
+
this.stopController?.abort();
|
|
108
|
+
const loop = this.receiveLoop;
|
|
109
|
+
this.receiveLoop = undefined;
|
|
110
|
+
if (loop)
|
|
111
|
+
await loop.catch(() => undefined);
|
|
112
|
+
this.emitConnection('closed');
|
|
113
|
+
}
|
|
114
|
+
async send(target, message) {
|
|
115
|
+
if (!this.started || !this.outbound) {
|
|
116
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'lark adapter is not started');
|
|
117
|
+
}
|
|
118
|
+
return this.outbound.send(target, message);
|
|
119
|
+
}
|
|
120
|
+
async edit(target, messageId, message) {
|
|
121
|
+
if (!this.started || !this.outbound) {
|
|
122
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'lark adapter is not started');
|
|
123
|
+
}
|
|
124
|
+
return this.outbound.edit(target, messageId, message);
|
|
125
|
+
}
|
|
126
|
+
async startTypingForTarget(target) {
|
|
127
|
+
if (this.config.card.typingIndicator === false || !target.replyToMessageId)
|
|
128
|
+
return;
|
|
129
|
+
await this.upstream.startTyping?.(String(target.replyToMessageId));
|
|
130
|
+
}
|
|
131
|
+
async stopTypingForTarget(target) {
|
|
132
|
+
if (this.config.card.typingIndicator === false || !target.replyToMessageId)
|
|
133
|
+
return;
|
|
134
|
+
await this.upstream.stopTyping?.(String(target.replyToMessageId));
|
|
135
|
+
}
|
|
136
|
+
async createReply(target, _options) {
|
|
137
|
+
if (!this.started || !this.ctx) {
|
|
138
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'lark adapter is not started');
|
|
139
|
+
}
|
|
140
|
+
return new LarkCardReply({
|
|
141
|
+
upstream: this.upstream,
|
|
142
|
+
target,
|
|
143
|
+
logger: this.ctx.logger,
|
|
144
|
+
createOnFirstDelta: this.config.card.createOnFirstDelta,
|
|
145
|
+
now: this.now,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async getHealth() {
|
|
149
|
+
if (!this.started) {
|
|
150
|
+
return { status: 'down', detail: 'lark adapter is not started', authenticated: false };
|
|
151
|
+
}
|
|
152
|
+
if (this.connected) {
|
|
153
|
+
return { status: 'ok', detail: 'connected', connection: 'connected', authenticated: true };
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
status: 'degraded',
|
|
157
|
+
detail: 'receive loop down',
|
|
158
|
+
connection: 'disconnected',
|
|
159
|
+
authenticated: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** WS long-connection receive loop with exponential backoff on failure. */
|
|
163
|
+
startReceiveLoop() {
|
|
164
|
+
if (this.receiveLoop)
|
|
165
|
+
return;
|
|
166
|
+
this.receiveLoop = this.runReceiveLoop();
|
|
167
|
+
}
|
|
168
|
+
/** Build the official-SDK upstream driver. */
|
|
169
|
+
buildUpstream() {
|
|
170
|
+
const outbound = this.resolveOpenApiOutbound();
|
|
171
|
+
const options = {
|
|
172
|
+
client: this.resolveSdkClient(),
|
|
173
|
+
outbound,
|
|
174
|
+
resolveChatType: (conversationId) => outbound.getChatType?.(conversationId) ?? Promise.resolve(undefined),
|
|
175
|
+
onConnected: () => this.markConnected(),
|
|
176
|
+
};
|
|
177
|
+
this.upstream = new LarkSdkUpstream(options);
|
|
178
|
+
}
|
|
179
|
+
/** Select the official OpenAPI outbound driver. */
|
|
180
|
+
resolveOpenApiOutbound() {
|
|
181
|
+
if (this.deps.openApiClient) {
|
|
182
|
+
return new LarkOpenApiOutbound({ client: this.deps.openApiClient });
|
|
183
|
+
}
|
|
184
|
+
if (this.deps.openApiClientFactory) {
|
|
185
|
+
return new LarkOpenApiOutbound({ client: this.deps.openApiClientFactory(this.config) });
|
|
186
|
+
}
|
|
187
|
+
return new LarkOpenApiOutbound({
|
|
188
|
+
client: createDefaultOpenApiClient(this.deps.appId, this.deps.appSecret, this.config),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Select the media port for inbound media hydration. An explicit dep wins;
|
|
193
|
+
* otherwise the adapter builds a default `LarkOpenApiMediaPort` from the
|
|
194
|
+
* same resolved OpenAPI client used for outbound.
|
|
195
|
+
*/
|
|
196
|
+
resolveMediaPort() {
|
|
197
|
+
if (this.deps.mediaPort)
|
|
198
|
+
return this.deps.mediaPort;
|
|
199
|
+
// The resolved client is the real SDK `Client`, which structurally
|
|
200
|
+
// satisfies the wider `LarkMediaClient` surface (message resource + image
|
|
201
|
+
// + file). When a caller injects a fake typed only as `LarkOpenApiClient`,
|
|
202
|
+
// hydration still safely degrades: a missing messageResource surfaces as
|
|
203
|
+
// a runtime failure that the hydrator marks.
|
|
204
|
+
const client = this.deps.openApiClient
|
|
205
|
+
?? (this.deps.openApiClientFactory && this.deps.openApiClientFactory(this.config))
|
|
206
|
+
?? createDefaultOpenApiClient(this.deps.appId, this.deps.appSecret, this.config);
|
|
207
|
+
return new LarkOpenApiMediaPort({ client: client });
|
|
208
|
+
}
|
|
209
|
+
resolveSdkClient() {
|
|
210
|
+
if (this.deps.sdkClient)
|
|
211
|
+
return this.deps.sdkClient;
|
|
212
|
+
if (this.deps.sdkClientFactory)
|
|
213
|
+
return this.deps.sdkClientFactory(this.config);
|
|
214
|
+
return createDefaultWSClient(this.deps.appId, this.deps.appSecret, this.config);
|
|
215
|
+
}
|
|
216
|
+
async runReceiveLoop() {
|
|
217
|
+
let attempt = 0;
|
|
218
|
+
while (!this.stopped && !this.aborted()) {
|
|
219
|
+
try {
|
|
220
|
+
await this.upstream.receive(this.receiveSignal, (raw) => {
|
|
221
|
+
void this.inbound.handle(raw).catch((error) => {
|
|
222
|
+
this.ctx.logger.error('[channel-lark] inbound handling failed', error);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
attempt = 0;
|
|
226
|
+
// A completed receive cycle proves the upstream is reachable; auth
|
|
227
|
+
// follows the connection state (the driver owns credentials). The WS
|
|
228
|
+
// driver reports connectivity via onConnected.
|
|
229
|
+
this.markConnected();
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (this.stopped || this.aborted())
|
|
233
|
+
break;
|
|
234
|
+
attempt += 1;
|
|
235
|
+
this.connected = false;
|
|
236
|
+
this.emitConnection('reconnecting');
|
|
237
|
+
if (this.config.reconnect.enabled && attempt > this.config.reconnect.maxRetries) {
|
|
238
|
+
this.ctx.logger.warn('[channel-lark] reconnect budget exhausted');
|
|
239
|
+
this.setAuth('failed');
|
|
240
|
+
this.emitConnection('disconnected');
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
const delay = Math.min(this.config.reconnect.baseDelayMs * 2 ** Math.min(attempt - 1, 8), this.config.reconnect.maxDelayMs);
|
|
244
|
+
this.ctx.logger.warn(`[channel-lark] receive loop error; retry in ${delay}ms`, error);
|
|
245
|
+
await sleep(delay, this.receiveSignal);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
this.connected = false;
|
|
249
|
+
if (!this.stopped && !this.aborted()) {
|
|
250
|
+
this.emitConnection('disconnected');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Flip connection/auth state once the upstream proves reachable. */
|
|
254
|
+
markConnected() {
|
|
255
|
+
if (this.stopped)
|
|
256
|
+
return;
|
|
257
|
+
if (this.connected)
|
|
258
|
+
return;
|
|
259
|
+
this.connected = true;
|
|
260
|
+
this.setAuth('authenticated');
|
|
261
|
+
this.emitConnection('connected');
|
|
262
|
+
}
|
|
263
|
+
aborted() {
|
|
264
|
+
return this.receiveSignal?.aborted ?? true;
|
|
265
|
+
}
|
|
266
|
+
setAuth(state) {
|
|
267
|
+
if (this.authState === state)
|
|
268
|
+
return;
|
|
269
|
+
this.authState = state;
|
|
270
|
+
this.emitAuth(state);
|
|
271
|
+
}
|
|
272
|
+
emitAuth(state) {
|
|
273
|
+
if (!this.ctx)
|
|
274
|
+
return;
|
|
275
|
+
void this.ctx
|
|
276
|
+
.emit({
|
|
277
|
+
type: 'auth.changed',
|
|
278
|
+
channel: this.id,
|
|
279
|
+
accountId: this.config.accountId,
|
|
280
|
+
state,
|
|
281
|
+
})
|
|
282
|
+
.catch(() => undefined);
|
|
283
|
+
}
|
|
284
|
+
emitConnection(state) {
|
|
285
|
+
if (!this.ctx)
|
|
286
|
+
return;
|
|
287
|
+
void this.ctx
|
|
288
|
+
.emit({
|
|
289
|
+
type: 'connection.changed',
|
|
290
|
+
channel: this.id,
|
|
291
|
+
accountId: this.config.accountId,
|
|
292
|
+
state,
|
|
293
|
+
})
|
|
294
|
+
.catch(() => undefined);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/** Build the real SDK WS client from resolved credentials; never logs them. */
|
|
298
|
+
function createDefaultWSClient(appId, appSecret, config) {
|
|
299
|
+
if (!appId || !appSecret) {
|
|
300
|
+
throw new ChannelError('CHANNEL_ERROR', 'lark upstream requires resolved appId and appSecret credentials');
|
|
301
|
+
}
|
|
302
|
+
return new WSClient({
|
|
303
|
+
appId,
|
|
304
|
+
appSecret,
|
|
305
|
+
domain: resolveDomain(config.upstream.domain ?? 'feishu'),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/** Build the real SDK OpenAPI client from resolved credentials; never logs them. */
|
|
309
|
+
function createDefaultOpenApiClient(appId, appSecret, config) {
|
|
310
|
+
if (!appId || !appSecret) {
|
|
311
|
+
throw new ChannelError('CHANNEL_ERROR', 'lark upstream requires resolved appId and appSecret credentials');
|
|
312
|
+
}
|
|
313
|
+
return new Client({
|
|
314
|
+
appId,
|
|
315
|
+
appSecret,
|
|
316
|
+
domain: resolveDomain(config.upstream.domain ?? 'feishu'),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Resolve a config domain string to the SDK's `Domain` enum, preserving a
|
|
321
|
+
* custom base domain verbatim (the SDK accepts `Domain | string`).
|
|
322
|
+
*/
|
|
323
|
+
export function resolveDomain(value) {
|
|
324
|
+
if (value === 'feishu')
|
|
325
|
+
return Domain.Feishu;
|
|
326
|
+
if (value === 'lark')
|
|
327
|
+
return Domain.Lark;
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
function sleep(ms, signal) {
|
|
331
|
+
return new Promise((resolve) => {
|
|
332
|
+
if (signal?.aborted) {
|
|
333
|
+
resolve();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const timer = setTimeout(() => {
|
|
337
|
+
signal?.removeEventListener('abort', onAbort);
|
|
338
|
+
resolve();
|
|
339
|
+
}, ms);
|
|
340
|
+
const onAbort = () => {
|
|
341
|
+
clearTimeout(timer);
|
|
342
|
+
resolve();
|
|
343
|
+
};
|
|
344
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthProviderSession, type PublicAuthStatus } from '@krischoichoi/channel-control';
|
|
2
|
+
export interface LarkDeviceAuthorizationOptions {
|
|
3
|
+
appId: string;
|
|
4
|
+
appSecret: string;
|
|
5
|
+
domain?: string;
|
|
6
|
+
fetcher?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
/** Begin Feishu/Lark device authorization. Secrets remain in opaque host state. */
|
|
9
|
+
export declare function beginLarkDeviceAuthorization(options: LarkDeviceAuthorizationOptions): Promise<AuthProviderSession>;
|
|
10
|
+
/** Poll a device authorization session without exposing provider tokens. */
|
|
11
|
+
export declare function pollLarkDeviceAuthorization(session: AuthProviderSession, options?: Pick<LarkDeviceAuthorizationOptions, 'fetcher'>): Promise<PublicAuthStatus>;
|
|
12
|
+
//# sourceMappingURL=device-authorization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-authorization.d.ts","sourceRoot":"","sources":["../../src/auth/device-authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AAsCvC,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAsDD,mFAAmF;AACnF,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,mBAAmB,CAAC,CAoC9B;AAED,4EAA4E;AAC5E,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,IAAI,CAAC,8BAA8B,EAAE,SAAS,CAAM,GAC5D,OAAO,CAAC,gBAAgB,CAAC,CA+C3B"}
|