tca-js 0.4.1 → 0.5.0
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 +21 -15
- package/dist/index.d.mts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +86 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +86 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,11 @@ await new TikTok().login({ cookies: 'sessionid=…; ttwid=…' }); // header str
|
|
|
86
86
|
Required cookies: `sessionid`, `sid_tt`, `sid_guard`, `uid_tt`, `ttwid`, `odin_tt`, `store-idc`, `tt-target-idc`.
|
|
87
87
|
A session lasts about 6 months, and logging in does not sign out other devices.
|
|
88
88
|
|
|
89
|
+
> **Sending needs `ticketGuard`** (since 12/11/2026). TikTok signs `message/send` with a per-login key pair; a session
|
|
90
|
+
> that sends unsigned gets blocked after its first message (`SESSION_BLOCKED`). Cookies alone are enough to READ, but
|
|
91
|
+
> to send use credentials from `loginQR` (which captures `ticketGuard`) and persist them as a whole — including
|
|
92
|
+
> `api.getContext().ticketGuard`. Treat it as secret as the cookies.
|
|
93
|
+
|
|
89
94
|
### QR code
|
|
90
95
|
|
|
91
96
|
`loginQR` opens a headless Chrome on TikTok's QR page, emits the QR image, and resolves with credentials once the code is
|
|
@@ -101,7 +106,7 @@ const credentials = await loginQR({
|
|
|
101
106
|
});
|
|
102
107
|
|
|
103
108
|
const api = await new TikTok().login(credentials);
|
|
104
|
-
// Save `credentials` to skip scanning next time.
|
|
109
|
+
// Save `credentials` (cookies + ticketGuard) to skip scanning next time — both are secrets.
|
|
105
110
|
```
|
|
106
111
|
|
|
107
112
|
| Option | Type | Default | Description |
|
|
@@ -143,12 +148,13 @@ with `sendMessage(msg, …)`.
|
|
|
143
148
|
|
|
144
149
|
### Session
|
|
145
150
|
|
|
146
|
-
| Method / property | Description
|
|
147
|
-
| ----------------- |
|
|
148
|
-
| `account` | Logged-in account: `uid`, `username`, `screenName`, `avatar`
|
|
149
|
-
| `getOwnId()` | Own user id
|
|
150
|
-
| `getCookies()` | Current cookies — persist them to resume the session
|
|
151
|
-
| `
|
|
151
|
+
| Method / property | Description |
|
|
152
|
+
| ----------------- | ------------------------------------------------------------- |
|
|
153
|
+
| `account` | Logged-in account: `uid`, `username`, `screenName`, `avatar` |
|
|
154
|
+
| `getOwnId()` | Own user id |
|
|
155
|
+
| `getCookies()` | Current cookies — persist them to resume the session |
|
|
156
|
+
| `getContext()` | `deviceId`, `userAgent`, `ticketGuard` — persist with cookies |
|
|
157
|
+
| `isAlive()` | `true` while the session is still valid |
|
|
152
158
|
|
|
153
159
|
### Message object
|
|
154
160
|
|
|
@@ -184,14 +190,14 @@ api.listener.stop();
|
|
|
184
190
|
|
|
185
191
|
All library errors are instances of `TikTokError` with a `code`:
|
|
186
192
|
|
|
187
|
-
| Code | Meaning
|
|
188
|
-
| ----------------------------------- |
|
|
189
|
-
| `SESSION_EXPIRED` | Cookies are no longer valid — log in again
|
|
190
|
-
| `SESSION_BLOCKED` | TikTok's gateway refuses this session (JSON `status_code: 7
|
|
191
|
-
| `IM_ERROR` | TikTok rejected the request
|
|
192
|
-
| `HTTP_ERROR` · `BAD_RESPONSE` | Network or unexpected response
|
|
193
|
-
| `UPLOAD_FAILED` · `DOWNLOAD_FAILED` | Media transfer failed
|
|
194
|
-
| `LOGIN_FAILED` · `LOGIN_ABORTED` | QR login failed or was cancelled
|
|
193
|
+
| Code | Meaning |
|
|
194
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
195
|
+
| `SESSION_EXPIRED` | Cookies are no longer valid — log in again |
|
|
196
|
+
| `SESSION_BLOCKED` | TikTok's gateway refuses this session (JSON `status_code: 7`) — usually sent without `ticketGuard`; log in again with `loginQR` |
|
|
197
|
+
| `IM_ERROR` | TikTok rejected the request |
|
|
198
|
+
| `HTTP_ERROR` · `BAD_RESPONSE` | Network or unexpected response |
|
|
199
|
+
| `UPLOAD_FAILED` · `DOWNLOAD_FAILED` | Media transfer failed |
|
|
200
|
+
| `LOGIN_FAILED` · `LOGIN_ABORTED` | QR login failed or was cancelled |
|
|
195
201
|
|
|
196
202
|
```ts
|
|
197
203
|
import { TikTokError } from 'tca-js';
|
package/dist/index.d.mts
CHANGED
|
@@ -44,6 +44,22 @@ interface Credentials {
|
|
|
44
44
|
/** IM device id (`wid`) — leave empty and `login` asks TikTok using `ttwid`. Only used when that fails. */
|
|
45
45
|
deviceId?: string;
|
|
46
46
|
userAgent?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Request-signing material captured at login (`loginQR` fills it). Required to SEND messages since 12/11/2026 —
|
|
49
|
+
* without it TikTok blocks the session's sends after the first one (`SESSION_BLOCKED`). Keep it with the cookies.
|
|
50
|
+
*/
|
|
51
|
+
ticketGuard?: TicketGuard;
|
|
52
|
+
}
|
|
53
|
+
/** TikTok "ticket guard" signing material — see `protocol/ticket-guard.ts`. Secret: treat like the session cookie. */
|
|
54
|
+
interface TicketGuard {
|
|
55
|
+
/** EC P-256 private key, PKCS#8 PEM. */
|
|
56
|
+
privateKey: string;
|
|
57
|
+
/** Matching public key, SPKI PEM. */
|
|
58
|
+
publicKey: string;
|
|
59
|
+
/** Server-issued `ts_sign` bound to the public key. */
|
|
60
|
+
tsSign: string;
|
|
61
|
+
/** Server-issued ticket (plain text). */
|
|
62
|
+
ticket: string;
|
|
47
63
|
}
|
|
48
64
|
interface TikTokOptions {
|
|
49
65
|
/** Debug logging — silent by default. */
|
|
@@ -152,6 +168,8 @@ declare class Context {
|
|
|
152
168
|
*/
|
|
153
169
|
deviceId: string;
|
|
154
170
|
readonly userAgent: string;
|
|
171
|
+
/** Signing material for `SIGNED_PATHS` — absent for sessions saved before 0.5.0 (sends then go unsigned). */
|
|
172
|
+
readonly ticketGuard?: TicketGuard;
|
|
155
173
|
ownId: string;
|
|
156
174
|
private seq;
|
|
157
175
|
constructor(creds: Credentials, options?: TikTokOptions);
|
|
@@ -166,7 +184,8 @@ declare class Context {
|
|
|
166
184
|
buildRequest(cmd: number, bodyField: number, body: Buffer): Buffer;
|
|
167
185
|
/**
|
|
168
186
|
* Call an IM command over HTTP. Returns the unwrapped body (`ResponseBody[cmd]`).
|
|
169
|
-
* NO X-Gnarly/X-Bogus/msToken signature needed
|
|
187
|
+
* NO X-Gnarly/X-Bogus/msToken signature needed. `message/send` additionally needs the ticket-guard headers
|
|
188
|
+
* (enforced 12/11/2026) — added from `ticketGuard` when present.
|
|
170
189
|
*/
|
|
171
190
|
im(path: string, cmd: number, body: Buffer, bodyField?: number): Promise<Msg>;
|
|
172
191
|
}
|
|
@@ -230,9 +249,11 @@ declare class API {
|
|
|
230
249
|
name: string;
|
|
231
250
|
value: string;
|
|
232
251
|
}[];
|
|
252
|
+
/** Non-cookie session state to save alongside the cookies (pass back into `login`). */
|
|
233
253
|
getContext(): {
|
|
234
254
|
deviceId: string;
|
|
235
255
|
userAgent: string;
|
|
256
|
+
ticketGuard?: TicketGuard;
|
|
236
257
|
};
|
|
237
258
|
/** Whether the session is still alive — call periodically to detect expiry early. */
|
|
238
259
|
isAlive(): Promise<boolean>;
|
|
@@ -371,4 +392,4 @@ declare function loginQR(opts: QrLoginOptions): Promise<Credentials>;
|
|
|
371
392
|
declare function findChrome(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | null;
|
|
372
393
|
|
|
373
394
|
export { API, Listener, MANUAL_CLOSE, MessageType, TikTok, TikTokError, findChrome, loginQR };
|
|
374
|
-
export type { ConversationPage, ConversationRef, CookieInput, Credentials, ImageInput, ListenerEvents, MessagePage, QrLoginEvent, QrLoginOptions, SendResult, TikTokAccount, TikTokConversation, TikTokErrorCode, TikTokImage, TikTokMessage, TikTokOptions, TikTokReaction, TikTokSticker, TikTokUser };
|
|
395
|
+
export type { ConversationPage, ConversationRef, CookieInput, Credentials, ImageInput, ListenerEvents, MessagePage, QrLoginEvent, QrLoginOptions, SendResult, TicketGuard, TikTokAccount, TikTokConversation, TikTokErrorCode, TikTokImage, TikTokMessage, TikTokOptions, TikTokReaction, TikTokSticker, TikTokUser };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,22 @@ interface Credentials {
|
|
|
44
44
|
/** IM device id (`wid`) — leave empty and `login` asks TikTok using `ttwid`. Only used when that fails. */
|
|
45
45
|
deviceId?: string;
|
|
46
46
|
userAgent?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Request-signing material captured at login (`loginQR` fills it). Required to SEND messages since 12/11/2026 —
|
|
49
|
+
* without it TikTok blocks the session's sends after the first one (`SESSION_BLOCKED`). Keep it with the cookies.
|
|
50
|
+
*/
|
|
51
|
+
ticketGuard?: TicketGuard;
|
|
52
|
+
}
|
|
53
|
+
/** TikTok "ticket guard" signing material — see `protocol/ticket-guard.ts`. Secret: treat like the session cookie. */
|
|
54
|
+
interface TicketGuard {
|
|
55
|
+
/** EC P-256 private key, PKCS#8 PEM. */
|
|
56
|
+
privateKey: string;
|
|
57
|
+
/** Matching public key, SPKI PEM. */
|
|
58
|
+
publicKey: string;
|
|
59
|
+
/** Server-issued `ts_sign` bound to the public key. */
|
|
60
|
+
tsSign: string;
|
|
61
|
+
/** Server-issued ticket (plain text). */
|
|
62
|
+
ticket: string;
|
|
47
63
|
}
|
|
48
64
|
interface TikTokOptions {
|
|
49
65
|
/** Debug logging — silent by default. */
|
|
@@ -152,6 +168,8 @@ declare class Context {
|
|
|
152
168
|
*/
|
|
153
169
|
deviceId: string;
|
|
154
170
|
readonly userAgent: string;
|
|
171
|
+
/** Signing material for `SIGNED_PATHS` — absent for sessions saved before 0.5.0 (sends then go unsigned). */
|
|
172
|
+
readonly ticketGuard?: TicketGuard;
|
|
155
173
|
ownId: string;
|
|
156
174
|
private seq;
|
|
157
175
|
constructor(creds: Credentials, options?: TikTokOptions);
|
|
@@ -166,7 +184,8 @@ declare class Context {
|
|
|
166
184
|
buildRequest(cmd: number, bodyField: number, body: Buffer): Buffer;
|
|
167
185
|
/**
|
|
168
186
|
* Call an IM command over HTTP. Returns the unwrapped body (`ResponseBody[cmd]`).
|
|
169
|
-
* NO X-Gnarly/X-Bogus/msToken signature needed
|
|
187
|
+
* NO X-Gnarly/X-Bogus/msToken signature needed. `message/send` additionally needs the ticket-guard headers
|
|
188
|
+
* (enforced 12/11/2026) — added from `ticketGuard` when present.
|
|
170
189
|
*/
|
|
171
190
|
im(path: string, cmd: number, body: Buffer, bodyField?: number): Promise<Msg>;
|
|
172
191
|
}
|
|
@@ -230,9 +249,11 @@ declare class API {
|
|
|
230
249
|
name: string;
|
|
231
250
|
value: string;
|
|
232
251
|
}[];
|
|
252
|
+
/** Non-cookie session state to save alongside the cookies (pass back into `login`). */
|
|
233
253
|
getContext(): {
|
|
234
254
|
deviceId: string;
|
|
235
255
|
userAgent: string;
|
|
256
|
+
ticketGuard?: TicketGuard;
|
|
236
257
|
};
|
|
237
258
|
/** Whether the session is still alive — call periodically to detect expiry early. */
|
|
238
259
|
isAlive(): Promise<boolean>;
|
|
@@ -371,4 +392,4 @@ declare function loginQR(opts: QrLoginOptions): Promise<Credentials>;
|
|
|
371
392
|
declare function findChrome(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | null;
|
|
372
393
|
|
|
373
394
|
export { API, Listener, MANUAL_CLOSE, MessageType, TikTok, TikTokError, findChrome, loginQR };
|
|
374
|
-
export type { ConversationPage, ConversationRef, CookieInput, Credentials, ImageInput, ListenerEvents, MessagePage, QrLoginEvent, QrLoginOptions, SendResult, TikTokAccount, TikTokConversation, TikTokErrorCode, TikTokImage, TikTokMessage, TikTokOptions, TikTokReaction, TikTokSticker, TikTokUser };
|
|
395
|
+
export type { ConversationPage, ConversationRef, CookieInput, Credentials, ImageInput, ListenerEvents, MessagePage, QrLoginEvent, QrLoginOptions, SendResult, TicketGuard, TikTokAccount, TikTokConversation, TikTokErrorCode, TikTokImage, TikTokMessage, TikTokOptions, TikTokReaction, TikTokSticker, TikTokUser };
|
package/dist/index.js
CHANGED
|
@@ -793,8 +793,9 @@ var API = class {
|
|
|
793
793
|
getCookies() {
|
|
794
794
|
return [...this.ctx.cookies].map(([name, value]) => ({ name, value }));
|
|
795
795
|
}
|
|
796
|
+
/** Non-cookie session state to save alongside the cookies (pass back into `login`). */
|
|
796
797
|
getContext() {
|
|
797
|
-
return { deviceId: this.ctx.deviceId, userAgent: this.ctx.userAgent };
|
|
798
|
+
return { deviceId: this.ctx.deviceId, userAgent: this.ctx.userAgent, ticketGuard: this.ctx.ticketGuard };
|
|
798
799
|
}
|
|
799
800
|
/** Whether the session is still alive — call periodically to detect expiry early. */
|
|
800
801
|
async isAlive() {
|
|
@@ -924,6 +925,56 @@ var API = class {
|
|
|
924
925
|
}
|
|
925
926
|
};
|
|
926
927
|
|
|
928
|
+
// src/protocol/ticket-guard.ts
|
|
929
|
+
var import_node_crypto4 = require("crypto");
|
|
930
|
+
var SIGNED_PATHS = /* @__PURE__ */ new Set(["/v1/message/send"]);
|
|
931
|
+
function ticketGuardHeaders(guard, path, now = Date.now()) {
|
|
932
|
+
const timestamp = Math.floor(now / 1e3);
|
|
933
|
+
const message = `ticket=${guard.ticket}&path=${path}×tamp=${timestamp}`;
|
|
934
|
+
const reqSign = (0, import_node_crypto4.sign)("sha256", Buffer.from(message), (0, import_node_crypto4.createPrivateKey)(guard.privateKey)).toString("base64");
|
|
935
|
+
const clientData = { ts_sign: guard.tsSign, req_content: "ticket,path,timestamp", req_sign: reqSign, timestamp };
|
|
936
|
+
return {
|
|
937
|
+
"tt-ticket-guard-client-data": Buffer.from(JSON.stringify(clientData)).toString("base64"),
|
|
938
|
+
"tt-ticket-guard-public-key": publicPoint(guard.publicKey),
|
|
939
|
+
"tt-ticket-guard-version": "2",
|
|
940
|
+
"tt-ticket-guard-web-version": "1",
|
|
941
|
+
"tt-ticket-guard-iteration-version": "0"
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function publicPoint(pem) {
|
|
945
|
+
const jwk = (0, import_node_crypto4.createPublicKey)(pem).export({ format: "jwk" });
|
|
946
|
+
return Buffer.concat([
|
|
947
|
+
Buffer.from([4]),
|
|
948
|
+
Buffer.from(jwk.x ?? "", "base64url"),
|
|
949
|
+
Buffer.from(jwk.y ?? "", "base64url")
|
|
950
|
+
]).toString("base64");
|
|
951
|
+
}
|
|
952
|
+
var READ_TICKET_GUARD_JS = `(async () => {
|
|
953
|
+
try {
|
|
954
|
+
const db = await new Promise((ok, ko) => { const r = indexedDB.open('secure-store'); r.onsuccess = () => ok(r.result); r.onerror = ko; });
|
|
955
|
+
if (!db.objectStoreNames.contains('cryptvalues')) return null;
|
|
956
|
+
const get = (k) => new Promise((ok) => { const q = db.transaction('cryptvalues').objectStore('cryptvalues').get(k); q.onsuccess = () => ok(q.result); q.onerror = () => ok(null); });
|
|
957
|
+
const keysRaw = await get('security-sdk/s_sdk_crypt_sdk');
|
|
958
|
+
const signRaw = await get('security-sdk/s_sdk_sign_data_key/tt_fetch');
|
|
959
|
+
if (!keysRaw || !signRaw) return null;
|
|
960
|
+
const keys = JSON.parse(JSON.parse(keysRaw).data);
|
|
961
|
+
const signData = JSON.parse(JSON.parse(signRaw).data);
|
|
962
|
+
if (!keys.ec_privateKey || !keys.ec_publicKey || !signData.ts_sign || !signData.encrypt_ticket) return null;
|
|
963
|
+
const te = new TextEncoder();
|
|
964
|
+
const base = await crypto.subtle.importKey('raw', te.encode('tt-ticket-guard-iv'), 'PBKDF2', false, ['deriveKey']);
|
|
965
|
+
const aes = await crypto.subtle.deriveKey({ name: 'PBKDF2', salt: te.encode('secure-salt'), iterations: 1000, hash: 'SHA-256' }, base, { name: 'AES-GCM', length: 128 }, false, ['decrypt']);
|
|
966
|
+
const enc = Uint8Array.from(atob(signData.encrypt_ticket), (c) => c.charCodeAt(0));
|
|
967
|
+
const ticket = new TextDecoder().decode(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: enc.slice(0, 12) }, aes, enc.slice(12)));
|
|
968
|
+
return { privateKey: keys.ec_privateKey, publicKey: keys.ec_publicKey, tsSign: signData.ts_sign, ticket };
|
|
969
|
+
} catch (e) {
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
})()`;
|
|
973
|
+
function isTicketGuard(v) {
|
|
974
|
+
const g = v;
|
|
975
|
+
return !!g && typeof g.privateKey === "string" && typeof g.publicKey === "string" && typeof g.tsSign === "string" && typeof g.ticket === "string" && g.ticket.length > 0;
|
|
976
|
+
}
|
|
977
|
+
|
|
927
978
|
// src/protocol/context.ts
|
|
928
979
|
function normalizeCookies(input) {
|
|
929
980
|
const jar = /* @__PURE__ */ new Map();
|
|
@@ -945,6 +996,7 @@ var Context = class {
|
|
|
945
996
|
this.cookies = normalizeCookies(creds.cookies);
|
|
946
997
|
this.deviceId = creds.deviceId ?? "";
|
|
947
998
|
this.userAgent = creds.userAgent || DEFAULT_USER_AGENT;
|
|
999
|
+
if (isTicketGuard(creds.ticketGuard)) this.ticketGuard = creds.ticketGuard;
|
|
948
1000
|
}
|
|
949
1001
|
options;
|
|
950
1002
|
cookies;
|
|
@@ -954,6 +1006,8 @@ var Context = class {
|
|
|
954
1006
|
*/
|
|
955
1007
|
deviceId;
|
|
956
1008
|
userAgent;
|
|
1009
|
+
/** Signing material for `SIGNED_PATHS` — absent for sessions saved before 0.5.0 (sends then go unsigned). */
|
|
1010
|
+
ticketGuard;
|
|
957
1011
|
ownId = "";
|
|
958
1012
|
seq = 1e4;
|
|
959
1013
|
cookieHeader() {
|
|
@@ -999,13 +1053,18 @@ var Context = class {
|
|
|
999
1053
|
}
|
|
1000
1054
|
/**
|
|
1001
1055
|
* Call an IM command over HTTP. Returns the unwrapped body (`ResponseBody[cmd]`).
|
|
1002
|
-
* NO X-Gnarly/X-Bogus/msToken signature needed
|
|
1056
|
+
* NO X-Gnarly/X-Bogus/msToken signature needed. `message/send` additionally needs the ticket-guard headers
|
|
1057
|
+
* (enforced 12/11/2026) — added from `ticketGuard` when present.
|
|
1003
1058
|
*/
|
|
1004
1059
|
async im(path, cmd, body, bodyField = cmd) {
|
|
1005
1060
|
const url = `${IM_API}${path}?aid=${AID_WEB}&version_code=1.0.0&app_name=tiktok_web&device_platform=web_pc`;
|
|
1006
1061
|
const res = await this.fetch(url, {
|
|
1007
1062
|
method: "POST",
|
|
1008
|
-
headers: {
|
|
1063
|
+
headers: {
|
|
1064
|
+
"Content-Type": "application/x-protobuf",
|
|
1065
|
+
Accept: "application/x-protobuf",
|
|
1066
|
+
...this.ticketGuard && SIGNED_PATHS.has(path) ? ticketGuardHeaders(this.ticketGuard, path) : {}
|
|
1067
|
+
},
|
|
1009
1068
|
body: new Uint8Array(this.buildRequest(cmd, bodyField, body))
|
|
1010
1069
|
});
|
|
1011
1070
|
if (!res.ok) throw new TikTokError("HTTP_ERROR", `${path}: HTTP ${res.status}`, res.status);
|
|
@@ -1242,6 +1301,28 @@ async function loginQR(opts) {
|
|
|
1242
1301
|
return null;
|
|
1243
1302
|
}
|
|
1244
1303
|
};
|
|
1304
|
+
const readTicketGuard = async () => {
|
|
1305
|
+
const read = async () => {
|
|
1306
|
+
const r = await tab.send("Runtime.evaluate", {
|
|
1307
|
+
expression: READ_TICKET_GUARD_JS,
|
|
1308
|
+
awaitPromise: true,
|
|
1309
|
+
returnByValue: true
|
|
1310
|
+
}).catch(() => null);
|
|
1311
|
+
return isTicketGuard(r?.result?.value) ? r.result.value : void 0;
|
|
1312
|
+
};
|
|
1313
|
+
for (let i = 0; i < 8 && !done; i++) {
|
|
1314
|
+
const g = await read();
|
|
1315
|
+
if (g) return g;
|
|
1316
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1317
|
+
}
|
|
1318
|
+
await tab.send("Page.navigate", { url: `${WEB}/messages` }).catch(() => void 0);
|
|
1319
|
+
for (let i = 0; i < 20 && !done; i++) {
|
|
1320
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1321
|
+
const g = await read();
|
|
1322
|
+
if (g) return g;
|
|
1323
|
+
}
|
|
1324
|
+
return void 0;
|
|
1325
|
+
};
|
|
1245
1326
|
const collectCookies = async () => {
|
|
1246
1327
|
for (let i = 0; i < 30 && !done; i++) {
|
|
1247
1328
|
const { cookies } = await tab.send(
|
|
@@ -1255,7 +1336,8 @@ async function loginQR(opts) {
|
|
|
1255
1336
|
)) {
|
|
1256
1337
|
byName.set(c.name, { name: c.name, value: c.value });
|
|
1257
1338
|
}
|
|
1258
|
-
|
|
1339
|
+
const ticketGuard = await readTicketGuard();
|
|
1340
|
+
return finish(null, { cookies: [...byName.values()], userAgent, ...ticketGuard ? { ticketGuard } : {} });
|
|
1259
1341
|
}
|
|
1260
1342
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
1261
1343
|
}
|