@rei-standard/amsg-client 2.0.0-pre1 → 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 +30 -0
- package/dist/index.cjs +66 -1
- package/dist/index.d.cts +84 -5
- package/dist/index.d.ts +84 -5
- package/dist/index.mjs +66 -1
- package/package.json +1 -1
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
|
|
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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ReiStandard Client SDK
|
|
3
|
-
* v2.0.
|
|
3
|
+
* v2.0.1
|
|
4
4
|
*
|
|
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
|
|
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
|
|
28
|
-
* @property {string}
|
|
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
|
|
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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ReiStandard Client SDK
|
|
3
|
-
* v2.0.
|
|
3
|
+
* v2.0.1
|
|
4
4
|
*
|
|
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
|
|
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
|
|
28
|
-
* @property {string}
|
|
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
|
|
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
|
|
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
|
*
|