@rubytech/create-maxy-code 0.1.484 → 0.1.485
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/package.json +1 -1
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +26 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +16 -8
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +25 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +23 -7
- package/payload/server/server.js +41 -33
package/package.json
CHANGED
|
@@ -144,3 +144,29 @@ describe('device binding', () => {
|
|
|
144
144
|
expect(text).toMatch(/accountId\s+TEXT NOT NULL DEFAULT ''/)
|
|
145
145
|
})
|
|
146
146
|
})
|
|
147
|
+
|
|
148
|
+
const portalJs = () => readFileSync(join(TEMPLATE, 'portal.js'), 'utf8')
|
|
149
|
+
const indexHtml = () => readFileSync(join(TEMPLATE, 'index.html'), 'utf8')
|
|
150
|
+
|
|
151
|
+
describe('login error surfacing and passcode reveal (Task 1872)', () => {
|
|
152
|
+
it('maps each auth failure to a distinct message, not one opaque string', () => {
|
|
153
|
+
const js = portalJs()
|
|
154
|
+
expect(js).not.toMatch(/That did not work\./)
|
|
155
|
+
expect(js).toContain('Too many attempts. Try again later.')
|
|
156
|
+
expect(js).toContain('Enter both your name and your passcode.')
|
|
157
|
+
expect(js).toContain('That name and passcode did not match.')
|
|
158
|
+
expect(js).toContain('Something went wrong. Please try again in a moment.')
|
|
159
|
+
expect(js).toContain('Could not reach the server. Check your connection and try again.')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('logs the status and error on the auth failure path', () => {
|
|
163
|
+
expect(portalJs()).toMatch(/console\.error\(\s*'\[data-portal\] auth failed'/)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('ships a reveal control bound to the passcode field', () => {
|
|
167
|
+
const html = indexHtml()
|
|
168
|
+
expect(html).toMatch(/id="dp-reveal"/)
|
|
169
|
+
expect(html).toMatch(/aria-controls="dp-passcode"/)
|
|
170
|
+
expect(portalJs()).toMatch(/dp-reveal/)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -18,14 +18,22 @@
|
|
|
18
18
|
<label class="dp-label" for="dp-owner">Your name</label>
|
|
19
19
|
<input class="dp-input" id="dp-owner" name="owner" autocomplete="username" required />
|
|
20
20
|
<label class="dp-label" for="dp-passcode">Passcode</label>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
<div class="dp-field">
|
|
22
|
+
<input
|
|
23
|
+
class="dp-input"
|
|
24
|
+
id="dp-passcode"
|
|
25
|
+
name="passcode"
|
|
26
|
+
type="password"
|
|
27
|
+
autocomplete="current-password"
|
|
28
|
+
required
|
|
29
|
+
/>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="dp-reveal"
|
|
33
|
+
id="dp-reveal"
|
|
34
|
+
aria-controls="dp-passcode"
|
|
35
|
+
>Show passcode</button>
|
|
36
|
+
</div>
|
|
29
37
|
<button class="dp-btn" type="submit">Sign in</button>
|
|
30
38
|
</form>
|
|
31
39
|
|
|
@@ -178,3 +178,28 @@ body {
|
|
|
178
178
|
cursor: pointer;
|
|
179
179
|
text-align: left;
|
|
180
180
|
}
|
|
181
|
+
|
|
182
|
+
/* Passcode field with its reveal control. The passcode is transcribed out of
|
|
183
|
+
band, so being able to check what was typed is the point of the toggle.
|
|
184
|
+
Reuses existing variables and declares no new :root value, which
|
|
185
|
+
portal-brand-css.mjs would drop. */
|
|
186
|
+
.dp-field {
|
|
187
|
+
display: flex;
|
|
188
|
+
gap: 0.5rem;
|
|
189
|
+
align-items: stretch;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.dp-field .dp-input {
|
|
193
|
+
flex: 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.dp-reveal {
|
|
197
|
+
border: 1px solid var(--dp-rule);
|
|
198
|
+
border-radius: 0.375rem;
|
|
199
|
+
background: transparent;
|
|
200
|
+
color: var(--dp-muted);
|
|
201
|
+
padding: 0.375rem 0.625rem;
|
|
202
|
+
font: inherit;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
}
|
|
@@ -192,6 +192,13 @@
|
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
function authFailureMessage(status) {
|
|
196
|
+
if (status === 429) return 'Too many attempts. Try again later.';
|
|
197
|
+
if (status === 400) return 'Enter both your name and your passcode.';
|
|
198
|
+
if (status === 401) return 'That name and passcode did not match.';
|
|
199
|
+
return 'Something went wrong. Please try again in a moment.';
|
|
200
|
+
}
|
|
201
|
+
|
|
195
202
|
loginEl.addEventListener('submit', function (e) {
|
|
196
203
|
e.preventDefault();
|
|
197
204
|
say('Signing in…');
|
|
@@ -209,12 +216,12 @@
|
|
|
209
216
|
});
|
|
210
217
|
})
|
|
211
218
|
.then(function (res) {
|
|
212
|
-
if (res.s === 429) {
|
|
213
|
-
say('Too many attempts. Try again later.');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
219
|
if (!res.d.ok) {
|
|
217
|
-
|
|
220
|
+
// The server returns the same denial for a wrong passcode and an
|
|
221
|
+
// unknown owner on purpose, so the message stays single and the
|
|
222
|
+
// console carries only the status class, never the reason.
|
|
223
|
+
console.error('[data-portal] auth failed', { status: res.s, error: res.d.error });
|
|
224
|
+
say(authFailureMessage(res.s));
|
|
218
225
|
return;
|
|
219
226
|
}
|
|
220
227
|
document.getElementById('dp-passcode').value = '';
|
|
@@ -223,11 +230,20 @@
|
|
|
223
230
|
say('');
|
|
224
231
|
return refresh();
|
|
225
232
|
})
|
|
226
|
-
.catch(function () {
|
|
227
|
-
|
|
233
|
+
.catch(function (err) {
|
|
234
|
+
console.error('[data-portal] auth failed', { status: 'network', error: err });
|
|
235
|
+
say('Could not reach the server. Check your connection and try again.');
|
|
228
236
|
});
|
|
229
237
|
});
|
|
230
238
|
|
|
239
|
+
var revealEl = document.getElementById('dp-reveal');
|
|
240
|
+
var passcodeInput = document.getElementById('dp-passcode');
|
|
241
|
+
revealEl.addEventListener('click', function () {
|
|
242
|
+
var revealed = passcodeInput.type === 'text';
|
|
243
|
+
passcodeInput.type = revealed ? 'password' : 'text';
|
|
244
|
+
revealEl.textContent = revealed ? 'Show passcode' : 'Hide passcode';
|
|
245
|
+
});
|
|
246
|
+
|
|
231
247
|
document.getElementById('dp-upload').addEventListener('submit', function (e) {
|
|
232
248
|
e.preventDefault();
|
|
233
249
|
var input = document.getElementById('dp-file');
|
package/payload/server/server.js
CHANGED
|
@@ -4107,7 +4107,8 @@ async function sendPaused(sock, to) {
|
|
|
4107
4107
|
} catch {
|
|
4108
4108
|
}
|
|
4109
4109
|
}
|
|
4110
|
-
async function sendReadReceipt(sock, chatJid, messageIds, participant) {
|
|
4110
|
+
async function sendReadReceipt(sock, chatJid, messageIds, participant, accountId) {
|
|
4111
|
+
const idTag = messageIds[0] ?? "?";
|
|
4111
4112
|
try {
|
|
4112
4113
|
await sock.readMessages([
|
|
4113
4114
|
...messageIds.map((id) => ({
|
|
@@ -4116,7 +4117,13 @@ async function sendReadReceipt(sock, chatJid, messageIds, participant) {
|
|
|
4116
4117
|
participant
|
|
4117
4118
|
}))
|
|
4118
4119
|
]);
|
|
4119
|
-
|
|
4120
|
+
console.error(
|
|
4121
|
+
`[whatsapp:receipt] op=ack account=${accountId ?? "?"} jid=${chatJid} id=${idTag} ok=true`
|
|
4122
|
+
);
|
|
4123
|
+
} catch (err) {
|
|
4124
|
+
console.error(
|
|
4125
|
+
`[whatsapp:receipt] op=ack account=${accountId ?? "?"} jid=${chatJid} id=${idTag} ok=false reason=${String(err).slice(0, 120)}`
|
|
4126
|
+
);
|
|
4120
4127
|
}
|
|
4121
4128
|
}
|
|
4122
4129
|
async function sendMediaMessage(sock, to, media, opts) {
|
|
@@ -6148,7 +6155,7 @@ function monitorInbound(conn) {
|
|
|
6148
6155
|
const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
|
|
6149
6156
|
const sendAppendReceipt = () => {
|
|
6150
6157
|
if (sendReceipts && msgKeyId) {
|
|
6151
|
-
sendReadReceipt(sock2, remoteJid, [msgKeyId], participant);
|
|
6158
|
+
sendReadReceipt(sock2, remoteJid, [msgKeyId], participant, conn.accountId);
|
|
6152
6159
|
}
|
|
6153
6160
|
};
|
|
6154
6161
|
const accepted = appendMediaQueue.schedule(async () => {
|
|
@@ -6200,7 +6207,8 @@ function monitorInbound(conn) {
|
|
|
6200
6207
|
sock,
|
|
6201
6208
|
remoteJid,
|
|
6202
6209
|
[msg.key.id],
|
|
6203
|
-
isGroupJid(remoteJid) ? msg.key.participant ?? void 0 : void 0
|
|
6210
|
+
isGroupJid(remoteJid) ? msg.key.participant ?? void 0 : void 0,
|
|
6211
|
+
conn.accountId
|
|
6204
6212
|
);
|
|
6205
6213
|
}
|
|
6206
6214
|
const decision = evaluateSelfChatCommand({
|
|
@@ -6361,39 +6369,12 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6361
6369
|
console.error(`${TAG17} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
6362
6370
|
return;
|
|
6363
6371
|
}
|
|
6364
|
-
let mediaResult;
|
|
6365
|
-
if (extracted.mediaType && conn.sock) {
|
|
6366
|
-
const maxMb = whatsAppConfig.mediaMaxMb ?? 50;
|
|
6367
|
-
mediaResult = await downloadInboundMedia(msg, conn.sock, {
|
|
6368
|
-
maxBytes: maxMb * 1024 * 1024
|
|
6369
|
-
});
|
|
6370
|
-
if (!mediaResult) {
|
|
6371
|
-
console.error(`${TAG17} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
6372
|
-
}
|
|
6373
|
-
}
|
|
6374
6372
|
const isGroup = isGroupJid(remoteJid);
|
|
6375
6373
|
const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
|
|
6376
6374
|
const senderPhone = await resolveJidToE164(senderJid, conn.lidMapping) ?? senderJid;
|
|
6377
6375
|
const lidCapture = lidCaptureFor(senderJid, senderPhone);
|
|
6378
6376
|
if (lidCapture) recordLidMapping(lidCapture.numericLid, lidCapture.e164);
|
|
6379
6377
|
const selfPhone = conn.selfPhone ?? "";
|
|
6380
|
-
if (extracted.mediaType === "audio" && mediaResult?.path) {
|
|
6381
|
-
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
6382
|
-
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
6383
|
-
let resolvePending;
|
|
6384
|
-
const sttPending = new Promise((resolve39) => {
|
|
6385
|
-
resolvePending = resolve39;
|
|
6386
|
-
});
|
|
6387
|
-
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
6388
|
-
try {
|
|
6389
|
-
const sttResult = await transcribe(mediaResult.path, mediaResult.mimetype);
|
|
6390
|
-
if (sttResult?.text) {
|
|
6391
|
-
extracted.text = sttResult.text;
|
|
6392
|
-
}
|
|
6393
|
-
} finally {
|
|
6394
|
-
resolvePending();
|
|
6395
|
-
}
|
|
6396
|
-
}
|
|
6397
6378
|
let accessResult;
|
|
6398
6379
|
if (isGroup) {
|
|
6399
6380
|
accessResult = checkGroupAccess({ groupJid: remoteJid });
|
|
@@ -6409,7 +6390,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6409
6390
|
});
|
|
6410
6391
|
}
|
|
6411
6392
|
console.error(
|
|
6412
|
-
`${TAG17} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (
|
|
6393
|
+
`${TAG17} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
6413
6394
|
);
|
|
6414
6395
|
if (!accessResult.allowed && accessResult.reason === "account-manager-unresolved") {
|
|
6415
6396
|
console.error(
|
|
@@ -6431,7 +6412,34 @@ async function handleInboundMessage(conn, msg) {
|
|
|
6431
6412
|
}
|
|
6432
6413
|
const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
|
|
6433
6414
|
if (sendReceipts && msg.key.id) {
|
|
6434
|
-
sendReadReceipt(conn.sock, remoteJid, [msg.key.id], isGroup ? senderJid : void 0);
|
|
6415
|
+
sendReadReceipt(conn.sock, remoteJid, [msg.key.id], isGroup ? senderJid : void 0, conn.accountId);
|
|
6416
|
+
}
|
|
6417
|
+
let mediaResult;
|
|
6418
|
+
if (extracted.mediaType && conn.sock) {
|
|
6419
|
+
const maxMb = whatsAppConfig.mediaMaxMb ?? 50;
|
|
6420
|
+
mediaResult = await downloadInboundMedia(msg, conn.sock, {
|
|
6421
|
+
maxBytes: maxMb * 1024 * 1024
|
|
6422
|
+
});
|
|
6423
|
+
if (!mediaResult) {
|
|
6424
|
+
console.error(`${TAG17} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (extracted.mediaType === "audio" && mediaResult?.path) {
|
|
6428
|
+
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
6429
|
+
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
6430
|
+
let resolvePending;
|
|
6431
|
+
const sttPending = new Promise((resolve39) => {
|
|
6432
|
+
resolvePending = resolve39;
|
|
6433
|
+
});
|
|
6434
|
+
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
6435
|
+
try {
|
|
6436
|
+
const sttResult = await transcribe(mediaResult.path, mediaResult.mimetype);
|
|
6437
|
+
if (sttResult?.text) {
|
|
6438
|
+
extracted.text = sttResult.text;
|
|
6439
|
+
}
|
|
6440
|
+
} finally {
|
|
6441
|
+
resolvePending();
|
|
6442
|
+
}
|
|
6435
6443
|
}
|
|
6436
6444
|
const reply = async (text) => {
|
|
6437
6445
|
if (isGroupJid(remoteJid)) {
|