@zhin.js/adapter-lark 1.1.18 → 1.1.19
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/CHANGELOG.md +38 -0
- package/README.md +8 -7
- package/adapters/lark/index.js +3 -17
- package/adapters/lark/index.ts +2 -17
- package/commands/lark/endpoint/definition.js +0 -2
- package/commands/lark/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +19 -0
- package/lib/adapter.js +29 -0
- package/lib/client.d.ts +95 -11
- package/lib/client.js +657 -2
- package/lib/console.d.ts +1 -1
- package/lib/console.js +2 -5
- package/lib/endpoint.d.ts +11 -102
- package/lib/endpoint.js +26 -671
- package/lib/index.d.ts +4 -2
- package/lib/index.js +3 -2
- package/lib/long-connection.d.ts +1 -1
- package/lib/long-connection.js +1 -1
- package/lib/media-upload.d.ts +5 -1
- package/lib/media-upload.js +6 -6
- package/lib/protocol.d.ts +1 -1
- package/lib/protocol.js +1 -1
- package/lib/sdk-http.d.ts +4 -0
- package/lib/sdk-http.js +88 -0
- package/lib/webhook.d.ts +1 -1
- package/lib/webhook.js +1 -1
- package/package.json +18 -18
- package/plugin.js +0 -3
- package/src/adapter.ts +30 -0
- package/src/client.ts +726 -10
- package/src/console.ts +2 -5
- package/src/endpoint.ts +29 -749
- package/src/index.ts +4 -3
- package/src/long-connection.ts +2 -2
- package/src/media-upload.ts +10 -5
- package/src/protocol.ts +2 -2
- package/src/sdk-http.ts +65 -0
- package/src/webhook.ts +2 -2
- package/lib/lark-runtime-state.d.ts +0 -1
- package/lib/lark-runtime-state.js +0 -6
- package/src/lark-runtime-state.ts +0 -7
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ export {
|
|
|
20
20
|
type ResolvedLarkConfig,
|
|
21
21
|
} from './protocol.js';
|
|
22
22
|
|
|
23
|
-
export {
|
|
23
|
+
export { type LarkClientEventMap } from './client.js';
|
|
24
24
|
|
|
25
25
|
export {
|
|
26
26
|
checkLarkPlatformPermit,
|
|
@@ -30,9 +30,7 @@ export {
|
|
|
30
30
|
} from './platform-permit.js';
|
|
31
31
|
|
|
32
32
|
export {
|
|
33
|
-
LarkClient,
|
|
34
33
|
LarkEndpoint,
|
|
35
|
-
type LarkClientApi,
|
|
36
34
|
type LarkEndpointOptions,
|
|
37
35
|
type LarkFetch,
|
|
38
36
|
} from './endpoint.js';
|
|
@@ -56,3 +54,6 @@ export {
|
|
|
56
54
|
type LarkLongConnectionFactory,
|
|
57
55
|
type LarkLongConnectionOptions,
|
|
58
56
|
} from './long-connection.js';
|
|
57
|
+
|
|
58
|
+
export { LarkClient, type LarkClientApi } from './client.js';
|
|
59
|
+
export { LarkAdapter } from './adapter.js';
|
package/src/long-connection.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface LarkLongConnection {
|
|
|
10
10
|
}
|
|
11
11
|
export interface LarkLongConnectionOptions {
|
|
12
12
|
readonly config: ResolvedLarkConfig;
|
|
13
|
-
readonly receive: (message: LarkMessage) => void;
|
|
13
|
+
readonly receive: (message: LarkMessage, nativeEvent?: unknown) => void;
|
|
14
14
|
readonly cardAction?: (event: LarkCardAction) => void;
|
|
15
15
|
readonly disconnected: () => void;
|
|
16
16
|
}
|
|
@@ -50,7 +50,7 @@ export const createLarkLongConnection: LarkLongConnectionFactory = ({ config, re
|
|
|
50
50
|
const dispatcher = new lark.EventDispatcher({}).register({
|
|
51
51
|
'card.action.trigger': (event: LarkCardAction) => { if (!cancelled) cardAction?.(event as LarkCardAction); return {}; },
|
|
52
52
|
'im.message.receive_v1': (event) => {
|
|
53
|
-
if (!cancelled) receive({ ...event.message, sender: event.sender });
|
|
53
|
+
if (!cancelled) receive({ ...event.message, sender: event.sender }, event);
|
|
54
54
|
// No downstream send is awaited: SDK ACK remains within its 3-second window.
|
|
55
55
|
},
|
|
56
56
|
});
|
package/src/media-upload.ts
CHANGED
|
@@ -22,10 +22,15 @@ const MIME_EXT: Record<string, string> = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/** 从 canonical MediaRef 解析二进制:base64 解码 / 本地读盘 / URL 下载。 */
|
|
25
|
+
export interface MediaResolveOptions {
|
|
26
|
+
readonly download?: (url: string, signal?: AbortSignal) => Promise<Buffer>;
|
|
27
|
+
readonly signal?: AbortSignal;
|
|
28
|
+
}
|
|
25
29
|
export async function resolveMediaBinary(
|
|
26
30
|
media: MediaRef,
|
|
27
|
-
download
|
|
31
|
+
{ download = defaultDownload, signal }: MediaResolveOptions = {},
|
|
28
32
|
): Promise<MediaBinary> {
|
|
33
|
+
signal?.throwIfAborted();
|
|
29
34
|
const mimeType = media.mime_type ?? 'image/png';
|
|
30
35
|
const ext = MIME_EXT[mimeType] ?? 'png';
|
|
31
36
|
if (media.kind === 'base64') {
|
|
@@ -36,13 +41,13 @@ export async function resolveMediaBinary(
|
|
|
36
41
|
}
|
|
37
42
|
if (media.kind === 'path') {
|
|
38
43
|
const path = media.value.startsWith('file://') ? media.value.slice('file://'.length) : media.value;
|
|
39
|
-
return { data: await readFile(path), mimeType, fileName: basename(path) };
|
|
44
|
+
return { data: await readFile(path, { signal }), mimeType, fileName: basename(path) };
|
|
40
45
|
}
|
|
41
|
-
return { data: await download(media.value), mimeType, fileName: `image.${ext}` };
|
|
46
|
+
return { data: await download(media.value, signal), mimeType, fileName: `image.${ext}` };
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
async function defaultDownload(url: string): Promise<Buffer> {
|
|
45
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(30_000) });
|
|
49
|
+
async function defaultDownload(url: string, signal?: AbortSignal): Promise<Buffer> {
|
|
50
|
+
const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000) });
|
|
46
51
|
if (!response.ok) throw new Error(`download failed: HTTP ${response.status}`);
|
|
47
52
|
return Buffer.from(await response.arrayBuffer());
|
|
48
53
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -186,10 +186,10 @@ export function larkInboundConversation(endpointKey: string, msg: LarkMessage):
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
export function resolveSender(msg: LarkMessage): string {
|
|
189
|
+
export function resolveSender(msg: LarkMessage): string | undefined {
|
|
190
190
|
return msg.sender?.sender_id?.open_id
|
|
191
191
|
|| msg.sender?.sender_id?.user_id
|
|
192
|
-
||
|
|
192
|
+
|| undefined;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
export function generateMessageId(msg: LarkMessage): string {
|
package/src/sdk-http.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { HttpInstance, HttpRequestOptions } from '@larksuiteoapi/node-sdk';
|
|
2
|
+
import { Readable, PassThrough } from 'node:stream';
|
|
3
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
4
|
+
import type { LarkFetch } from './client.js';
|
|
5
|
+
|
|
6
|
+
/** The installed SDK's documented HttpInstance seam, using the same account-owned transport. */
|
|
7
|
+
export function createLarkSdkHttpInstance(fetch: LarkFetch): HttpInstance {
|
|
8
|
+
const request = async <T = unknown, R = T, D = unknown>(input: HttpRequestOptions<D>): Promise<R> => {
|
|
9
|
+
if (!input.url) throw new TypeError('Lark SDK request URL is required');
|
|
10
|
+
const url = new URL(input.url);
|
|
11
|
+
if (input.params) {
|
|
12
|
+
const serializer = typeof input.paramsSerializer === 'function' ? input.paramsSerializer
|
|
13
|
+
: input.paramsSerializer?.serialize;
|
|
14
|
+
if (serializer) {
|
|
15
|
+
const query = serializer(input.params); if (query) url.search = url.search ? `${url.search}&${query}` : query;
|
|
16
|
+
} else for (const [key, value] of Object.entries(input.params)) {
|
|
17
|
+
if (value === undefined || value === null) continue;
|
|
18
|
+
for (const item of Array.isArray(value) ? value : [value]) url.searchParams.append(key, String(item));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const headers = Object.fromEntries(Object.entries(input.headers ?? {}).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
|
|
22
|
+
let body: string | FormData | Uint8Array | ArrayBuffer | AsyncIterable<Uint8Array> | undefined;
|
|
23
|
+
let streaming = false;
|
|
24
|
+
if (input.data !== undefined) {
|
|
25
|
+
const native = input.data as { getHeaders?: () => Record<string, string>; pipe?: (target: PassThrough) => unknown; destroy?: () => void; [Symbol.asyncIterator]?: unknown };
|
|
26
|
+
if (typeof input.data === 'string' || input.data instanceof FormData || input.data instanceof ArrayBuffer || ArrayBuffer.isView(input.data)) body = input.data as string | FormData | Uint8Array | ArrayBuffer;
|
|
27
|
+
else if (native && (typeof native.pipe === 'function' || typeof native[Symbol.asyncIterator] === 'function')) {
|
|
28
|
+
if (native.getHeaders) Object.assign(headers, native.getHeaders());
|
|
29
|
+
if (typeof native.pipe === 'function') {
|
|
30
|
+
const stream = new PassThrough();
|
|
31
|
+
stream.once('close', () => native.destroy?.()); native.pipe(stream); body = stream;
|
|
32
|
+
} else body = input.data as AsyncIterable<Uint8Array>;
|
|
33
|
+
streaming = true;
|
|
34
|
+
}
|
|
35
|
+
else { body = JSON.stringify(input.data); if (!Object.keys(headers).some(key => key.toLowerCase() === 'content-type')) headers['Content-Type'] = 'application/json'; }
|
|
36
|
+
}
|
|
37
|
+
const response = await fetch(url.toString(), { method: input.method, headers, body, ...(streaming ? { duplex: 'half' as const } : {}),
|
|
38
|
+
...(input.timeout ? { signal: AbortSignal.timeout(input.timeout) } : {}) });
|
|
39
|
+
if (!response.ok) throw new EndpointDeliveryError('http_error', `Lark HTTP ${response.status}`, response.status >= 400 && response.status < 500 && response.status !== 408 ? 'rejected' : 'unknown');
|
|
40
|
+
const native = response as Partial<Response>;
|
|
41
|
+
let data: unknown;
|
|
42
|
+
if (input.responseType === 'stream') {
|
|
43
|
+
if (!native.body) throw new Error('Lark transport returned no response stream');
|
|
44
|
+
data = Readable.fromWeb(native.body as Parameters<typeof Readable.fromWeb>[0]);
|
|
45
|
+
} else if (input.responseType === 'arraybuffer') {
|
|
46
|
+
if (!native.arrayBuffer) throw new Error('Lark transport returned no binary reader');
|
|
47
|
+
data = Buffer.from(await native.arrayBuffer());
|
|
48
|
+
} else if (input.responseType === 'blob') {
|
|
49
|
+
if (!native.blob) throw new Error('Lark transport returned no blob reader');
|
|
50
|
+
data = await native.blob();
|
|
51
|
+
} else if (input.responseType === 'text' || input.responseType === 'document') data = await response.text();
|
|
52
|
+
else data = await response.json();
|
|
53
|
+
return (input.$return_headers ? { data, headers: Object.fromEntries(native.headers?.entries() ?? []) } : data) as R;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
request,
|
|
57
|
+
get: (url, options) => request({ ...options, url, method: 'GET' }),
|
|
58
|
+
delete: (url, options) => request({ ...options, url, method: 'DELETE' }),
|
|
59
|
+
head: (url, options) => request({ ...options, url, method: 'HEAD' }),
|
|
60
|
+
options: (url, options) => request({ ...options, url, method: 'OPTIONS' }),
|
|
61
|
+
post: (url, data, options) => request({ ...options, url, data, method: 'POST' }),
|
|
62
|
+
put: (url, data, options) => request({ ...options, url, data, method: 'PUT' }),
|
|
63
|
+
patch: (url, data, options) => request({ ...options, url, data, method: 'PATCH' }),
|
|
64
|
+
};
|
|
65
|
+
}
|
package/src/webhook.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface LarkWebhookHandler {
|
|
|
21
21
|
readonly config: ResolvedLarkConfig;
|
|
22
22
|
readonly isOpen: boolean;
|
|
23
23
|
admitPlatform(name: string, event: unknown): void;
|
|
24
|
-
admit(msg: LarkMessage): void;
|
|
24
|
+
admit(msg: LarkMessage, nativeEvent?: unknown): void;
|
|
25
25
|
admitCardAction?(event: LarkCardAction): void;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -103,7 +103,7 @@ export async function handleLarkWebhookRequest(
|
|
|
103
103
|
...event.event.message,
|
|
104
104
|
sender: event.event.message.sender ?? event.event.sender,
|
|
105
105
|
};
|
|
106
|
-
handler.admit(message);
|
|
106
|
+
handler.admit(message, event);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const larkRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lark 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `lark endpoint` 命令共享(同一 owner generation)。
|
|
4
|
-
*/
|
|
5
|
-
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
|
-
export const larkRuntimeStateToken = defineEndpointRuntimeStateToken('lark');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lark 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `lark endpoint` 命令共享(同一 owner generation)。
|
|
4
|
-
*/
|
|
5
|
-
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
|
-
|
|
7
|
-
export const larkRuntimeStateToken = defineEndpointRuntimeStateToken('lark');
|