feat(xiaomi-mimo): server-assisted desktop login, five account clusters, v2.6 models
Reproduce the MiMo Desktop login surface server-side so headless/Docker deployments can link a Xiaomi account without the Desktop client. The account session (passToken) is captured during the proxied login and stored per connection. - Five account clusters (cn/sgp/ams/ru/in): per-region mimo-server host and SSO sid, unknown region falls back to sgp - mimo-v2.6-pro/flash/pro-ultraspeed dual-route models: account-service route when desktop credentials exist, cloud API (sk- key) otherwise; drops obsolete mimo-x-*-preview ids - Desktop ServiceTokenManager 2-phase handshake (single serviceLogin with target sid, raw 64-bit nonce preserved), per-region session cache - reasoning_effort bridged to output_config.effort; i18n runtime now observes characterData mutations so React text rewrites get translated - Security hardening on the login proxy: session travels only in the httpOnly cookie (never in the URL), proxy branch requires dashboard auth, authorization/proxy-authorization never forwarded upstream, and upstream Set-Cookie is not replayed onto the app origin
This commit is contained in:
1 parent
6af26a9ee8
commit
910db749aa
16 files changed
+1687
-352
No files matched your search
+31
-16
@@ -56,12 +56,13 @@ export function onLocaleChange(callback) {
|
||||
|
||||
// Process text node
|
||||
function processTextNode(node) {
|
||||
if (!node.nodeValue || !node.nodeValue.trim()) return;
|
||||
|
||||
const current = node.nodeValue;
|
||||
if (!current || !current.trim()) return;
|
||||
|
||||
// Skip if parent is script, style, code, or structural elements
|
||||
const parent = node.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
|
||||
// Skip if parent or any ancestor has data-i18n-skip attribute
|
||||
let element = parent;
|
||||
while (element) {
|
||||
@@ -70,27 +71,33 @@ function processTextNode(node) {
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
|
||||
|
||||
const tagName = parent.tagName?.toLowerCase();
|
||||
|
||||
|
||||
// Skip elements that don't allow text nodes
|
||||
const skipTags = [
|
||||
"script", "style", "code", "pre",
|
||||
"colgroup", "table", "thead", "tbody", "tfoot", "tr",
|
||||
"select", "datalist", "optgroup"
|
||||
];
|
||||
|
||||
|
||||
if (skipTags.includes(tagName)) return;
|
||||
|
||||
// Store original text if not already stored
|
||||
if (!node._originalText) {
|
||||
node._originalText = node.nodeValue;
|
||||
|
||||
// React reuses text nodes and rewrites their value on re-render (a
|
||||
// characterData mutation, no childList event). When the current value is
|
||||
// neither our last translation nor the recorded original, it is fresh
|
||||
// source text — re-capture it as the new original before translating.
|
||||
const isOurTranslation = node._translated != null && current === node._translated;
|
||||
const isSameAsOriginal = current === node._originalText;
|
||||
if (!isOurTranslation && !isSameAsOriginal) {
|
||||
node._originalText = current;
|
||||
}
|
||||
|
||||
// Use original text for translation
|
||||
const original = node._originalText;
|
||||
const translated = translate(original);
|
||||
|
||||
if (node._originalText == null) node._originalText = current;
|
||||
|
||||
// Translate from the recorded original so locale switches stay idempotent
|
||||
const translated = translate(node._originalText);
|
||||
node._translated = translated;
|
||||
|
||||
// Only update if different to avoid unnecessary DOM mutations
|
||||
if (translated !== node.nodeValue) {
|
||||
node.nodeValue = translated;
|
||||
@@ -130,9 +137,16 @@ export async function initRuntimeI18n() {
|
||||
// Process existing DOM
|
||||
processElement(document.body);
|
||||
|
||||
// Watch for new nodes
|
||||
// Watch for new nodes AND in-place text rewrites. React reuses text nodes on
|
||||
// re-render (only nodeValue changes → a characterData mutation with no
|
||||
// childList event), so observing childList alone leaves later-updated labels
|
||||
// untranslated.
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === "characterData") {
|
||||
processTextNode(mutation.target);
|
||||
return;
|
||||
}
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
processElement(node);
|
||||
@@ -146,6 +160,7 @@ export async function initRuntimeI18n() {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user