diff --git a/node_modules/zca-js/dist/apis.d.ts b/node_modules/zca-js/dist/apis.d.ts
index 0d0898c..71cc111 100644
--- a/node_modules/zca-js/dist/apis.d.ts
+++ b/node_modules/zca-js/dist/apis.d.ts
@@ -84,6 +84,7 @@ import { getSettingsFactory } from "./apis/getSettings.js";
 import { getStickerCategoryDetailFactory } from "./apis/getStickerCategoryDetail.js";
 import { getStickersFactory } from "./apis/getStickers.js";
 import { getStickersDetailFactory } from "./apis/getStickersDetail.js";
+import { getSyncDataFactory } from "./apis/getSyncData.js";
 import { getUnreadMarkFactory } from "./apis/getUnreadMark.js";
 import { getUserInfoFactory } from "./apis/getUserInfo.js";
 import { inviteUserToGroupsFactory } from "./apis/inviteUserToGroups.js";
@@ -93,6 +94,7 @@ import { keepAliveFactory } from "./apis/keepAlive.js";
 import { lastOnlineFactory } from "./apis/lastOnline.js";
 import { leaveGroupFactory } from "./apis/leaveGroup.js";
 import { lockPollFactory } from "./apis/lockPoll.js";
+import { pullMobileFactory } from "./apis/pullMobile.js";
 import { parseLinkFactory } from "./apis/parseLink.js";
 import { rejectFriendRequestFactory } from "./apis/rejectFriendRequest.js";
 import { removeFriendFactory } from "./apis/removeFriend.js";
@@ -234,6 +236,7 @@ export declare class API {
     getStickerCategoryDetail: ReturnType<typeof getStickerCategoryDetailFactory>;
     getStickers: ReturnType<typeof getStickersFactory>;
     getStickersDetail: ReturnType<typeof getStickersDetailFactory>;
+    getSyncData: ReturnType<typeof getSyncDataFactory>;
     getUnreadMark: ReturnType<typeof getUnreadMarkFactory>;
     getUserInfo: ReturnType<typeof getUserInfoFactory>;
     inviteUserToGroups: ReturnType<typeof inviteUserToGroupsFactory>;
@@ -243,6 +246,7 @@ export declare class API {
     lastOnline: ReturnType<typeof lastOnlineFactory>;
     leaveGroup: ReturnType<typeof leaveGroupFactory>;
     lockPoll: ReturnType<typeof lockPollFactory>;
+    pullMobile: ReturnType<typeof pullMobileFactory>;
     parseLink: ReturnType<typeof parseLinkFactory>;
     rejectFriendRequest: ReturnType<typeof rejectFriendRequestFactory>;
     removeFriend: ReturnType<typeof removeFriendFactory>;
diff --git a/node_modules/zca-js/dist/apis.js b/node_modules/zca-js/dist/apis.js
index 448a548..255df58 100644
--- a/node_modules/zca-js/dist/apis.js
+++ b/node_modules/zca-js/dist/apis.js
@@ -84,6 +84,7 @@ import { getSettingsFactory } from "./apis/getSettings.js";
 import { getStickerCategoryDetailFactory } from "./apis/getStickerCategoryDetail.js";
 import { getStickersFactory } from "./apis/getStickers.js";
 import { getStickersDetailFactory } from "./apis/getStickersDetail.js";
+import { getSyncDataFactory } from "./apis/getSyncData.js";
 import { getUnreadMarkFactory } from "./apis/getUnreadMark.js";
 import { getUserInfoFactory } from "./apis/getUserInfo.js";
 import { inviteUserToGroupsFactory } from "./apis/inviteUserToGroups.js";
@@ -93,6 +94,7 @@ import { keepAliveFactory } from "./apis/keepAlive.js";
 import { lastOnlineFactory } from "./apis/lastOnline.js";
 import { leaveGroupFactory } from "./apis/leaveGroup.js";
 import { lockPollFactory } from "./apis/lockPoll.js";
+import { pullMobileFactory } from "./apis/pullMobile.js";
 import { parseLinkFactory } from "./apis/parseLink.js";
 import { rejectFriendRequestFactory } from "./apis/rejectFriendRequest.js";
 import { removeFriendFactory } from "./apis/removeFriend.js";
@@ -234,6 +236,7 @@ export class API {
         this.getStickerCategoryDetail = getStickerCategoryDetailFactory(ctx, this);
         this.getStickers = getStickersFactory(ctx, this);
         this.getStickersDetail = getStickersDetailFactory(ctx, this);
+        this.getSyncData = getSyncDataFactory(ctx, this);
         this.getUnreadMark = getUnreadMarkFactory(ctx, this);
         this.getUserInfo = getUserInfoFactory(ctx, this);
         this.inviteUserToGroups = inviteUserToGroupsFactory(ctx, this);
@@ -243,6 +246,7 @@ export class API {
         this.lastOnline = lastOnlineFactory(ctx, this);
         this.leaveGroup = leaveGroupFactory(ctx, this);
         this.lockPoll = lockPollFactory(ctx, this);
+        this.pullMobile = pullMobileFactory(ctx, this);
         this.parseLink = parseLinkFactory(ctx, this);
         this.rejectFriendRequest = rejectFriendRequestFactory(ctx, this);
         this.removeFriend = removeFriendFactory(ctx, this);
diff --git a/node_modules/zca-js/dist/apis/getSyncData.d.ts b/node_modules/zca-js/dist/apis/getSyncData.d.ts
new file mode 100644
index 0000000..6b395af
--- /dev/null
+++ b/node_modules/zca-js/dist/apis/getSyncData.d.ts
@@ -0,0 +1,13 @@
+import type { SyncDataResponse } from "../models/index.js";
+export type GetSyncDataParams = {
+    url: string;
+    encrypted_key: string;
+    file_name: string;
+    file_size: number;
+    checksum_code: string;
+    from_seq_id: number;
+    is_full_transfer: number;
+    returnStream?: boolean;
+    savePath?: string;
+};
+export declare const getSyncDataFactory: (ctx: import("../context.js").ContextBase, api: import("../apis.js").API) => (params: GetSyncDataParams) => Promise<SyncDataResponse>;
diff --git a/node_modules/zca-js/dist/apis/getSyncData.js b/node_modules/zca-js/dist/apis/getSyncData.js
new file mode 100644
index 0000000..d27c1ef
--- /dev/null
+++ b/node_modules/zca-js/dist/apis/getSyncData.js
@@ -0,0 +1,158 @@
+import { ZaloApiError } from "../Errors/ZaloApiError.js";
+import { apiFactory } from "../utils.js";
+import toughCookie from "tough-cookie";
+import fs from "node:fs";
+import http from "node:http";
+import https from "node:https";
+import path from "node:path";
+export const getSyncDataFactory = apiFactory()((api, ctx) => {
+    /**
+     * Downloads a raw file stream from a URL using Node http/https with proper authentication.
+     * Do not use fetch here: undici may transparently transform compressed responses, while
+     * Zalo's checksum/signature is calculated over the exact encrypted bytes.
+     *
+     * @param url The URL to download from
+     * @returns Promise<object> A small Response-like object with body containing a raw stream
+     * @throws {ZaloApiError} If download fails
+     */
+    async function downloadFileStream(url) {
+        if (!ctx.cookie)
+            ctx.cookie = new toughCookie.CookieJar();
+        const cookie = ctx.cookie;
+        const manualHeader = {
+            "Cache-Control": "no-store, must-revalidate",
+            Pragma: "no-cache",
+            "Accept-Encoding": "identity",
+            "Accept-Language": "en-US",
+            "User-Agent": ctx.userAgent || "",
+            cookie: `${await cookie.getCookieString("https://chat.zalo.me/")}`,
+            Expires: "0",
+            "Sec-Fetch-Dest": "empty",
+            "Sec-Fetch-Mode": "no-cors",
+            "Sec-Fetch-Site": "none",
+            Connection: "keep-alive",
+        };
+        const response = await rawGet(url, manualHeader);
+        if (!response.ok) {
+            throw new ZaloApiError(`Failed to download file: HTTP ${response.status}`);
+        }
+        return response;
+    }
+    /**
+     * Downloads a file from a URL using fetch with proper authentication
+     * Converts the Response body (ReadableStream) to Buffer
+     *
+     * @param url The URL to download from
+     * @returns Promise<Buffer> The downloaded file as a buffer
+     * @throws {ZaloApiError} If download fails
+     */
+    async function downloadFile(url) {
+        const response = await downloadFileStream(url);
+        if (!response.body) {
+            throw new ZaloApiError("Response body is null");
+        }
+        const chunks = [];
+        for await (const chunk of response.body) {
+            chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
+        }
+        return Buffer.concat(chunks);
+    }
+    /**
+     * Download sync data from Zalo's mobile sync and save as encrypted .db.crypt file
+     *
+     * @param params GetSyncDataParams containing URL, encrypted key, and metadata
+     *
+     * @throws {ZaloApiError}
+     *
+     * @returns Promise<SyncDataResponse> The saved file path or stream
+     */
+    return async function getSyncData({ url, encrypted_key, file_name, file_size, checksum_code, from_seq_id, is_full_transfer, returnStream = false, savePath = "./", }) {
+        try {
+            if (returnStream) {
+                const response = await downloadFileStream(url);
+                const syncDataStream = {
+                    stream: response.body,
+                    metadata: {
+                        file_name,
+                        file_size,
+                        checksum_code,
+                        from_seq_id,
+                        is_full_transfer,
+                        encrypted_key,
+                    },
+                    response: {
+                        status: response.status,
+                        statusText: response.statusText,
+                        url: response.url,
+                        contentType: response.headers.get("content-type"),
+                        contentLength: response.headers.get("content-length"),
+                    },
+                };
+                return {
+                    success: true,
+                    stream: syncDataStream,
+                };
+            }
+            const encryptedFileData = await downloadFile(url);
+            const fileName = file_name.endsWith(".db.crypt") ? file_name : `${file_name}.db.crypt`;
+            const fullPath = path.join(savePath, fileName);
+            const dir = path.dirname(fullPath);
+            if (!fs.existsSync(dir)) {
+                fs.mkdirSync(dir, { recursive: true });
+            }
+            fs.writeFileSync(fullPath, encryptedFileData);
+            const syncData = {
+                file_path: fullPath,
+                encrypted_key,
+                metadata: {
+                    file_name: fileName,
+                    file_size,
+                    checksum_code,
+                    from_seq_id,
+                    is_full_transfer,
+                },
+            };
+            return {
+                success: true,
+                data: syncData,
+            };
+        }
+        catch (error) {
+            return {
+                success: false,
+                error: error instanceof Error ? error.message : "Unknown error occurred",
+            };
+        }
+    };
+});
+function rawGet(url, headers, redirectsLeft = 5) {
+    return new Promise((resolve, reject) => {
+        const lib = url.startsWith("https:") ? https : http;
+        const req = lib.get(url, { headers }, (res) => {
+            const status = res.statusCode || 0;
+            const location = res.headers.location;
+            if ([301, 302, 303, 307, 308].includes(status) && location && redirectsLeft > 0) {
+                res.resume();
+                resolve(rawGet(new URL(location, url).toString(), headers, redirectsLeft - 1));
+                return;
+            }
+            resolve({
+                ok: status >= 200 && status < 300,
+                status,
+                statusText: res.statusMessage || "",
+                url,
+                headers: {
+                    get(name) {
+                        const value = res.headers[name.toLowerCase()];
+                        return Array.isArray(value) ? value.join(", ") : value ?? null;
+                    },
+                },
+                body: res,
+            });
+        });
+        req.on("error", reject);
+        req.setTimeout(30000, () => {
+            req.destroy(new ZaloApiError("Download timed out"));
+        });
+    });
+}
diff --git a/node_modules/zca-js/dist/apis/listen.d.ts b/node_modules/zca-js/dist/apis/listen.d.ts
index 79c9eb7..162b40d 100644
--- a/node_modules/zca-js/dist/apis/listen.d.ts
+++ b/node_modules/zca-js/dist/apis/listen.d.ts
@@ -6,6 +6,7 @@ import { Reaction, Undo, ThreadType } from "../models/index.js";
 import type { ContextSession } from "../context.js";
 import { type SeenMessage } from "../models/SeenMessage.js";
 import { type DeliveredMessage } from "../models/DeliveredMessage.js";
+import { type SyncEvent } from "../models/SyncEvent.js";
 type UploadEventData = {
     fileUrl: string;
     fileId: string;
@@ -42,6 +43,7 @@ interface ListenerEvents {
     friend_event: [data: FriendEvent];
     group_event: [data: GroupEvent];
     cipher_key: [key: string];
+    sync_event: [data: SyncEvent];
 }
 export declare class Listener extends EventEmitter<ListenerEvents> {
     private ctx;
diff --git a/node_modules/zca-js/dist/apis/listen.js b/node_modules/zca-js/dist/apis/listen.js
index ce5d95e..bdcec02 100644
--- a/node_modules/zca-js/dist/apis/listen.js
+++ b/node_modules/zca-js/dist/apis/listen.js
@@ -7,6 +7,7 @@ import { decodeEventData, getFriendEventType, getGroupEventType, hasOwn, logger,
 import { ZaloApiError } from "../Errors/ZaloApiError.js";
 import { GroupSeenMessage, UserSeenMessage } from "../models/SeenMessage.js";
 import { UserDeliveredMessage, GroupDeliveredMessage } from "../models/DeliveredMessage.js";
+import { initializeSyncEvent } from "../models/SyncEvent.js";
 export var CloseReason;
 (function (CloseReason) {
     CloseReason[CloseReason["ManualClosure"] = 1000] = "ManualClosure";
@@ -154,6 +155,46 @@ export class Listener extends EventEmitter {
                 if (decodedData.length == 0)
                     return;
                 const parsed = JSON.parse(decodedData);
+                // [toolchat] Ghi log mọi khung WebSocket mà thư viện KHÔNG giải mã.
+                // zca-js chỉ xử lý một tập cmd cố định và im lặng bỏ qua phần còn lại, nên
+                // dữ liệu "đồng bộ tin nhắn" điện thoại đẩy lên (nếu có) sẽ biến mất không
+                // dấu vết. Bật bằng ZALO_WS_DEBUG=1; mặc định tắt để không phình log.
+                if (process.env.ZALO_WS_DEBUG === "1") {
+                    const KNOWN = [1, 501, 502, 510, 511, 521, 522, 601, 602, 610, 611, 612, 3000];
+                    if (!KNOWN.includes(cmd)) {
+                        // Gần như mọi khung đều mã hoá (`encrypt: 2`), in nguyên bản chỉ ra
+                        // base64 vô nghĩa. Giải mã bằng đúng hàm thư viện dùng cho cmd đã biết.
+                        let decoded = null;
+                        try {
+                            decoded = (await decodeEventData(parsed, this.cipherKey)).data;
+                        }
+                        catch (e) {
+                            decoded = { _loi_giai_ma: String(e && e.message) };
+                        }
+                        console.log(JSON.stringify({
+                            tag: "ZALO_WS_UNKNOWN",
+                            uid: this.ctx.uid,
+                            version, cmd, subCmd,
+                            bytes: dataToDecode.length,
+                            keys: decoded && typeof decoded === "object" ? Object.keys(decoded) : null,
+                            // Đếm phần tử của mọi mảng con: một lô đồng bộ lịch sử sẽ lộ ra
+                            // ở đây bằng con số lớn, kể cả khi phần mẫu bị cắt.
+                            counts: decoded && typeof decoded === "object"
+                                ? Object.fromEntries(Object.entries(decoded)
+                                    .filter(([, v]) => Array.isArray(v))
+                                    .map(([k, v]) => [k, v.length]))
+                                : null,
+                            // Cắt ngắn: một lô đồng bộ có thể vài MB, in hết là ngập log.
+                            sample: JSON.stringify(decoded).slice(0, 700),
+                        }));
+                    }
+                    else {
+                        console.log(JSON.stringify({
+                            tag: "ZALO_WS_KNOWN", uid: this.ctx.uid, version, cmd, subCmd,
+                            bytes: dataToDecode.length,
+                        }));
+                    }
+                }
                 if (version == 1 && cmd == 1 && subCmd == 1 && hasOwn(parsed, "key")) {
                     this.cipherKey = parsed.key;
                     this.emit("cipher_key", parsed.key);
@@ -260,6 +301,13 @@ export class Listener extends EventEmitter {
                                 continue;
                             this.emit("friend_event", friendEvent);
                         }
+                        else if (control.content.act_type == "syncmsgmb") {
+                            const syncEventData = typeof control.content.data == "string"
+                                ? JSON.parse(control.content.data)
+                                : control.content.data;
+                            const syncEvent = initializeSyncEvent(syncEventData, control.content.act, control.content.act_type);
+                            this.emit("sync_event", syncEvent);
+                        }
                     }
                 }
                 if (cmd == 612) {
diff --git a/node_modules/zca-js/dist/apis/pullMobile.d.ts b/node_modules/zca-js/dist/apis/pullMobile.d.ts
new file mode 100644
index 0000000..a60e1e3
--- /dev/null
+++ b/node_modules/zca-js/dist/apis/pullMobile.d.ts
@@ -0,0 +1,13 @@
+export type PullMobileResponse = {
+    error_code: string;
+    error_message: string;
+    data: string;
+};
+export type PullMobileParams = {
+    public_key: string;
+    from_seq_id?: number;
+    is_retry?: number;
+    min_seq_id?: number;
+    temp_key?: string;
+};
+export declare const pullMobileFactory: (ctx: import("../context.js").ContextBase, api: import("../apis.js").API) => (params: PullMobileParams) => Promise<PullMobileResponse>;
diff --git a/node_modules/zca-js/dist/apis/pullMobile.js b/node_modules/zca-js/dist/apis/pullMobile.js
new file mode 100644
index 0000000..479cacd
--- /dev/null
+++ b/node_modules/zca-js/dist/apis/pullMobile.js
@@ -0,0 +1,37 @@
+import { apiFactory } from "../utils.js";
+import { ZaloApiError } from "../Errors/ZaloApiError.js";
+// The response from this does not contain any meaningful fields, it returns empty encrypted data
+export const pullMobileFactory = apiFactory()((api, ctx, utils) => {
+    const baseURL = utils.makeURL(`${api.zpwServiceMap.file[0]}/api/message/pull_mobile_msg`);
+    /**
+     * Pull mobile messages from the server
+     *
+     * @param params Pull mobile message parameters
+     *
+     * @throws {ZaloApiError}
+     */
+    return async function pullMobile({ public_key, from_seq_id = 0, is_retry = 0, min_seq_id = 0, temp_key = "", }) {
+        const Params = {
+            pc_name: "Web", // This will not be changed
+            public_key,
+            from_seq_id,
+            is_retry,
+            min_seq_id,
+            temp_key,
+            imei: ctx.imei,
+        };
+        const encryptedParams = utils.encodeAES(JSON.stringify(Params));
+        if (!encryptedParams)
+            throw new ZaloApiError("Failed to encrypt params");
+        const serviceURL = utils.makeURL(baseURL, {
+            zpw_ver: ctx.API_VERSION,
+            zpw_type: ctx.API_TYPE,
+            params: encryptedParams,
+            nretry: 0,
+        }, false);
+        const response = await utils.request(serviceURL, {
+            method: "GET",
+        });
+        return utils.resolve(response);
+    };
+});
diff --git a/node_modules/zca-js/dist/index.d.ts b/node_modules/zca-js/dist/index.d.ts
index b69610b..03ae933 100644
--- a/node_modules/zca-js/dist/index.d.ts
+++ b/node_modules/zca-js/dist/index.d.ts
@@ -81,6 +81,8 @@ export type { GetReminderResponse } from "./apis/getReminder.js";
 export type { GetReminderResponsesResponse } from "./apis/getReminderResponses.js";
 export type { GetSentFriendRequestResponse, SentFriendRequestInfo } from "./apis/getSentFriendRequest.js";
 export type { GetSettingsResponse } from "./apis/getSettings.js";
+export type { GetSyncDataParams } from "./apis/getSyncData.js";
+export type { PullMobileParams, PullMobileResponse } from "./apis/pullMobile.js";
 export type { GetStickerCategoryDetailResponse } from "./apis/getStickerCategoryDetail.js";
 export type { StickerDetailResponse } from "./apis/getStickersDetail.js";
 export type { GetUnreadMarkResponse, UnreadMark } from "./apis/getUnreadMark.js";
@@ -155,3 +157,4 @@ export { MuteAction, MuteDuration } from "./apis/setMute.js";
 export { ChatTTL } from "./apis/updateAutoDeleteChat.js";
 export { UpdateLangAvailableLanguages } from "./apis/updateLang.js";
 export { UpdateSettingsType } from "./apis/updateSettings.js";
+export { generateRSAKeyPair, decryptWithPrivateKey } from "./utils.js";
diff --git a/node_modules/zca-js/dist/index.js b/node_modules/zca-js/dist/index.js
index 3b9b8bd..7f4269f 100644
--- a/node_modules/zca-js/dist/index.js
+++ b/node_modules/zca-js/dist/index.js
@@ -12,3 +12,4 @@ export { MuteAction, MuteDuration } from "./apis/setMute.js";
 export { ChatTTL } from "./apis/updateAutoDeleteChat.js";
 export { UpdateLangAvailableLanguages } from "./apis/updateLang.js";
 export { UpdateSettingsType } from "./apis/updateSettings.js";
+export { generateRSAKeyPair, decryptWithPrivateKey } from "./utils.js";
diff --git a/node_modules/zca-js/dist/models/SyncData.d.ts b/node_modules/zca-js/dist/models/SyncData.d.ts
new file mode 100644
index 0000000..a7f9ab4
--- /dev/null
+++ b/node_modules/zca-js/dist/models/SyncData.d.ts
@@ -0,0 +1,35 @@
+export type SyncData = {
+    file_path: string;
+    encrypted_key: string;
+    metadata: {
+        file_name: string;
+        file_size: number;
+        checksum_code: string;
+        from_seq_id: number;
+        is_full_transfer: number;
+    };
+};
+export type SyncDataStream = {
+    stream: ReadableStream<Uint8Array> | null;
+    metadata: {
+        file_name: string;
+        file_size: number;
+        checksum_code: string;
+        from_seq_id: number;
+        is_full_transfer: number;
+        encrypted_key: string;
+    };
+    response?: {
+        status: number;
+        statusText: string;
+        url: string;
+        contentType: string | null;
+        contentLength: string | null;
+    };
+};
+export type SyncDataResponse = {
+    success: boolean;
+    data?: SyncData;
+    stream?: SyncDataStream;
+    error?: string;
+};
diff --git a/node_modules/zca-js/dist/models/SyncData.js b/node_modules/zca-js/dist/models/SyncData.js
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/node_modules/zca-js/dist/models/SyncData.js
@@ -0,0 +1 @@
+export {};
diff --git a/node_modules/zca-js/dist/models/SyncEvent.d.ts b/node_modules/zca-js/dist/models/SyncEvent.d.ts
new file mode 100644
index 0000000..a2e20a9
--- /dev/null
+++ b/node_modules/zca-js/dist/models/SyncEvent.d.ts
@@ -0,0 +1,66 @@
+export declare enum SyncEventType {
+    USER_CONFIRM = "user_confirm",
+    SYNCMSG_INFO = "syncmsg_info",
+    TRANSFER_ERROR = "transfer_error",
+    UNKNOWN = "unknown"
+}
+export type DatabaseBackupInfo = {
+    backup_db: {
+        msg_total: number;
+        msg_thread: number;
+    };
+    db_format: number;
+};
+export type TSyncEventUserConfirm = {
+    user_action: number;
+    public_key: string;
+    pc_name: string;
+};
+export type TSyncEventSyncMsgInfo = {
+    uid: number;
+    url: string;
+    time: number;
+    device_type: number;
+    device_name: string;
+    client_version: number;
+    file_name: string;
+    checksum_code: string;
+    file_size: number;
+    client_time: number;
+    from_seq_id: number;
+    public_key: string;
+    encrypted_key: string;
+    trigger_reason: number;
+    is_full_transfer: number;
+    db_info: string | DatabaseBackupInfo;
+};
+export type TSyncEventTransferError = {
+    public_key: string;
+    pc_name: string;
+    can_retry: number;
+    error_msg: string;
+    error_code: number;
+};
+export type TSyncEvent = TSyncEventUserConfirm | TSyncEventSyncMsgInfo | TSyncEventTransferError;
+export type SyncEvent = {
+    type: SyncEventType.USER_CONFIRM;
+    data: TSyncEventUserConfirm;
+    act: string;
+    act_type: string;
+} | {
+    type: SyncEventType.SYNCMSG_INFO;
+    data: TSyncEventSyncMsgInfo;
+    act: string;
+    act_type: string;
+} | {
+    type: SyncEventType.TRANSFER_ERROR;
+    data: TSyncEventTransferError;
+    act: string;
+    act_type: string;
+} | {
+    type: SyncEventType.UNKNOWN;
+    data: unknown;
+    act: string;
+    act_type: string;
+};
+export declare function initializeSyncEvent(data: TSyncEvent, act: string, act_type: string): SyncEvent;
diff --git a/node_modules/zca-js/dist/models/SyncEvent.js b/node_modules/zca-js/dist/models/SyncEvent.js
new file mode 100644
index 0000000..cbf3e0d
--- /dev/null
+++ b/node_modules/zca-js/dist/models/SyncEvent.js
@@ -0,0 +1,41 @@
+export var SyncEventType;
+(function (SyncEventType) {
+    SyncEventType["USER_CONFIRM"] = "user_confirm";
+    SyncEventType["SYNCMSG_INFO"] = "syncmsg_info";
+    SyncEventType["TRANSFER_ERROR"] = "transfer_error";
+    SyncEventType["UNKNOWN"] = "unknown";
+})(SyncEventType || (SyncEventType = {}));
+export function initializeSyncEvent(data, act, act_type) {
+    if (act === "user_confirm") {
+        return {
+            type: SyncEventType.USER_CONFIRM,
+            data: data,
+            act,
+            act_type,
+        };
+    }
+    else if (act === "syncmsg_info") {
+        return {
+            type: SyncEventType.SYNCMSG_INFO,
+            data: data,
+            act,
+            act_type,
+        };
+    }
+    else if (act === "transfer_error") {
+        return {
+            type: SyncEventType.TRANSFER_ERROR,
+            data: data,
+            act,
+            act_type,
+        };
+    }
+    else {
+        return {
+            type: SyncEventType.UNKNOWN,
+            data,
+            act,
+            act_type,
+        };
+    }
+}
diff --git a/node_modules/zca-js/dist/models/index.d.ts b/node_modules/zca-js/dist/models/index.d.ts
index 46f087d..b935e6e 100644
--- a/node_modules/zca-js/dist/models/index.d.ts
+++ b/node_modules/zca-js/dist/models/index.d.ts
@@ -19,3 +19,5 @@ export * from "./User.js";
 export * from "./ZBusiness.js";
 export * from "./Label.js";
 export * from "./Sticker.js";
+export * from "./SyncData.js";
+export * from "./SyncEvent.js";
diff --git a/node_modules/zca-js/dist/models/index.js b/node_modules/zca-js/dist/models/index.js
index 46f087d..b935e6e 100644
--- a/node_modules/zca-js/dist/models/index.js
+++ b/node_modules/zca-js/dist/models/index.js
@@ -19,3 +19,5 @@ export * from "./User.js";
 export * from "./ZBusiness.js";
 export * from "./Label.js";
 export * from "./Sticker.js";
+export * from "./SyncData.js";
+export * from "./SyncEvent.js";
diff --git a/node_modules/zca-js/dist/utils.d.ts b/node_modules/zca-js/dist/utils.d.ts
index 2cf9969..42cecb2 100644
--- a/node_modules/zca-js/dist/utils.d.ts
+++ b/node_modules/zca-js/dist/utils.d.ts
@@ -145,6 +145,27 @@ export declare function encryptPin(pin: string): string;
  * const isInvalid = validatePin(encryptedPin, "5678"); // false if not pin created is 5678
  */
 export declare function validatePin(encryptedPin: string, pin: string): boolean;
+/**
+ * Generate an RSA-2048 key pair used to bootstrap the `pull_mobile_msg` mobile sync flow.
+ * The public key (base64, headers stripped) is sent to Zalo; Zalo wraps the AES-256 sync
+ * key with it and returns it in the `syncmsg_info` event as `encrypted_key`.
+ */
+export declare function generateRSAKeyPair(): {
+    publicKeyBase64: string;
+    privateKeyBase64: string;
+    publicKey: string;
+    privateKey: string;
+};
+/**
+ * Decrypt data using RSA private key with PKCS1 scheme
+ * Validates that the decrypted result is a 32-byte AES key
+ *
+ * @param privateKey - RSA private key in PEM format
+ * @param encryptedBase64 - Base64 encoded encrypted data
+ * @returns Decrypted buffer (32 bytes)
+ * @throws Error if decryption fails or result is not 32 bytes
+ */
+export declare function decryptWithPrivateKey(privateKey: string, encryptedBase64: string): Buffer;
 /**
  * Converts a hex color code to a negative color number used by Zalo API
  * @param hex Hex color code (e.g. '#00FF00' or '00FF00')
diff --git a/node_modules/zca-js/dist/utils.js b/node_modules/zca-js/dist/utils.js
index a23e1ba..481b5fd 100644
--- a/node_modules/zca-js/dist/utils.js
+++ b/node_modules/zca-js/dist/utils.js
@@ -668,6 +668,46 @@ export function validatePin(encryptedPin, pin) {
     const hash = crypto.createHash("md5").update(pin).digest("hex");
     return hash === encryptedPin;
 }
+/**
+ * Generate an RSA-2048 key pair used to bootstrap the `pull_mobile_msg` mobile sync flow.
+ * The public key is DER/SPKI base64, matching the Zalo PC backup flow. Zalo wraps the
+ * sync key with it and returns it in the `syncmsg_info` event as `encrypted_key`.
+ */
+export function generateRSAKeyPair() {
+    const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
+        modulusLength: 2048,
+        publicKeyEncoding: { type: "spki", format: "der" },
+        privateKeyEncoding: { type: "pkcs1", format: "pem" },
+    });
+    const publicKeyBase64 = publicKey.toString("base64");
+    const privateKeyBase64 = privateKey
+        .toString()
+        .replace(/-----BEGIN RSA PRIVATE KEY-----/g, "")
+        .replace(/-----END RSA PRIVATE KEY-----/g, "")
+        .replace(/\s+/g, "");
+    return { publicKeyBase64, privateKeyBase64, publicKey, privateKey };
+}
+/**
+ * Decrypt data using RSA private key with PKCS1 scheme
+ * Validates that the decrypted result is a 32-byte AES key
+ *
+ * @param privateKey - RSA private key in PEM format
+ * @param encryptedBase64 - Base64 encoded encrypted data
+ * @returns Decrypted buffer (32 bytes)
+ * @throws Error if decryption fails or result is not 32 bytes
+ */
+export function decryptWithPrivateKey(privateKey, encryptedBase64) {
+    const cleanKey = privateKey.replace(/\\n/g, "\n");
+    const encryptedBuffer = Buffer.from(encryptedBase64, "base64");
+    const decryptedBuffer = crypto.privateDecrypt({
+        key: cleanKey,
+        padding: crypto.constants.RSA_PKCS1_PADDING,
+    }, encryptedBuffer);
+    if (decryptedBuffer.length !== 32) {
+        throw new Error(`Invalid AES key length: ${decryptedBuffer.length} bytes (expected 32 bytes)`);
+    }
+    return decryptedBuffer;
+}
 /**
  * Converts a hex color code to a negative color number used by Zalo API
  * @param hex Hex color code (e.g. '#00FF00' or '00FF00')
