Files
trade-message-center/apps/chrome-extension/src/onetalk/main-page/sync-push-decoder.ts
T

204 lines
6.1 KiB
TypeScript

// 解码 OneTalk 的同步推送数据
export type MessagePackValue =
| null
| boolean
| number
| bigint
| string
| Uint8Array
| MessagePackValue[]
| { [key: string]: MessagePackValue };
class MessagePackDecoder {
private readonly bytes: Uint8Array;
private readonly view: DataView;
private readonly textDecoder = new TextDecoder("utf-8", { fatal: true });
private offset = 0;
constructor(bytes: Uint8Array) {
this.bytes = bytes;
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
}
get done(): boolean {
return this.offset === this.bytes.byteLength;
}
private decodeArray(length: number): MessagePackValue[] {
return Array.from({ length }, () => this.decode());
}
private decodeMap(length: number): { [key: string]: MessagePackValue } {
const result: { [key: string]: MessagePackValue } = {};
for (let index = 0; index < length; index += 1) {
const key = this.decode();
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "bigint") {
throw new Error("Unsupported MessagePack map key");
}
result[String(key)] = this.decode();
}
return result;
}
private decodeString(length: number): string {
return this.textDecoder.decode(this.readBytes(length));
}
private readBytes(length: number): Uint8Array {
this.ensureAvailable(length);
const start = this.offset;
this.offset += length;
return this.bytes.subarray(start, this.offset);
}
private readUint8(): number {
this.ensureAvailable(1);
return this.view.getUint8(this.offset++);
}
private readInt8(): number {
this.ensureAvailable(1);
const value = this.view.getInt8(this.offset);
this.offset += 1;
return value;
}
private readUint16(): number {
this.ensureAvailable(2);
const value = this.view.getUint16(this.offset);
this.offset += 2;
return value;
}
private readInt16(): number {
this.ensureAvailable(2);
const value = this.view.getInt16(this.offset);
this.offset += 2;
return value;
}
private readUint32(): number {
this.ensureAvailable(4);
const value = this.view.getUint32(this.offset);
this.offset += 4;
return value;
}
private readInt32(): number {
this.ensureAvailable(4);
const value = this.view.getInt32(this.offset);
this.offset += 4;
return value;
}
private readBigUint64(): bigint {
this.ensureAvailable(8);
const value = this.view.getBigUint64(this.offset);
this.offset += 8;
return value;
}
private readBigInt64(): bigint {
this.ensureAvailable(8);
const value = this.view.getBigInt64(this.offset);
this.offset += 8;
return value;
}
private readFloat32(): number {
this.ensureAvailable(4);
const value = this.view.getFloat32(this.offset);
this.offset += 4;
return value;
}
private readFloat64(): number {
this.ensureAvailable(8);
const value = this.view.getFloat64(this.offset);
this.offset += 8;
return value;
}
private ensureAvailable(length: number): void {
if (length < 0 || this.offset + length > this.bytes.byteLength) {
throw new Error("Unexpected end of MessagePack data");
}
}
/** 解码一个完整的 MessagePack 值。 */
decode(): MessagePackValue {
const prefix = this.readUint8();
if (prefix <= 0x7f) return prefix;
if (prefix >= 0xe0) return prefix - 0x100;
if ((prefix & 0xf0) === 0x80) return this.decodeMap(prefix & 0x0f);
if ((prefix & 0xf0) === 0x90) return this.decodeArray(prefix & 0x0f);
if ((prefix & 0xe0) === 0xa0) return this.decodeString(prefix & 0x1f);
switch (prefix) {
case 0xc0:
return null;
case 0xc2:
return false;
case 0xc3:
return true;
case 0xc4:
return this.readBytes(this.readUint8());
case 0xc5:
return this.readBytes(this.readUint16());
case 0xc6:
return this.readBytes(this.readUint32());
case 0xca:
return this.readFloat32();
case 0xcb:
return this.readFloat64();
case 0xcc:
return this.readUint8();
case 0xcd:
return this.readUint16();
case 0xce:
return this.readUint32();
case 0xcf:
return this.readBigUint64();
case 0xd0:
return this.readInt8();
case 0xd1:
return this.readInt16();
case 0xd2:
return this.readInt32();
case 0xd3:
return this.readBigInt64();
case 0xd9:
return this.decodeString(this.readUint8());
case 0xda:
return this.decodeString(this.readUint16());
case 0xdb:
return this.decodeString(this.readUint32());
case 0xdc:
return this.decodeArray(this.readUint16());
case 0xdd:
return this.decodeArray(this.readUint32());
case 0xde:
return this.decodeMap(this.readUint16());
case 0xdf:
return this.decodeMap(this.readUint32());
default:
throw new Error(`Unsupported MessagePack prefix 0x${prefix.toString(16)}`);
}
}
}
/** 解码 Base64 编码的 OneTalk 同步推送数据。 */
export function decodeOneTalkSyncPushData(encoded: string): MessagePackValue {
const binary = atob(encoded);
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
const decoder = new MessagePackDecoder(bytes);
const value = decoder.decode();
if (!decoder.done) throw new Error("Unexpected trailing MessagePack bytes");
return value;
}