@magpie-community/opencode-mimo-auth 0.1.2 → 0.1.5
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 +5 -4
- package/index.mjs +71 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,10 +29,11 @@ Chat completions go to `https://mimo-server-<region>.xiaomimimo.com/api/route/ch
|
|
|
29
29
|
|
|
30
30
|
`mimo-auto`, the app's default model, is requested as `mimo-pro`.
|
|
31
31
|
|
|
32
|
-
The session is renewed with the passToken a day after it was issued.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
sign in
|
|
32
|
+
The session is renewed with the passToken a day after it was issued. A model
|
|
33
|
+
request the server turns away (a 401) is answered as the server answered it,
|
|
34
|
+
as magpie's built-in MiMo account did; reading usage renews the session once
|
|
35
|
+
when the server turns it away. If the passToken no longer works, sign in
|
|
36
|
+
again.
|
|
36
37
|
|
|
37
38
|
## Where the sign-in is kept
|
|
38
39
|
|
package/index.mjs
CHANGED
|
@@ -117,7 +117,11 @@ async function session(creds, base) {
|
|
|
117
117
|
const headers = appHeaders()
|
|
118
118
|
const cookie = jar.header(url)
|
|
119
119
|
if (cookie) headers.Cookie = cookie
|
|
120
|
-
|
|
120
|
+
try {
|
|
121
|
+
res = await fetch(url, { headers, redirect: "manual", signal })
|
|
122
|
+
} catch (e) {
|
|
123
|
+
throw new Error("Xiaomi MiMo sign-in: " + (e?.message ?? e))
|
|
124
|
+
}
|
|
121
125
|
jar.take(res, url)
|
|
122
126
|
const loc = res.headers.get("location")
|
|
123
127
|
if (res.status < 300 || res.status >= 400 || !loc) break
|
|
@@ -184,6 +188,15 @@ function fromAuth(auth) {
|
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
// said is an answer with magpie's X-Magpie-Sign-In, which tells what it
|
|
192
|
+
// means for the account's sign-in whatever its status: "expired" marks it
|
|
193
|
+
// lapsed, "kept" leaves it be. magpie takes it off before the agent sees it.
|
|
194
|
+
function said(res, v) {
|
|
195
|
+
const h = new Headers(res.headers)
|
|
196
|
+
h.set("X-Magpie-Sign-In", v)
|
|
197
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h })
|
|
198
|
+
}
|
|
199
|
+
|
|
187
200
|
// ---- the sign-in ------------------------------------------------------------
|
|
188
201
|
|
|
189
202
|
// serviceOf is the sid and callback the MiMo server sends a browser with
|
|
@@ -324,7 +337,9 @@ function serverTime(v) {
|
|
|
324
337
|
// ---- the plugin -------------------------------------------------------------
|
|
325
338
|
|
|
326
339
|
export const MimoAuthPlugin = async ({ client }) => {
|
|
327
|
-
|
|
340
|
+
// renewals under way, one to an account: two accounts signing on at
|
|
341
|
+
// once each keep their own session
|
|
342
|
+
const renewing = new Map()
|
|
328
343
|
|
|
329
344
|
const save = async (auth) => {
|
|
330
345
|
try {
|
|
@@ -334,41 +349,51 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
334
349
|
|
|
335
350
|
// fresh is the account with a live session, signed on again when it is
|
|
336
351
|
// older than the app lets one get, or when force is set (the server
|
|
337
|
-
// turned the last one away)
|
|
352
|
+
// turned the last one away); renewed when it was signed on again, which
|
|
353
|
+
// took the built-in's lapse mark off
|
|
338
354
|
const fresh = async (getAuth, force) => {
|
|
339
355
|
const a = fromAuth(await getAuth())
|
|
340
356
|
if (!a) throw new Error("Xiaomi MiMo: not signed in")
|
|
341
357
|
if (!force && Object.keys(a.cookies).length && Date.now() < a.expires) return a
|
|
342
|
-
|
|
343
|
-
|
|
358
|
+
const who = String(a.creds.userId ?? "")
|
|
359
|
+
let r = renewing.get(who)
|
|
360
|
+
if (!r) {
|
|
361
|
+
r = (async () => {
|
|
344
362
|
try {
|
|
345
363
|
const s = await session(a.creds, a.creds.base)
|
|
346
364
|
const issued = Date.now()
|
|
347
365
|
await save(toAuth(a.creds, s.cookies, issued))
|
|
348
|
-
return { creds: a.creds, cookies: s.cookies, expires: issued + RENEW_AFTER }
|
|
366
|
+
return { creds: a.creds, cookies: s.cookies, expires: issued + RENEW_AFTER, renewed: true }
|
|
349
367
|
} catch (e) {
|
|
350
368
|
// a hiccup: the session in hand may still do
|
|
351
369
|
if (!(e instanceof Lapsed) && !force && Object.keys(a.cookies).length) return a
|
|
352
370
|
throw e
|
|
353
371
|
}
|
|
354
372
|
})().finally(() => {
|
|
355
|
-
renewing
|
|
373
|
+
renewing.delete(who)
|
|
356
374
|
})
|
|
375
|
+
renewing.set(who, r)
|
|
357
376
|
}
|
|
358
|
-
return
|
|
377
|
+
return r
|
|
359
378
|
}
|
|
360
379
|
|
|
361
380
|
// page asks the MiMo server for one of the account's pages, signing on
|
|
362
|
-
// again once when it is turned away, and gives its data.
|
|
363
|
-
|
|
381
|
+
// again once when it is turned away, and gives its data. read.signIn is
|
|
382
|
+
// what that did to the built-in's lapse mark: signed on again took it
|
|
383
|
+
// off, a sign-in Xiaomi no longer takes (mimoLapse) put it on.
|
|
384
|
+
const page = async (getAuth, path, read) => {
|
|
364
385
|
for (let t = 0; ; t++) {
|
|
365
386
|
let s
|
|
366
387
|
try {
|
|
367
388
|
s = await fresh(getAuth, t > 0)
|
|
368
389
|
} catch (e) {
|
|
369
|
-
if (e instanceof Lapsed)
|
|
390
|
+
if (e instanceof Lapsed) {
|
|
391
|
+
read.signIn = "expired"
|
|
392
|
+
throw new SignInGone(`${fromAuth(await getAuth())?.creds.userId}: the Xiaomi MiMo sign-in has expired — sign in again`)
|
|
393
|
+
}
|
|
370
394
|
throw e
|
|
371
395
|
}
|
|
396
|
+
if (s.renewed) read.signIn = "renewed"
|
|
372
397
|
const res = await fetch(s.creds.base.replace(/\/+$/, "") + path, {
|
|
373
398
|
headers: { Cookie: cookieHeader(s.cookies), ...appHeaders() },
|
|
374
399
|
signal: AbortSignal.timeout(20_000),
|
|
@@ -382,6 +407,7 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
382
407
|
if (res.status === 401 || (res.status === 200 && bad)) {
|
|
383
408
|
// a session gone stale, or a redirect to Xiaomi's sign-in
|
|
384
409
|
if (t === 0) continue
|
|
410
|
+
read.signIn = "expired"
|
|
385
411
|
throw new SignInGone(`${s.creds.userId}: the Xiaomi MiMo sign-in has expired — sign in again`)
|
|
386
412
|
}
|
|
387
413
|
if (res.status !== 200 || bad) throw new Error(`Xiaomi MiMo ${path}: ${vendorError(text, statusLine(res.status))}`)
|
|
@@ -390,13 +416,16 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
390
416
|
}
|
|
391
417
|
}
|
|
392
418
|
|
|
393
|
-
// usage is the plan and the week's allowance, magpie's own hook
|
|
419
|
+
// usage is the plan and the week's allowance, magpie's own hook; signIn
|
|
420
|
+
// is the lapse mark as the built-in's reads left it, a clean read with
|
|
421
|
+
// no signing on again leaving it be
|
|
394
422
|
const usage = async (getAuth) => {
|
|
423
|
+
const read = { signIn: "kept" }
|
|
395
424
|
let self
|
|
396
425
|
try {
|
|
397
|
-
self = await page(getAuth, "/user/xiaomi/subscription/self")
|
|
426
|
+
self = await page(getAuth, "/user/xiaomi/subscription/self", read)
|
|
398
427
|
} catch (e) {
|
|
399
|
-
return { error: e?.message ?? String(e) }
|
|
428
|
+
return { error: e?.message ?? String(e), signIn: read.signIn }
|
|
400
429
|
}
|
|
401
430
|
const c = self?.current
|
|
402
431
|
const out = { plan: "Free" }
|
|
@@ -410,12 +439,13 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
410
439
|
}
|
|
411
440
|
let use
|
|
412
441
|
try {
|
|
413
|
-
use = await page(getAuth, "/user/usage")
|
|
442
|
+
use = await page(getAuth, "/user/usage", read)
|
|
414
443
|
} catch (e) {
|
|
415
444
|
if (e instanceof SignInGone) out.error = e.message
|
|
416
445
|
// the plan alone, when the week's allowance can't be read
|
|
417
|
-
return out
|
|
446
|
+
return { ...out, signIn: read.signIn }
|
|
418
447
|
}
|
|
448
|
+
out.signIn = read.signIn
|
|
419
449
|
const percent = use?.percent
|
|
420
450
|
const reset = use?.resetDate
|
|
421
451
|
// no plan: the free offer shows no allowance
|
|
@@ -452,6 +482,9 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
452
482
|
const req = input instanceof Request ? input : null
|
|
453
483
|
const url = req ? req.url : String(input)
|
|
454
484
|
let body = init.body ?? (req ? await req.text() : undefined)
|
|
485
|
+
// a body handed over as bytes is read as the string it is
|
|
486
|
+
if (body instanceof ArrayBuffer) body = new TextDecoder().decode(body)
|
|
487
|
+
else if (ArrayBuffer.isView(body)) body = new TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength))
|
|
455
488
|
// mimo-auto, the app's default, is asked as the model it stands for
|
|
456
489
|
if (typeof body === "string" && body.includes('"mimo-auto"')) {
|
|
457
490
|
try {
|
|
@@ -459,22 +492,29 @@ export const MimoAuthPlugin = async ({ client }) => {
|
|
|
459
492
|
if (j?.model === "mimo-auto") body = JSON.stringify({ ...j, model: "mimo-pro" })
|
|
460
493
|
} catch {}
|
|
461
494
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
495
|
+
let s
|
|
496
|
+
try {
|
|
497
|
+
s = await fresh(getAuth, false)
|
|
498
|
+
} catch (e) {
|
|
499
|
+
// the passToken no longer signs the account on: the built-in
|
|
500
|
+
// failed the request (magpie's 502) in mimoLapse's words and
|
|
501
|
+
// marked the account lapsed
|
|
502
|
+
if (!(e instanceof Lapsed)) throw e
|
|
503
|
+
const who = fromAuth(await getAuth())?.creds.userId ?? a.creds.userId
|
|
504
|
+
const message = `${who}: the Xiaomi MiMo sign-in has expired — sign in again`
|
|
505
|
+
return said(new Response(JSON.stringify({ error: { message, type: "api_error", code: null } }), { status: 502, headers: { "content-type": "application/json" } }), "expired")
|
|
471
506
|
}
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
507
|
+
const headers = new Headers(init.headers ?? req?.headers)
|
|
508
|
+
headers.delete("authorization")
|
|
509
|
+
headers.set("Cookie", cookieHeader(s.cookies))
|
|
510
|
+
headers.set("X-Mimo-Source", SOURCE)
|
|
511
|
+
headers.set("User-Agent", UA)
|
|
512
|
+
headers.set("X-Client-Version", APP_VERSION)
|
|
513
|
+
// the server's answer goes through as it is, a 401 too, as the
|
|
514
|
+
// built-in's did: it took the lapse off only when it signed on
|
|
515
|
+
// again, whatever the server then answered
|
|
516
|
+
const res = await fetch(url, { ...init, method: init.method ?? req?.method ?? "POST", headers, body })
|
|
517
|
+
return said(res, s.renewed ? "renewed" : "kept")
|
|
478
518
|
},
|
|
479
519
|
}
|
|
480
520
|
},
|
package/package.json
CHANGED