@zhin.js/adapter-satori 1.1.17 → 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/src/protocol.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Satori protocol helpers. Spec: https://satori.chat/zh-CN/protocol/overview.html */
2
2
 
3
- import { isMediaRef, type ConversationKind, type ConversationRef } from '@zhin.js/im-contract';
3
+ import { EndpointDeliveryError, isMediaRef, type ConversationKind, type ConversationRef } from '@zhin.js/im-contract';
4
4
  import { formatCompact, getLogger } from '@zhin.js/logger';
5
5
 
6
6
  const logger = getLogger('satori');
@@ -69,6 +69,7 @@ export interface SatoriApiOptions {
69
69
  readonly platform: string;
70
70
  readonly userId: string;
71
71
  readonly token?: string;
72
+ readonly signal?: AbortSignal;
72
73
  }
73
74
 
74
75
  export interface SatoriWireSegment {
@@ -79,6 +80,9 @@ export interface SatoriWireSegment {
79
80
  /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
80
81
  export interface SatoriEndpointConfig {
81
82
  readonly id: string;
83
+ /** Exact remote login selected by this configured Endpoint. */
84
+ readonly platform: string;
85
+ readonly userId: string;
82
86
  readonly connection?: 'ws' | 'webhook';
83
87
  readonly baseUrl: string;
84
88
  readonly token?: string;
@@ -91,6 +95,8 @@ export interface ResolvedSatoriWsConfig {
91
95
  readonly context: 'satori';
92
96
  readonly connection: 'ws';
93
97
  readonly id: string;
98
+ readonly platform: string;
99
+ readonly userId: string;
94
100
  readonly baseUrl: string;
95
101
  readonly token?: string;
96
102
  readonly heartbeat_interval: number;
@@ -100,6 +106,8 @@ export interface ResolvedSatoriWebhookConfig {
100
106
  readonly context: 'satori';
101
107
  readonly connection: 'webhook';
102
108
  readonly id: string;
109
+ readonly platform: string;
110
+ readonly userId: string;
103
111
  readonly baseUrl: string;
104
112
  readonly token?: string;
105
113
  readonly path: string;
@@ -110,6 +118,8 @@ export type ResolvedSatoriConfig = ResolvedSatoriWsConfig | ResolvedSatoriWebhoo
110
118
  export function resolveSatoriConfig(config: SatoriEndpointConfig): ResolvedSatoriConfig {
111
119
  const connection = config.connection ?? 'ws';
112
120
  const id = requiredEndpointField(config.id, 'id');
121
+ const platform = requiredEndpointField(config.platform, 'platform');
122
+ const userId = requiredEndpointField(config.userId, 'userId');
113
123
  const baseUrl = requiredEndpointField(config.baseUrl, 'baseUrl');
114
124
  const token = typeof config.token === 'string' && config.token.trim()
115
125
  ? config.token.trim()
@@ -121,6 +131,8 @@ export function resolveSatoriConfig(config: SatoriEndpointConfig): ResolvedSator
121
131
  context: 'satori',
122
132
  connection: 'webhook',
123
133
  id,
134
+ platform,
135
+ userId,
124
136
  baseUrl,
125
137
  token,
126
138
  path,
@@ -132,6 +144,8 @@ export function resolveSatoriConfig(config: SatoriEndpointConfig): ResolvedSator
132
144
  context: 'satori',
133
145
  connection: 'ws',
134
146
  id,
147
+ platform,
148
+ userId,
135
149
  baseUrl,
136
150
  token,
137
151
  heartbeat_interval: heartbeat,
@@ -140,7 +154,7 @@ export function resolveSatoriConfig(config: SatoriEndpointConfig): ResolvedSator
140
154
 
141
155
  function requiredEndpointField(
142
156
  value: unknown,
143
- field: 'id' | 'baseUrl' | 'path',
157
+ field: 'id' | 'baseUrl' | 'path' | 'platform' | 'userId',
144
158
  ): string {
145
159
  if (typeof value !== 'string' || !value.trim()) {
146
160
  throw new TypeError(`Satori endpoint requires a non-empty ${field}`);
@@ -170,11 +184,14 @@ export function buildWsUrl(baseUrl: string, token?: string): string {
170
184
  * Call Satori HTTP API: POST {baseUrl}/v1/{resource}.{method}
171
185
  * @see https://satori.chat/en-US/protocol/api.html
172
186
  */
187
+ export interface SatoriApiRequest extends SatoriApiOptions {
188
+ readonly resource: string;
189
+ readonly method: string;
190
+ readonly params?: Record<string, unknown>;
191
+ }
192
+
173
193
  export async function callSatoriApi<T = unknown>(
174
- options: SatoriApiOptions,
175
- resource: string,
176
- method: string,
177
- params: Record<string, unknown> = {},
194
+ { resource, method, params = {}, ...options }: SatoriApiRequest,
178
195
  ): Promise<T> {
179
196
  const { baseUrl, platform, userId, token } = options;
180
197
  const url = `${baseUrl.replace(/\/$/, '')}/v1/${resource}.${method}`;
@@ -185,11 +202,14 @@ export async function callSatoriApi<T = unknown>(
185
202
  };
186
203
  if (token) headers.Authorization = `Bearer ${token}`;
187
204
 
188
- const res = await fetch(url, {
189
- method: 'POST',
190
- headers,
191
- body: JSON.stringify(params),
192
- });
205
+ if (options.signal?.aborted) throw new EndpointDeliveryError('endpoint_inactive', 'Satori request ended before submission', 'not_sent', { cause: options.signal.reason });
206
+ const body = JSON.stringify(params);
207
+ let res: Response;
208
+ try {
209
+ res = await fetch(url, { method: 'POST', headers, body, signal: options.signal });
210
+ } catch (cause) {
211
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'Satori HTTP request outcome is unknown', 'unknown', { cause });
212
+ }
193
213
 
194
214
  const text = await res.text();
195
215
  if (res.status === 401) throw new Error(`Satori API 认证失败: ${text}`);
@@ -1 +0,0 @@
1
- export declare const satoriRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
@@ -1,6 +0,0 @@
1
- /**
2
- * Satori 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `satori endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
- export const satoriRuntimeStateToken = defineEndpointRuntimeStateToken('satori');
@@ -1,7 +0,0 @@
1
- /**
2
- * Satori 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `satori endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
-
7
- export const satoriRuntimeStateToken = defineEndpointRuntimeStateToken('satori');