-
-
Notifications
You must be signed in to change notification settings - Fork 41
Open
Labels
bugSomething isn't workingSomething isn't working
Description
getData() fails for media in 1:1 E2EE chats — “Unsupported state or unable to authenticate data”
Title: getData() fails for media in 1:1 E2EE chats — “Unsupported state or unable to authenticate data”
Summary
message.getData() works for media from Official Accounts / non‑E2EE chats, but consistently fails for 1:1 E2EE chats with:
Unsupported state or unable to authenticate data
Text messages decrypt fine. The failure only occurs when fetching media bytes from 1:1 E2EE threads.
Environment
- @evex/linejs: latest (repro’d with
LINE_VERSION=18.6.2) - Node: 18.x (also reproduced on 20.x)
- OS: macOS (Apple Silicon)
- Auth:
loginWithAuthToken(Desktop profile) - Storage:
FileStorage(temp JSON file)
Expected vs Actual
- Expected:
message.getData()returns decrypted bytes for images in 1:1 E2EE chats (same as OA / non‑E2EE). - Actual: Throws
Unsupported state or unable to authenticate datain 1:1 E2EE chats. OA / non‑E2EE media works.
Minimal Repro (trimmed)
import { loginWithAuthToken } from "@evex/linejs";
import { FileStorage } from "@evex/linejs/storage";
const token = process.env.LINE_AUTH_TOKEN!; // not included here
const storage = new FileStorage("/tmp/linejs-dev.json");
const client = await loginWithAuthToken(token, { device: "DESKTOPWIN", storage });
const talk = (client as any).base?.talk ?? client;
const ensureMediaPrereqs = async (chatMid?: string) => {
// try to ensure keys/tokens flow
await (client as any)?.base?.e2ee?.syncE2EEKey?.().catch(() => {});
await (client as any)?.base?.talk?.e2ee?.syncE2EEKey?.().catch(() => {});
await (client as any)?.base?.talk?.acquireEncryptedAccessToken?.({ featureType: "OBS_GENERAL" }).catch(() => {});
if (chatMid && typeof (client as any)?.base?.talk?.determineMediaMessageFlow === "function") {
await (client as any).base.talk.determineMediaMessageFlow({ request: { chatMid } }).catch(() => {});
}
};
const resolveDecryptor = (c: any) => {
const fns = [
c?.e2ee?.decryptE2EEMessage?.bind(c?.e2ee),
c?.base?.e2ee?.decryptE2EEMessage?.bind(c?.base?.e2ee),
c?.base?.talk?.e2ee?.decryptE2EEMessage?.bind(c?.base?.talk?.e2ee),
typeof c?.base?.decryptE2EEMessage === "function" ? c.base.decryptE2EEMessage.bind(c.base) : null,
typeof c?.decryptE2EEMessage === "function" ? c.decryptE2EEMessage.bind(c) : null,
].filter(Boolean);
if (!fns.length) return null;
return async (m: any) => { for (const f of fns) { try { const out = await (f as any)(m); if (out) return out; } catch {} } return m; };
};
(client as any).on?.("message", async (m: any) => {
const chatMid = m?.chat?.chatMid ?? m?.from ?? null;
const rawCT = String(m?.contentType || "");
const isImage = /IMAGE/i.test(rawCT);
// try to decrypt the message envelope first
const dec = await (resolveDecryptor(client) || (async (x:any)=>x))(m);
if (isImage && typeof (dec as any)?.getData === "function") {
try {
// pre-warm keys/tokens/flow
await ensureMediaPrereqs(chatMid);
const blobOrBuf = await (dec as any).getData(); // <-- fails only in 1:1 E2EE chats
// normalize to Buffer if Blob
const buf = Buffer.isBuffer(blobOrBuf)
? blobOrBuf
: Buffer.from(new Uint8Array(await blobOrBuf.arrayBuffer?.() || new ArrayBuffer(0)));
console.log("image bytes", buf.byteLength);
} catch (e:any) {
console.warn("getData failed", e?.message || e);
}
}
});
await client.listen({ talk: true });Reproduction Steps
- Log in with
loginWithAuthToken(Desktop profile). - Start listening:
client.listen({ talk: true }). - From a 1:1 E2EE chat, send an image to the logged-in account.
- In the message handler, call
dec.getData()after decrypting the envelope. - Observe error below. Repeat the same from an OA / non‑E2EE chat and it succeeds.
Logs (sanitized)
Successful OA / non‑E2EE image:
attachment getData starting { rawCT: 'IMAGE', hasChunks: false, provider: 'decrypted(dec)' }
attachment getData success (first) { byteLength: 185488 }
attachment persist -> saved { kind: 'image', mime: 'image/jpeg' }
1:1 E2EE image (fails):
attachment getData starting { rawCT: 'IMAGE', hasChunks: true, provider: 'decrypted(dec)', chatMid: 'u****************' }
attachment getData failed (first attempt) { err: 'Unsupported state or unable to authenticate data', contentType: 'IMAGE', hasChunks: true }
media prereq priming -> start
media prereq priming -> done
attachment getData failed (retry) { err: 'Unsupported state or unable to authenticate data' }
What I’ve Tried
- Decrypting the message envelope first (
decryptE2EEMessage) and then callingdec.getData(). - Syncing E2EE keys on both
base.e2eeandbase.talk.e2ee. - Acquiring OBS encrypted access tokens via
base.talk.acquireEncryptedAccessToken({ featureType: "OBS_GENERAL" })(also triedbase.obsandbase.call). - Priming flow with
base.talk.determineMediaMessageFlow({ request: { chatMid } }). - Retrying
getData()after the above steps. - Using the raw message object’s
getData()vs the decrypted one’s.
None of the above change the failure for 1:1 E2EE media. Non‑E2EE media continues to work.
Hypotheses
- The E2EE media path for 1:1 chats may require an additional step (e.g., per‑message media key unwrap / MAC validation) that
getData()isn’t currently performing. - The
getData()implementation may be using the wrong E2EE context (session/device) for 1:1 media, even though envelope decryption succeeds. - The OBS token obtained via
acquireEncryptedAccessToken(OBS_GENERAL)may be insufficient for 1:1 E2EE media blobs, or needs tying to a per‑chat flow.
What Would Help
- Confirmation of the intended way to fetch & decrypt media bytes for 1:1 E2EE messages using
@evex/linejs. - Guidance on any additional API calls needed (e.g., dedicated media‑key API, different
featureType, or explicite2ee.decryptMedia(...)helper). - If this is a bug, a fix to have
message.getData()do the right thing for 1:1 E2EE media (same DX as OA / groups).
Notes
- The same code path successfully decrypts message bodies (text) and downloads OA images.
- Identifiers here are redacted; I can share detailed traces privately with maintainers if needed.
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working