aamp-openclaw-plugin 0.1.22 → 0.1.25
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/bin/aamp-openclaw-plugin.mjs +162 -16
- package/dist/index.js +31 -11
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
|
@@ -62,9 +62,113 @@ function expandHome(pathValue) {
|
|
|
62
62
|
return pathValue
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function stripJsonComments(text) {
|
|
66
|
+
let result = ''
|
|
67
|
+
let inString = false
|
|
68
|
+
let stringQuote = ''
|
|
69
|
+
let escaped = false
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
72
|
+
const char = text[i]
|
|
73
|
+
const next = text[i + 1]
|
|
74
|
+
|
|
75
|
+
if (inString) {
|
|
76
|
+
result += char
|
|
77
|
+
if (escaped) {
|
|
78
|
+
escaped = false
|
|
79
|
+
} else if (char === '\\') {
|
|
80
|
+
escaped = true
|
|
81
|
+
} else if (char === stringQuote) {
|
|
82
|
+
inString = false
|
|
83
|
+
stringQuote = ''
|
|
84
|
+
}
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (char === '"' || char === "'") {
|
|
89
|
+
inString = true
|
|
90
|
+
stringQuote = char
|
|
91
|
+
result += char
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (char === '/' && next === '/') {
|
|
96
|
+
i += 2
|
|
97
|
+
while (i < text.length && text[i] !== '\n') i += 1
|
|
98
|
+
if (i < text.length) result += text[i]
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (char === '/' && next === '*') {
|
|
103
|
+
i += 2
|
|
104
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i += 1
|
|
105
|
+
i += 1
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
result += char
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return result
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function stripTrailingCommas(text) {
|
|
116
|
+
let result = ''
|
|
117
|
+
let inString = false
|
|
118
|
+
let stringQuote = ''
|
|
119
|
+
let escaped = false
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
122
|
+
const char = text[i]
|
|
123
|
+
|
|
124
|
+
if (inString) {
|
|
125
|
+
result += char
|
|
126
|
+
if (escaped) {
|
|
127
|
+
escaped = false
|
|
128
|
+
} else if (char === '\\') {
|
|
129
|
+
escaped = true
|
|
130
|
+
} else if (char === stringQuote) {
|
|
131
|
+
inString = false
|
|
132
|
+
stringQuote = ''
|
|
133
|
+
}
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (char === '"' || char === "'") {
|
|
138
|
+
inString = true
|
|
139
|
+
stringQuote = char
|
|
140
|
+
result += char
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (char === ',') {
|
|
145
|
+
let lookahead = i + 1
|
|
146
|
+
while (lookahead < text.length && /\s/.test(text[lookahead])) lookahead += 1
|
|
147
|
+
if (text[lookahead] === '}' || text[lookahead] === ']') {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
result += char
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function parseJsonConfig(raw, path) {
|
|
159
|
+
const normalized = raw.replace(/^\uFEFF/, '')
|
|
160
|
+
const sanitized = stripTrailingCommas(stripJsonComments(normalized))
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(sanitized)
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
165
|
+
throw new Error(`Failed to parse ${path}: ${reason}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
65
169
|
function readJsonFile(path) {
|
|
66
170
|
if (!existsSync(path)) return null
|
|
67
|
-
return
|
|
171
|
+
return parseJsonConfig(readFileSync(path, 'utf-8'), path)
|
|
68
172
|
}
|
|
69
173
|
|
|
70
174
|
function writeJsonFile(path, value) {
|
|
@@ -82,6 +186,7 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
|
82
186
|
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
83
187
|
if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
|
|
84
188
|
if (!next.plugins.entries || typeof next.plugins.entries !== 'object') next.plugins.entries = {}
|
|
189
|
+
if (!next.channels || typeof next.channels !== 'object') next.channels = {}
|
|
85
190
|
|
|
86
191
|
if (!next.plugins.allow.includes(PLUGIN_ID)) {
|
|
87
192
|
next.plugins.allow.push(PLUGIN_ID)
|
|
@@ -89,29 +194,51 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
|
89
194
|
|
|
90
195
|
const legacyEntry = next.plugins.entries.aamp
|
|
91
196
|
const prevEntry = next.plugins.entries[PLUGIN_ID] ?? legacyEntry
|
|
92
|
-
const mergedConfig = {
|
|
93
|
-
...(prevEntry?.config && typeof prevEntry.config === 'object' ? prevEntry.config : {}),
|
|
94
|
-
...pluginConfig,
|
|
95
|
-
}
|
|
96
|
-
if (!pluginConfig.senderPolicies) {
|
|
97
|
-
delete mergedConfig.senderPolicies
|
|
98
|
-
}
|
|
99
|
-
|
|
100
197
|
next.plugins.entries[PLUGIN_ID] = {
|
|
101
198
|
enabled: true,
|
|
102
199
|
...(prevEntry && typeof prevEntry === 'object' ? prevEntry : {}),
|
|
103
|
-
config: mergedConfig,
|
|
104
200
|
}
|
|
105
201
|
|
|
106
202
|
if (next.plugins.entries.aamp) {
|
|
107
203
|
delete next.plugins.entries.aamp
|
|
108
204
|
}
|
|
109
205
|
|
|
206
|
+
const previousChannelConfig =
|
|
207
|
+
next.channels.aamp && typeof next.channels.aamp === 'object' ? next.channels.aamp : {}
|
|
208
|
+
const mergedChannelConfig = {
|
|
209
|
+
...previousChannelConfig,
|
|
210
|
+
...pluginConfig,
|
|
211
|
+
enabled: true,
|
|
212
|
+
}
|
|
213
|
+
if (!pluginConfig.senderPolicies) {
|
|
214
|
+
delete mergedChannelConfig.senderPolicies
|
|
215
|
+
}
|
|
216
|
+
next.channels.aamp = mergedChannelConfig
|
|
217
|
+
|
|
110
218
|
next.tools = ensureAampToolAllowlist(next.tools, options)
|
|
111
219
|
|
|
112
220
|
return next
|
|
113
221
|
}
|
|
114
222
|
|
|
223
|
+
function ensurePluginInstallRecord(config, installRecord) {
|
|
224
|
+
const next = config && typeof config === 'object' ? structuredClone(config) : {}
|
|
225
|
+
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
226
|
+
if (!next.plugins.installs || typeof next.plugins.installs !== 'object') next.plugins.installs = {}
|
|
227
|
+
|
|
228
|
+
next.plugins.installs[PLUGIN_ID] = {
|
|
229
|
+
...(next.plugins.installs[PLUGIN_ID] && typeof next.plugins.installs[PLUGIN_ID] === 'object'
|
|
230
|
+
? next.plugins.installs[PLUGIN_ID]
|
|
231
|
+
: {}),
|
|
232
|
+
...installRecord,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (next.plugins.installs.aamp) {
|
|
236
|
+
delete next.plugins.installs.aamp
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return next
|
|
240
|
+
}
|
|
241
|
+
|
|
115
242
|
function ensureAampToolAllowlist(toolsConfig, options = {}) {
|
|
116
243
|
const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
|
|
117
244
|
const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
|
|
@@ -278,7 +405,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
|
278
405
|
writeFileSync(credentialsPath, existingCredentials)
|
|
279
406
|
}
|
|
280
407
|
|
|
281
|
-
return extensionDir
|
|
408
|
+
return { extensionDir, packageJson, packageRoot }
|
|
282
409
|
}
|
|
283
410
|
|
|
284
411
|
function restartGateway() {
|
|
@@ -371,9 +498,12 @@ async function runInit() {
|
|
|
371
498
|
const configPath = resolveOpenClawConfigPath()
|
|
372
499
|
const existing = readJsonFile(configPath)
|
|
373
500
|
const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
|
|
374
|
-
const previousConfig =
|
|
375
|
-
|
|
376
|
-
|
|
501
|
+
const previousConfig =
|
|
502
|
+
existing?.channels?.aamp && typeof existing.channels.aamp === 'object'
|
|
503
|
+
? existing.channels.aamp
|
|
504
|
+
: previousEntry?.config && typeof previousEntry.config === 'object'
|
|
505
|
+
? previousEntry.config
|
|
506
|
+
: null
|
|
377
507
|
const previousCredentialsFile = previousConfig?.credentialsFile || DEFAULT_CREDENTIALS_FILE
|
|
378
508
|
const previousSlug = previousConfig?.slug || 'openclaw-agent'
|
|
379
509
|
|
|
@@ -466,10 +596,10 @@ async function runInit() {
|
|
|
466
596
|
}
|
|
467
597
|
|
|
468
598
|
output.write('\nInstalling OpenClaw plugin files...\n')
|
|
469
|
-
const extensionDir = installPluginFiles(previousCredentialsFile)
|
|
599
|
+
const { extensionDir, packageJson, packageRoot } = installPluginFiles(previousCredentialsFile)
|
|
470
600
|
|
|
471
601
|
const toolPolicyPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline })
|
|
472
|
-
|
|
602
|
+
let next = ensurePluginConfig(existing, {
|
|
473
603
|
aampHost,
|
|
474
604
|
slug,
|
|
475
605
|
credentialsFile: DEFAULT_CREDENTIALS_FILE,
|
|
@@ -478,6 +608,20 @@ async function runInit() {
|
|
|
478
608
|
includeCodingBaseline,
|
|
479
609
|
})
|
|
480
610
|
|
|
611
|
+
const now = new Date().toISOString()
|
|
612
|
+
next = ensurePluginInstallRecord(next, {
|
|
613
|
+
source: 'npm',
|
|
614
|
+
spec: packageJson?.name || PLUGIN_ID,
|
|
615
|
+
sourcePath: packageRoot,
|
|
616
|
+
installPath: extensionDir,
|
|
617
|
+
version: packageJson?.version || '0.0.0',
|
|
618
|
+
resolvedName: packageJson?.name || PLUGIN_ID,
|
|
619
|
+
resolvedVersion: packageJson?.version || '0.0.0',
|
|
620
|
+
resolvedSpec: `${packageJson?.name || PLUGIN_ID}@${packageJson?.version || '0.0.0'}`,
|
|
621
|
+
installedAt: now,
|
|
622
|
+
resolvedAt: now,
|
|
623
|
+
})
|
|
624
|
+
|
|
481
625
|
writeJsonFile(configPath, next)
|
|
482
626
|
|
|
483
627
|
const identityResult = await ensureMailboxIdentity({
|
|
@@ -496,6 +640,8 @@ async function runInit() {
|
|
|
496
640
|
'',
|
|
497
641
|
'Configured plugin entry:',
|
|
498
642
|
` plugins.entries["${PLUGIN_ID}"]`,
|
|
643
|
+
` plugins.installs["${PLUGIN_ID}"]`,
|
|
644
|
+
` channels.aamp.enabled: ${next.channels?.aamp?.enabled === true ? 'true' : 'false'}`,
|
|
499
645
|
` aampHost: ${aampHost}`,
|
|
500
646
|
` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
|
|
501
647
|
` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import WebSocket from "ws";
|
|
3
3
|
|
|
4
4
|
// ../sdk/src/types.ts
|
|
5
|
+
var AAMP_PROTOCOL_VERSION = "1.0";
|
|
5
6
|
var AAMP_HEADER = {
|
|
7
|
+
VERSION: "X-AAMP-Version",
|
|
6
8
|
INTENT: "X-AAMP-Intent",
|
|
7
9
|
TASK_ID: "X-AAMP-TaskId",
|
|
8
10
|
TIMEOUT: "X-AAMP-Timeout",
|
|
@@ -122,6 +124,7 @@ function parseAampHeaders(meta) {
|
|
|
122
124
|
const headers = normalizeHeaders(meta.headers);
|
|
123
125
|
const intent = getAampHeader(headers, AAMP_HEADER.INTENT);
|
|
124
126
|
const taskId = getAampHeader(headers, AAMP_HEADER.TASK_ID);
|
|
127
|
+
const protocolVersion = getAampHeader(headers, AAMP_HEADER.VERSION) ?? AAMP_PROTOCOL_VERSION;
|
|
125
128
|
if (!intent || !taskId)
|
|
126
129
|
return null;
|
|
127
130
|
const from = meta.from.replace(/^<|>$/g, "");
|
|
@@ -135,6 +138,7 @@ function parseAampHeaders(meta) {
|
|
|
135
138
|
);
|
|
136
139
|
const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
|
|
137
140
|
const dispatch = {
|
|
141
|
+
protocolVersion,
|
|
138
142
|
intent: "task.dispatch",
|
|
139
143
|
taskId,
|
|
140
144
|
title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
|
|
@@ -159,6 +163,7 @@ function parseAampHeaders(meta) {
|
|
|
159
163
|
getAampHeader(headers, AAMP_HEADER.STRUCTURED_RESULT)
|
|
160
164
|
);
|
|
161
165
|
const result = {
|
|
166
|
+
protocolVersion,
|
|
162
167
|
intent: "task.result",
|
|
163
168
|
taskId,
|
|
164
169
|
status,
|
|
@@ -171,12 +176,13 @@ function parseAampHeaders(meta) {
|
|
|
171
176
|
};
|
|
172
177
|
return result;
|
|
173
178
|
}
|
|
174
|
-
if (intent === "task.help") {
|
|
179
|
+
if (intent === "task.help_needed" || intent === "task.help") {
|
|
175
180
|
const question = getAampHeader(headers, AAMP_HEADER.QUESTION) ?? "";
|
|
176
181
|
const blockedReason = getAampHeader(headers, AAMP_HEADER.BLOCKED_REASON) ?? "";
|
|
177
182
|
const suggestedOptionsStr = getAampHeader(headers, AAMP_HEADER.SUGGESTED_OPTIONS) ?? "";
|
|
178
183
|
const help = {
|
|
179
|
-
|
|
184
|
+
protocolVersion,
|
|
185
|
+
intent: "task.help_needed",
|
|
180
186
|
taskId,
|
|
181
187
|
question: decodeMimeEncodedWords(question),
|
|
182
188
|
blockedReason: decodeMimeEncodedWords(blockedReason),
|
|
@@ -189,6 +195,7 @@ function parseAampHeaders(meta) {
|
|
|
189
195
|
}
|
|
190
196
|
if (intent === "task.ack") {
|
|
191
197
|
const ack = {
|
|
198
|
+
protocolVersion,
|
|
192
199
|
intent: "task.ack",
|
|
193
200
|
taskId,
|
|
194
201
|
from,
|
|
@@ -201,6 +208,7 @@ function parseAampHeaders(meta) {
|
|
|
201
208
|
}
|
|
202
209
|
function buildDispatchHeaders(params) {
|
|
203
210
|
const headers = {
|
|
211
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
204
212
|
[AAMP_HEADER.INTENT]: "task.dispatch",
|
|
205
213
|
[AAMP_HEADER.TASK_ID]: params.taskId
|
|
206
214
|
};
|
|
@@ -221,12 +229,14 @@ function buildDispatchHeaders(params) {
|
|
|
221
229
|
}
|
|
222
230
|
function buildAckHeaders(opts) {
|
|
223
231
|
return {
|
|
232
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
224
233
|
[AAMP_HEADER.INTENT]: "task.ack",
|
|
225
234
|
[AAMP_HEADER.TASK_ID]: opts.taskId
|
|
226
235
|
};
|
|
227
236
|
}
|
|
228
237
|
function buildResultHeaders(params) {
|
|
229
238
|
const headers = {
|
|
239
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
230
240
|
[AAMP_HEADER.INTENT]: "task.result",
|
|
231
241
|
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
232
242
|
[AAMP_HEADER.STATUS]: params.status,
|
|
@@ -243,7 +253,8 @@ function buildResultHeaders(params) {
|
|
|
243
253
|
}
|
|
244
254
|
function buildHelpHeaders(params) {
|
|
245
255
|
return {
|
|
246
|
-
[AAMP_HEADER.
|
|
256
|
+
[AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
|
|
257
|
+
[AAMP_HEADER.INTENT]: "task.help_needed",
|
|
247
258
|
[AAMP_HEADER.TASK_ID]: params.taskId,
|
|
248
259
|
[AAMP_HEADER.QUESTION]: params.question,
|
|
249
260
|
[AAMP_HEADER.BLOCKED_REASON]: params.blockedReason,
|
|
@@ -493,7 +504,7 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
493
504
|
* Process a received email.
|
|
494
505
|
*
|
|
495
506
|
* Priority:
|
|
496
|
-
* 1. If X-AAMP-Intent is present → emit typed AAMP event (task.dispatch / task.result / task.
|
|
507
|
+
* 1. If X-AAMP-Intent is present → emit typed AAMP event (task.dispatch / task.result / task.help_needed)
|
|
497
508
|
* 2. If In-Reply-To is present → emit 'reply' event so the application layer can
|
|
498
509
|
* resolve the thread (inReplyTo → taskId via Redis/DB) and handle human replies.
|
|
499
510
|
* 3. Otherwise → ignore (not an AAMP-related email)
|
|
@@ -541,7 +552,8 @@ var JmapPushClient = class extends TinyEmitter {
|
|
|
541
552
|
case "task.result":
|
|
542
553
|
this.emit("task.result", aampMsg);
|
|
543
554
|
break;
|
|
544
|
-
case "task.
|
|
555
|
+
case "task.help_needed":
|
|
556
|
+
this.emit("task.help_needed", aampMsg);
|
|
545
557
|
this.emit("task.help", aampMsg);
|
|
546
558
|
break;
|
|
547
559
|
case "task.ack":
|
|
@@ -1240,7 +1252,8 @@ var AampClient = class extends TinyEmitter {
|
|
|
1240
1252
|
this.jmapClient.on("task.result", (result) => {
|
|
1241
1253
|
this.emit("task.result", result);
|
|
1242
1254
|
});
|
|
1243
|
-
this.jmapClient.on("task.
|
|
1255
|
+
this.jmapClient.on("task.help_needed", (help) => {
|
|
1256
|
+
this.emit("task.help_needed", help);
|
|
1244
1257
|
this.emit("task.help", help);
|
|
1245
1258
|
});
|
|
1246
1259
|
this.jmapClient.on("task.ack", (ack) => {
|
|
@@ -1523,7 +1536,7 @@ var src_default = {
|
|
|
1523
1536
|
},
|
|
1524
1537
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1525
1538
|
register(api) {
|
|
1526
|
-
const cfg = api.pluginConfig ?? {};
|
|
1539
|
+
const cfg = api.config?.channels?.aamp ?? api.pluginConfig ?? {};
|
|
1527
1540
|
api.registerChannel({
|
|
1528
1541
|
id: "aamp",
|
|
1529
1542
|
meta: { label: "AAMP" },
|
|
@@ -1803,8 +1816,8 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
|
|
|
1803
1816
|
api.logger.error(`[AAMP] Sub-task result processing failed: ${err.message}`);
|
|
1804
1817
|
});
|
|
1805
1818
|
});
|
|
1806
|
-
aampClient.on("task.
|
|
1807
|
-
api.logger.info(`[AAMP] \u2190 task.
|
|
1819
|
+
aampClient.on("task.help_needed", (help) => {
|
|
1820
|
+
api.logger.info(`[AAMP] \u2190 task.help_needed ${help.taskId} question="${help.question}" from=${help.from}`);
|
|
1808
1821
|
const waiter = waitingDispatches.get(help.taskId);
|
|
1809
1822
|
if (waiter) {
|
|
1810
1823
|
waitingDispatches.delete(help.taskId);
|
|
@@ -2264,7 +2277,7 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
|
|
|
2264
2277
|
suggestedOptions: p.suggestedOptions ?? [],
|
|
2265
2278
|
inReplyTo: task.messageId || void 0
|
|
2266
2279
|
});
|
|
2267
|
-
api.logger.info(`[AAMP] \u2192 task.
|
|
2280
|
+
api.logger.info(`[AAMP] \u2192 task.help_needed ${task.taskId}`);
|
|
2268
2281
|
return {
|
|
2269
2282
|
content: [
|
|
2270
2283
|
{
|
|
@@ -2458,7 +2471,14 @@ Question: ${h.question}`,
|
|
|
2458
2471
|
return { content: [{ type: "text", text: "Error: email parameter is required" }] };
|
|
2459
2472
|
}
|
|
2460
2473
|
try {
|
|
2461
|
-
const
|
|
2474
|
+
const discoveryRes = await fetch(`${base}/.well-known/aamp`);
|
|
2475
|
+
if (!discoveryRes.ok)
|
|
2476
|
+
throw new Error(`HTTP ${discoveryRes.status}`);
|
|
2477
|
+
const discovery = await discoveryRes.json();
|
|
2478
|
+
const apiUrl = discovery.api?.url;
|
|
2479
|
+
if (!apiUrl)
|
|
2480
|
+
throw new Error("AAMP discovery did not return api.url");
|
|
2481
|
+
const res = await fetch(`${base}${apiUrl}?action=aamp.mailbox.check&email=${encodeURIComponent(email)}`);
|
|
2462
2482
|
if (!res.ok)
|
|
2463
2483
|
throw new Error(`HTTP ${res.status}`);
|
|
2464
2484
|
const data = await res.json();
|