@rei-standard/amsg-client 2.0.1 → 2.1.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 CHANGED
@@ -38,6 +38,35 @@ await client.scheduleMessage({
38
38
  });
39
39
  ```
40
40
 
41
+ ## 发送即时消息
42
+
43
+ 新代码用 `client.sendInstant(payload)`,走 [`@rei-standard/amsg-instant`](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/instant/README.md)。
44
+
45
+ ```js
46
+ const client = new ReiClient({
47
+ baseUrl: '/api/v1',
48
+ customBaseUrls: {
49
+ instant: 'https://instant.example.com', // 不传则用 baseUrl
50
+ },
51
+ userId: '550e8400-e29b-41d4-a716-446655440000',
52
+ });
53
+
54
+ await client.init();
55
+
56
+ await client.sendInstant({
57
+ contactName: 'Rei',
58
+ completePrompt: '你是 Rei,用一句话提醒用户带伞',
59
+ apiUrl: 'https://api.openai.com/v1/chat/completions',
60
+ apiKey: '...',
61
+ primaryModel: 'gpt-4o-mini',
62
+ pushSubscription: subscription.toJSON(),
63
+ });
64
+ ```
65
+
66
+ > `customBaseUrls` 是按端点名(如 `instant`)覆盖 `baseUrl` 的通用机制;后续其他端点也可以用同一字段独立指定 base URL,不会再加新的命名字段。
67
+
68
+ 旧路径 `scheduleMessage({ ...payload, messageType: 'instant' })` 仍然可用(兼容保留,多一次 DB 来回)。
69
+
41
70
  ## 导出 API(Exports)
42
71
 
43
72
  - `ReiClient`
@@ -46,6 +75,7 @@ await client.scheduleMessage({
46
75
 
47
76
  - `init()`
48
77
  - `scheduleMessage(payload)`
78
+ - `sendInstant(payload)`
49
79
  - `updateMessage(uuid, updates)`
50
80
  - `cancelMessage(uuid)`
51
81
  - `listMessages(opts)`
package/dist/index.cjs CHANGED
@@ -30,9 +30,27 @@ var ReiClient = class {
30
30
  if (!config || !config.baseUrl) throw new Error("[rei-standard-amsg-client] baseUrl is required");
31
31
  if (!config.userId) throw new Error("[rei-standard-amsg-client] userId is required");
32
32
  this._baseUrl = config.baseUrl.replace(/\/+$/, "");
33
+ this._customBaseUrls = {};
34
+ if (config.customBaseUrls && typeof config.customBaseUrls === "object") {
35
+ for (const [name, url] of Object.entries(config.customBaseUrls)) {
36
+ if (typeof url === "string" && url) {
37
+ this._customBaseUrls[name] = url.replace(/\/+$/, "");
38
+ }
39
+ }
40
+ }
33
41
  this._userId = config.userId;
34
42
  this._userKey = null;
35
43
  }
44
+ /**
45
+ * Resolve the base URL for a given endpoint, falling back to `baseUrl`.
46
+ *
47
+ * @private
48
+ * @param {string} endpointName
49
+ * @returns {string}
50
+ */
51
+ _resolveBaseUrl(endpointName) {
52
+ return this._customBaseUrls[endpointName] || this._baseUrl;
53
+ }
36
54
  // ─── Initialisation ─────────────────────────────────────────────
37
55
  /**
38
56
  * Fetch the user-specific encryption key.
@@ -53,7 +71,14 @@ var ReiClient = class {
53
71
  }
54
72
  // ─── Public API ─────────────────────────────────────────────────
55
73
  /**
56
- * Schedule (or instantly send) a message.
74
+ * Schedule a message.
75
+ *
76
+ * Note: For `messageType: 'instant'`, prefer `sendInstant()` instead.
77
+ * That routes through `@rei-standard/amsg-instant` (stateless, no DB
78
+ * round-trip) rather than `amsg-server`'s schedule-message endpoint.
79
+ * This method still works for instant via amsg-server for backward
80
+ * compatibility — see CHANGELOG / README for details.
81
+ *
57
82
  * The payload is automatically encrypted before transmission.
58
83
  *
59
84
  * @param {Object} payload - Schedule message payload.
@@ -73,6 +98,46 @@ var ReiClient = class {
73
98
  });
74
99
  return res.json();
75
100
  }
101
+ /**
102
+ * Send a one-shot instant message via `@rei-standard/amsg-instant`.
103
+ *
104
+ * Compared to `scheduleMessage({ messageType: 'instant', ... })`:
105
+ * - No DB round-trip on the server side (stateless)
106
+ * - Deployable to Cloudflare Workers / Deno Deploy / Vercel Edge
107
+ * - Rejects scheduled-only fields (`firstSendTime`, `recurrenceType`)
108
+ *
109
+ * The payload uses the same field names as `scheduleMessage`, minus
110
+ * `firstSendTime` and `recurrenceType`. It is auto-encrypted with the
111
+ * same `userKey` fetched by `init()`, so the upstream deployment of
112
+ * amsg-instant must share the same `masterKey` as the amsg-server
113
+ * tenant.
114
+ *
115
+ * Routes to `customBaseUrls.instant` if configured, otherwise `baseUrl`.
116
+ *
117
+ * @param {Object} payload - Instant message payload.
118
+ * @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
119
+ * @param {{ authorization?: string }} [opts] - Optional auth header to forward.
120
+ * @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
121
+ */
122
+ async sendInstant(payload, endpointPath = "/instant", opts = {}) {
123
+ const encrypted = await this._encrypt(JSON.stringify(payload));
124
+ const headers = {
125
+ "Content-Type": "application/json",
126
+ "X-User-Id": this._userId,
127
+ "X-Payload-Encrypted": "true",
128
+ "X-Encryption-Version": "1"
129
+ };
130
+ if (opts.authorization) {
131
+ headers["Authorization"] = opts.authorization;
132
+ }
133
+ const path = endpointPath.startsWith("/") ? endpointPath : `/${endpointPath}`;
134
+ const res = await fetch(`${this._resolveBaseUrl("instant")}${path}`, {
135
+ method: "POST",
136
+ headers,
137
+ body: JSON.stringify(encrypted)
138
+ });
139
+ return res.json();
140
+ }
76
141
  /**
77
142
  * Update an existing scheduled message.
78
143
  *
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Lightweight browser client that handles:
6
6
  * - AES-256-GCM encryption using the Web Crypto API
7
7
  * - Push subscription management via the Push API
8
- * - Convenient request helpers for all 7 endpoints
8
+ * - Convenient request helpers for amsg-server endpoints + amsg-instant
9
9
  *
10
10
  * Usage:
11
11
  * import { ReiClient } from '@rei-standard/amsg-client';
@@ -24,8 +24,16 @@
24
24
 
25
25
  /**
26
26
  * @typedef {Object} ReiClientConfig
27
- * @property {string} baseUrl - Base URL of the API (e.g. https://host/api/v1).
28
- * @property {string} userId - Current user identifier.
27
+ * @property {string} baseUrl - Default base URL of the API (e.g. https://host/api/v1).
28
+ * @property {Record<string, string>} [customBaseUrls] - Optional per-endpoint base URL overrides.
29
+ * Key is the endpoint name (e.g. `instant`); value is
30
+ * the base URL to use for that endpoint instead of
31
+ * `baseUrl`. Useful when different endpoints live on
32
+ * different deployments (e.g. `instant` on Cloudflare
33
+ * Workers while the rest run on Netlify). Future
34
+ * endpoints (e.g. `schedule`, `messages`) can be
35
+ * overridden the same way without an API change.
36
+ * @property {string} userId - Current user identifier (UUID v4).
29
37
  */
30
38
 
31
39
  class ReiClient {
@@ -39,11 +47,31 @@ class ReiClient {
39
47
  /** @private */
40
48
  this._baseUrl = config.baseUrl.replace(/\/+$/, '');
41
49
  /** @private */
50
+ this._customBaseUrls = {};
51
+ if (config.customBaseUrls && typeof config.customBaseUrls === 'object') {
52
+ for (const [name, url] of Object.entries(config.customBaseUrls)) {
53
+ if (typeof url === 'string' && url) {
54
+ this._customBaseUrls[name] = url.replace(/\/+$/, '');
55
+ }
56
+ }
57
+ }
58
+ /** @private */
42
59
  this._userId = config.userId;
43
60
  /** @private */
44
61
  this._userKey = null;
45
62
  }
46
63
 
64
+ /**
65
+ * Resolve the base URL for a given endpoint, falling back to `baseUrl`.
66
+ *
67
+ * @private
68
+ * @param {string} endpointName
69
+ * @returns {string}
70
+ */
71
+ _resolveBaseUrl(endpointName) {
72
+ return this._customBaseUrls[endpointName] || this._baseUrl;
73
+ }
74
+
47
75
  // ─── Initialisation ─────────────────────────────────────────────
48
76
 
49
77
  /**
@@ -70,7 +98,14 @@ class ReiClient {
70
98
  // ─── Public API ─────────────────────────────────────────────────
71
99
 
72
100
  /**
73
- * Schedule (or instantly send) a message.
101
+ * Schedule a message.
102
+ *
103
+ * Note: For `messageType: 'instant'`, prefer `sendInstant()` instead.
104
+ * That routes through `@rei-standard/amsg-instant` (stateless, no DB
105
+ * round-trip) rather than `amsg-server`'s schedule-message endpoint.
106
+ * This method still works for instant via amsg-server for backward
107
+ * compatibility — see CHANGELOG / README for details.
108
+ *
74
109
  * The payload is automatically encrypted before transmission.
75
110
  *
76
111
  * @param {Object} payload - Schedule message payload.
@@ -93,6 +128,50 @@ class ReiClient {
93
128
  return res.json();
94
129
  }
95
130
 
131
+ /**
132
+ * Send a one-shot instant message via `@rei-standard/amsg-instant`.
133
+ *
134
+ * Compared to `scheduleMessage({ messageType: 'instant', ... })`:
135
+ * - No DB round-trip on the server side (stateless)
136
+ * - Deployable to Cloudflare Workers / Deno Deploy / Vercel Edge
137
+ * - Rejects scheduled-only fields (`firstSendTime`, `recurrenceType`)
138
+ *
139
+ * The payload uses the same field names as `scheduleMessage`, minus
140
+ * `firstSendTime` and `recurrenceType`. It is auto-encrypted with the
141
+ * same `userKey` fetched by `init()`, so the upstream deployment of
142
+ * amsg-instant must share the same `masterKey` as the amsg-server
143
+ * tenant.
144
+ *
145
+ * Routes to `customBaseUrls.instant` if configured, otherwise `baseUrl`.
146
+ *
147
+ * @param {Object} payload - Instant message payload.
148
+ * @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
149
+ * @param {{ authorization?: string }} [opts] - Optional auth header to forward.
150
+ * @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
151
+ */
152
+ async sendInstant(payload, endpointPath = '/instant', opts = {}) {
153
+ const encrypted = await this._encrypt(JSON.stringify(payload));
154
+
155
+ const headers = {
156
+ 'Content-Type': 'application/json',
157
+ 'X-User-Id': this._userId,
158
+ 'X-Payload-Encrypted': 'true',
159
+ 'X-Encryption-Version': '1'
160
+ };
161
+ if (opts.authorization) {
162
+ headers['Authorization'] = opts.authorization;
163
+ }
164
+
165
+ const path = endpointPath.startsWith('/') ? endpointPath : `/${endpointPath}`;
166
+ const res = await fetch(`${this._resolveBaseUrl('instant')}${path}`, {
167
+ method: 'POST',
168
+ headers,
169
+ body: JSON.stringify(encrypted)
170
+ });
171
+
172
+ return res.json();
173
+ }
174
+
96
175
  /**
97
176
  * Update an existing scheduled message.
98
177
  *
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Lightweight browser client that handles:
6
6
  * - AES-256-GCM encryption using the Web Crypto API
7
7
  * - Push subscription management via the Push API
8
- * - Convenient request helpers for all 7 endpoints
8
+ * - Convenient request helpers for amsg-server endpoints + amsg-instant
9
9
  *
10
10
  * Usage:
11
11
  * import { ReiClient } from '@rei-standard/amsg-client';
@@ -24,8 +24,16 @@
24
24
 
25
25
  /**
26
26
  * @typedef {Object} ReiClientConfig
27
- * @property {string} baseUrl - Base URL of the API (e.g. https://host/api/v1).
28
- * @property {string} userId - Current user identifier.
27
+ * @property {string} baseUrl - Default base URL of the API (e.g. https://host/api/v1).
28
+ * @property {Record<string, string>} [customBaseUrls] - Optional per-endpoint base URL overrides.
29
+ * Key is the endpoint name (e.g. `instant`); value is
30
+ * the base URL to use for that endpoint instead of
31
+ * `baseUrl`. Useful when different endpoints live on
32
+ * different deployments (e.g. `instant` on Cloudflare
33
+ * Workers while the rest run on Netlify). Future
34
+ * endpoints (e.g. `schedule`, `messages`) can be
35
+ * overridden the same way without an API change.
36
+ * @property {string} userId - Current user identifier (UUID v4).
29
37
  */
30
38
 
31
39
  class ReiClient {
@@ -39,11 +47,31 @@ class ReiClient {
39
47
  /** @private */
40
48
  this._baseUrl = config.baseUrl.replace(/\/+$/, '');
41
49
  /** @private */
50
+ this._customBaseUrls = {};
51
+ if (config.customBaseUrls && typeof config.customBaseUrls === 'object') {
52
+ for (const [name, url] of Object.entries(config.customBaseUrls)) {
53
+ if (typeof url === 'string' && url) {
54
+ this._customBaseUrls[name] = url.replace(/\/+$/, '');
55
+ }
56
+ }
57
+ }
58
+ /** @private */
42
59
  this._userId = config.userId;
43
60
  /** @private */
44
61
  this._userKey = null;
45
62
  }
46
63
 
64
+ /**
65
+ * Resolve the base URL for a given endpoint, falling back to `baseUrl`.
66
+ *
67
+ * @private
68
+ * @param {string} endpointName
69
+ * @returns {string}
70
+ */
71
+ _resolveBaseUrl(endpointName) {
72
+ return this._customBaseUrls[endpointName] || this._baseUrl;
73
+ }
74
+
47
75
  // ─── Initialisation ─────────────────────────────────────────────
48
76
 
49
77
  /**
@@ -70,7 +98,14 @@ class ReiClient {
70
98
  // ─── Public API ─────────────────────────────────────────────────
71
99
 
72
100
  /**
73
- * Schedule (or instantly send) a message.
101
+ * Schedule a message.
102
+ *
103
+ * Note: For `messageType: 'instant'`, prefer `sendInstant()` instead.
104
+ * That routes through `@rei-standard/amsg-instant` (stateless, no DB
105
+ * round-trip) rather than `amsg-server`'s schedule-message endpoint.
106
+ * This method still works for instant via amsg-server for backward
107
+ * compatibility — see CHANGELOG / README for details.
108
+ *
74
109
  * The payload is automatically encrypted before transmission.
75
110
  *
76
111
  * @param {Object} payload - Schedule message payload.
@@ -93,6 +128,50 @@ class ReiClient {
93
128
  return res.json();
94
129
  }
95
130
 
131
+ /**
132
+ * Send a one-shot instant message via `@rei-standard/amsg-instant`.
133
+ *
134
+ * Compared to `scheduleMessage({ messageType: 'instant', ... })`:
135
+ * - No DB round-trip on the server side (stateless)
136
+ * - Deployable to Cloudflare Workers / Deno Deploy / Vercel Edge
137
+ * - Rejects scheduled-only fields (`firstSendTime`, `recurrenceType`)
138
+ *
139
+ * The payload uses the same field names as `scheduleMessage`, minus
140
+ * `firstSendTime` and `recurrenceType`. It is auto-encrypted with the
141
+ * same `userKey` fetched by `init()`, so the upstream deployment of
142
+ * amsg-instant must share the same `masterKey` as the amsg-server
143
+ * tenant.
144
+ *
145
+ * Routes to `customBaseUrls.instant` if configured, otherwise `baseUrl`.
146
+ *
147
+ * @param {Object} payload - Instant message payload.
148
+ * @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
149
+ * @param {{ authorization?: string }} [opts] - Optional auth header to forward.
150
+ * @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
151
+ */
152
+ async sendInstant(payload, endpointPath = '/instant', opts = {}) {
153
+ const encrypted = await this._encrypt(JSON.stringify(payload));
154
+
155
+ const headers = {
156
+ 'Content-Type': 'application/json',
157
+ 'X-User-Id': this._userId,
158
+ 'X-Payload-Encrypted': 'true',
159
+ 'X-Encryption-Version': '1'
160
+ };
161
+ if (opts.authorization) {
162
+ headers['Authorization'] = opts.authorization;
163
+ }
164
+
165
+ const path = endpointPath.startsWith('/') ? endpointPath : `/${endpointPath}`;
166
+ const res = await fetch(`${this._resolveBaseUrl('instant')}${path}`, {
167
+ method: 'POST',
168
+ headers,
169
+ body: JSON.stringify(encrypted)
170
+ });
171
+
172
+ return res.json();
173
+ }
174
+
96
175
  /**
97
176
  * Update an existing scheduled message.
98
177
  *
package/dist/index.mjs CHANGED
@@ -7,9 +7,27 @@ var ReiClient = class {
7
7
  if (!config || !config.baseUrl) throw new Error("[rei-standard-amsg-client] baseUrl is required");
8
8
  if (!config.userId) throw new Error("[rei-standard-amsg-client] userId is required");
9
9
  this._baseUrl = config.baseUrl.replace(/\/+$/, "");
10
+ this._customBaseUrls = {};
11
+ if (config.customBaseUrls && typeof config.customBaseUrls === "object") {
12
+ for (const [name, url] of Object.entries(config.customBaseUrls)) {
13
+ if (typeof url === "string" && url) {
14
+ this._customBaseUrls[name] = url.replace(/\/+$/, "");
15
+ }
16
+ }
17
+ }
10
18
  this._userId = config.userId;
11
19
  this._userKey = null;
12
20
  }
21
+ /**
22
+ * Resolve the base URL for a given endpoint, falling back to `baseUrl`.
23
+ *
24
+ * @private
25
+ * @param {string} endpointName
26
+ * @returns {string}
27
+ */
28
+ _resolveBaseUrl(endpointName) {
29
+ return this._customBaseUrls[endpointName] || this._baseUrl;
30
+ }
13
31
  // ─── Initialisation ─────────────────────────────────────────────
14
32
  /**
15
33
  * Fetch the user-specific encryption key.
@@ -30,7 +48,14 @@ var ReiClient = class {
30
48
  }
31
49
  // ─── Public API ─────────────────────────────────────────────────
32
50
  /**
33
- * Schedule (or instantly send) a message.
51
+ * Schedule a message.
52
+ *
53
+ * Note: For `messageType: 'instant'`, prefer `sendInstant()` instead.
54
+ * That routes through `@rei-standard/amsg-instant` (stateless, no DB
55
+ * round-trip) rather than `amsg-server`'s schedule-message endpoint.
56
+ * This method still works for instant via amsg-server for backward
57
+ * compatibility — see CHANGELOG / README for details.
58
+ *
34
59
  * The payload is automatically encrypted before transmission.
35
60
  *
36
61
  * @param {Object} payload - Schedule message payload.
@@ -50,6 +75,46 @@ var ReiClient = class {
50
75
  });
51
76
  return res.json();
52
77
  }
78
+ /**
79
+ * Send a one-shot instant message via `@rei-standard/amsg-instant`.
80
+ *
81
+ * Compared to `scheduleMessage({ messageType: 'instant', ... })`:
82
+ * - No DB round-trip on the server side (stateless)
83
+ * - Deployable to Cloudflare Workers / Deno Deploy / Vercel Edge
84
+ * - Rejects scheduled-only fields (`firstSendTime`, `recurrenceType`)
85
+ *
86
+ * The payload uses the same field names as `scheduleMessage`, minus
87
+ * `firstSendTime` and `recurrenceType`. It is auto-encrypted with the
88
+ * same `userKey` fetched by `init()`, so the upstream deployment of
89
+ * amsg-instant must share the same `masterKey` as the amsg-server
90
+ * tenant.
91
+ *
92
+ * Routes to `customBaseUrls.instant` if configured, otherwise `baseUrl`.
93
+ *
94
+ * @param {Object} payload - Instant message payload.
95
+ * @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
96
+ * @param {{ authorization?: string }} [opts] - Optional auth header to forward.
97
+ * @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
98
+ */
99
+ async sendInstant(payload, endpointPath = "/instant", opts = {}) {
100
+ const encrypted = await this._encrypt(JSON.stringify(payload));
101
+ const headers = {
102
+ "Content-Type": "application/json",
103
+ "X-User-Id": this._userId,
104
+ "X-Payload-Encrypted": "true",
105
+ "X-Encryption-Version": "1"
106
+ };
107
+ if (opts.authorization) {
108
+ headers["Authorization"] = opts.authorization;
109
+ }
110
+ const path = endpointPath.startsWith("/") ? endpointPath : `/${endpointPath}`;
111
+ const res = await fetch(`${this._resolveBaseUrl("instant")}${path}`, {
112
+ method: "POST",
113
+ headers,
114
+ body: JSON.stringify(encrypted)
115
+ });
116
+ return res.json();
117
+ }
53
118
  /**
54
119
  * Update an existing scheduled message.
55
120
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-client",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "ReiStandard Active Messaging browser client SDK",
5
5
  "repository": {
6
6
  "type": "git",