specific-ai-cli 0.0.0-stage → 0.1.1

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.
Files changed (277) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +177 -0
  3. package/README.ja.md +177 -0
  4. package/README.ko.md +177 -0
  5. package/README.md +174 -2
  6. package/README.zh-TW.md +177 -0
  7. package/assets/banner.txt +3 -0
  8. package/dist/cli/argv.js +98 -0
  9. package/dist/cli/commands/auth.js +11 -0
  10. package/dist/cli/commands/config.js +23 -0
  11. package/dist/cli/commands/doctor.js +134 -0
  12. package/dist/cli/commands/platforms.js +24 -0
  13. package/dist/cli/commands/version.js +4 -0
  14. package/dist/cli/confirm.js +22 -0
  15. package/dist/cli/dispatch.js +440 -0
  16. package/dist/cli/help.js +223 -0
  17. package/dist/cli/hints.js +60 -0
  18. package/dist/cli/main.js +18 -0
  19. package/dist/cli/output.js +66 -0
  20. package/dist/core/auth-handlers.js +41 -0
  21. package/dist/core/auth-store.js +119 -0
  22. package/dist/core/capabilities.js +163 -0
  23. package/dist/core/config.js +121 -0
  24. package/dist/core/cookies.js +164 -0
  25. package/dist/core/errors.js +37 -0
  26. package/dist/core/files.js +89 -0
  27. package/dist/core/fsutil.js +52 -0
  28. package/dist/core/hash.js +4 -0
  29. package/dist/core/http.js +331 -0
  30. package/dist/core/image.js +280 -0
  31. package/dist/core/log.js +40 -0
  32. package/dist/core/login.js +191 -0
  33. package/dist/core/mp4.js +130 -0
  34. package/dist/core/normalize.js +207 -0
  35. package/dist/core/options.js +110 -0
  36. package/dist/core/paths.js +38 -0
  37. package/dist/core/pb.js +59 -0
  38. package/dist/core/py.js +181 -0
  39. package/dist/core/rand.js +131 -0
  40. package/dist/core/registry.js +129 -0
  41. package/dist/core/schemas.js +41 -0
  42. package/dist/core/stream.js +104 -0
  43. package/dist/core/toolkit.js +35 -0
  44. package/dist/core/vm.js +73 -0
  45. package/dist/core/vocab.js +157 -0
  46. package/dist/platforms/_shared/impaas.js +410 -0
  47. package/dist/platforms/bilibili/index.js +216 -0
  48. package/dist/platforms/bilibili/web/api.js +806 -0
  49. package/dist/platforms/bilibili/web/client.js +311 -0
  50. package/dist/platforms/bilibili/web/commands.js +783 -0
  51. package/dist/platforms/bilibili/web/ddddocr.js +342 -0
  52. package/dist/platforms/bilibili/web/gaia.js +130 -0
  53. package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
  54. package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
  55. package/dist/platforms/bilibili/web/geetest.js +450 -0
  56. package/dist/platforms/bilibili/web/normalize.js +234 -0
  57. package/dist/platforms/bilibili/web/profile.js +135 -0
  58. package/dist/platforms/bilibili/web/resolve.js +112 -0
  59. package/dist/platforms/bilibili/web/sign.js +223 -0
  60. package/dist/platforms/bilibili/web/upos.js +102 -0
  61. package/dist/platforms/douyin/index.js +142 -0
  62. package/dist/platforms/douyin/web/api.js +863 -0
  63. package/dist/platforms/douyin/web/client.js +413 -0
  64. package/dist/platforms/douyin/web/commands.js +712 -0
  65. package/dist/platforms/douyin/web/creator.js +601 -0
  66. package/dist/platforms/douyin/web/crypto.js +247 -0
  67. package/dist/platforms/douyin/web/dtrait.js +190 -0
  68. package/dist/platforms/douyin/web/im.js +502 -0
  69. package/dist/platforms/douyin/web/image.js +5 -0
  70. package/dist/platforms/douyin/web/live.js +225 -0
  71. package/dist/platforms/douyin/web/mssdk.js +148 -0
  72. package/dist/platforms/douyin/web/normalize.js +326 -0
  73. package/dist/platforms/douyin/web/passport.js +1247 -0
  74. package/dist/platforms/douyin/web/profile.js +221 -0
  75. package/dist/platforms/douyin/web/proto.js +26 -0
  76. package/dist/platforms/douyin/web/resolve.js +88 -0
  77. package/dist/platforms/douyin/web/sign.js +436 -0
  78. package/dist/platforms/douyin/web/tos.js +78 -0
  79. package/dist/platforms/index.js +17 -0
  80. package/dist/platforms/jd/index.js +67 -0
  81. package/dist/platforms/jd/web/aks.js +115 -0
  82. package/dist/platforms/jd/web/api.js +549 -0
  83. package/dist/platforms/jd/web/chat.js +132 -0
  84. package/dist/platforms/jd/web/client.js +470 -0
  85. package/dist/platforms/jd/web/commands.js +557 -0
  86. package/dist/platforms/jd/web/jcap/helper.js +90 -0
  87. package/dist/platforms/jd/web/jcap/host.js +107 -0
  88. package/dist/platforms/jd/web/jcap/lsd.js +300 -0
  89. package/dist/platforms/jd/web/jcap/solver.js +995 -0
  90. package/dist/platforms/jd/web/jcap.js +150 -0
  91. package/dist/platforms/jd/web/js.js +420 -0
  92. package/dist/platforms/jd/web/login.js +775 -0
  93. package/dist/platforms/jd/web/normalize.js +297 -0
  94. package/dist/platforms/jd/web/profile.js +256 -0
  95. package/dist/platforms/jd/web/util.js +175 -0
  96. package/dist/platforms/jd/web/webm.js +133 -0
  97. package/dist/platforms/kuaishou/index.js +98 -0
  98. package/dist/platforms/kuaishou/web/api.js +694 -0
  99. package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
  100. package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
  101. package/dist/platforms/kuaishou/web/captcha.js +273 -0
  102. package/dist/platforms/kuaishou/web/client.js +487 -0
  103. package/dist/platforms/kuaishou/web/commands.js +863 -0
  104. package/dist/platforms/kuaishou/web/gap.js +144 -0
  105. package/dist/platforms/kuaishou/web/gdfp.js +376 -0
  106. package/dist/platforms/kuaishou/web/gql.js +11 -0
  107. package/dist/platforms/kuaishou/web/live.js +125 -0
  108. package/dist/platforms/kuaishou/web/normalize.js +241 -0
  109. package/dist/platforms/kuaishou/web/oracle.js +329 -0
  110. package/dist/platforms/kuaishou/web/profile.js +259 -0
  111. package/dist/platforms/kuaishou/web/resolve.js +84 -0
  112. package/dist/platforms/kuaishou/web/session.js +592 -0
  113. package/dist/platforms/kuaishou/web/sign.js +568 -0
  114. package/dist/platforms/taobao/index.js +56 -0
  115. package/dist/platforms/taobao/web/api.js +96 -0
  116. package/dist/platforms/taobao/web/client.js +102 -0
  117. package/dist/platforms/taobao/web/commands.js +172 -0
  118. package/dist/platforms/taobao/web/im.js +68 -0
  119. package/dist/platforms/taobao/web/normalize.js +103 -0
  120. package/dist/platforms/taobao/web/profile.js +85 -0
  121. package/dist/platforms/taobao/web/resolve.js +51 -0
  122. package/dist/platforms/taobao/web/sign.js +35 -0
  123. package/dist/platforms/tiktok/index.js +120 -0
  124. package/dist/platforms/tiktok/web/api.js +1234 -0
  125. package/dist/platforms/tiktok/web/client.js +362 -0
  126. package/dist/platforms/tiktok/web/commands.js +821 -0
  127. package/dist/platforms/tiktok/web/im.js +133 -0
  128. package/dist/platforms/tiktok/web/jsrun.js +126 -0
  129. package/dist/platforms/tiktok/web/normalize.js +286 -0
  130. package/dist/platforms/tiktok/web/profile.js +169 -0
  131. package/dist/platforms/tiktok/web/resolve.js +156 -0
  132. package/dist/platforms/tiktok/web/sign.js +585 -0
  133. package/dist/platforms/tiktok/web/upload.js +805 -0
  134. package/dist/platforms/tiktok/web/wire.js +490 -0
  135. package/dist/platforms/weibo/index.js +91 -0
  136. package/dist/platforms/weibo/web/api.js +206 -0
  137. package/dist/platforms/weibo/web/client.js +155 -0
  138. package/dist/platforms/weibo/web/commands.js +202 -0
  139. package/dist/platforms/weibo/web/normalize.js +137 -0
  140. package/dist/platforms/weibo/web/profile.js +246 -0
  141. package/dist/platforms/weibo/web/resolve.js +60 -0
  142. package/dist/platforms/weibo/web/sign.js +85 -0
  143. package/dist/platforms/x/index.js +107 -0
  144. package/dist/platforms/x/web/api.js +443 -0
  145. package/dist/platforms/x/web/article.js +212 -0
  146. package/dist/platforms/x/web/client.js +202 -0
  147. package/dist/platforms/x/web/commands.js +363 -0
  148. package/dist/platforms/x/web/graphql.js +108 -0
  149. package/dist/platforms/x/web/normalize.js +235 -0
  150. package/dist/platforms/x/web/profile.js +99 -0
  151. package/dist/platforms/x/web/resolve.js +44 -0
  152. package/dist/platforms/x/web/transaction.js +171 -0
  153. package/dist/platforms/xhs/index.js +101 -0
  154. package/dist/platforms/xhs/web/api.js +278 -0
  155. package/dist/platforms/xhs/web/client.js +540 -0
  156. package/dist/platforms/xhs/web/commands.js +1046 -0
  157. package/dist/platforms/xhs/web/creator-api.js +609 -0
  158. package/dist/platforms/xhs/web/creator.js +203 -0
  159. package/dist/platforms/xhs/web/js.js +194 -0
  160. package/dist/platforms/xhs/web/login.js +230 -0
  161. package/dist/platforms/xhs/web/normalize.js +166 -0
  162. package/dist/platforms/xhs/web/profile.js +127 -0
  163. package/dist/platforms/xhs/web/push.js +361 -0
  164. package/dist/platforms/xhs/web/resolve.js +69 -0
  165. package/dist/platforms/xhs/web/state.js +368 -0
  166. package/dist/platforms/xianyu/index.js +59 -0
  167. package/dist/platforms/xianyu/web/api.js +312 -0
  168. package/dist/platforms/xianyu/web/client.js +134 -0
  169. package/dist/platforms/xianyu/web/commands.js +305 -0
  170. package/dist/platforms/xianyu/web/im.js +73 -0
  171. package/dist/platforms/xianyu/web/normalize.js +123 -0
  172. package/dist/platforms/xianyu/web/profile.js +146 -0
  173. package/dist/platforms/xianyu/web/resolve.js +48 -0
  174. package/dist/platforms/xianyu/web/sign.js +79 -0
  175. package/package.json +77 -4
  176. package/static/douyin/Live.proto +194 -0
  177. package/static/douyin/Request.proto +89 -0
  178. package/static/douyin/Response.proto +46 -0
  179. package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
  180. package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
  181. package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
  182. package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
  183. package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
  184. package/static/douyin/challenge_profile.json +801 -0
  185. package/static/douyin/challenge_template_runner.js +217 -0
  186. package/static/douyin/dtrait_profile.json +66 -0
  187. package/static/douyin/mstoken_common_profile.json +1 -0
  188. package/static/douyin/mstoken_profile.json +214 -0
  189. package/static/jd/h5st5_env.js +750 -0
  190. package/static/jd/h5st5_lib.js +5 -0
  191. package/static/jd/h5st5_server.js +49 -0
  192. package/static/jd/jcap/env/env_core.js +512 -0
  193. package/static/jd/jcap/env/run.js +1307 -0
  194. package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
  195. package/static/jd/package.json +4 -0
  196. package/static/jd/pc_tk_lib.js +1 -0
  197. package/static/jd/pc_tk_server.js +63 -0
  198. package/static/jd/summer_cryptico_runner.js +50 -0
  199. package/static/jd/webm/env/run.js +453 -0
  200. package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
  201. package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
  202. package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
  203. package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
  204. package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
  205. package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
  206. package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
  207. package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
  208. package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
  209. package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
  210. package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
  211. package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
  212. package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
  213. package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
  214. package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
  215. package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
  216. package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
  217. package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
  218. package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
  219. package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
  220. package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
  221. package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
  222. package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
  223. package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
  224. package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
  225. package/static/kuaishou/js/cp-kwf.js +1 -0
  226. package/static/kuaishou/tools/kwf_oracle.js +799 -0
  227. package/static/kuaishou/tools/kws_oracle.js +885 -0
  228. package/static/kuaishou/tools/like_token_oracle.js +70 -0
  229. package/static/taobao/taobao_js_20260407.js +570 -0
  230. package/static/tiktok/Tiktok_Request.json +597 -0
  231. package/static/tiktok/package.json +3 -0
  232. package/static/tiktok/shop_bsid/core.js +1 -0
  233. package/static/tiktok/shop_bsid/env_core.js +512 -0
  234. package/static/tiktok/shop_bsid/loader.js +1 -0
  235. package/static/tiktok/shop_bsid/run.js +753 -0
  236. package/static/tiktok/shop_bsid/sign.js +100 -0
  237. package/static/tiktok/shop_bsid/webmssdk.js +1 -0
  238. package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
  239. package/static/tiktok/signing/env/env_core.js +512 -0
  240. package/static/tiktok/signing/env/sign.js +455 -0
  241. package/static/tiktok/signing/sdk_config.json +230 -0
  242. package/static/tiktok/signing/source/webmssdk.js +1 -0
  243. package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
  244. package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
  245. package/static/x/graphql.json +7777 -0
  246. package/static/x/transaction_l1.json +12 -0
  247. package/static/xhs/package.json +3 -0
  248. package/static/xhs/xhs_core/js/b1.js +210 -0
  249. package/static/xhs/xhs_core/js/mns.js +340 -0
  250. package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
  251. package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
  252. package/static/xhs/xhs_core/js/sign.js +225 -0
  253. package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
  254. package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
  255. package/static/xhs/xhs_core/js/xs_common.js +79 -0
  256. package/static/xhs/xhs_creator/js/profile.js +115 -0
  257. package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
  258. package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
  259. package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
  260. package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
  261. package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
  262. package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
  263. package/static/xhs/xhs_pc/js/b1.js +9 -0
  264. package/static/xhs/xhs_pc/js/deflate.js +320 -0
  265. package/static/xhs/xhs_pc/js/mns.js +4 -0
  266. package/static/xhs/xhs_pc/js/profile.js +141 -0
  267. package/static/xhs/xhs_pc/js/rap.js +173 -0
  268. package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
  269. package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
  270. package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
  271. package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
  272. package/static/xhs/xhs_pc/js/sign.js +6 -0
  273. package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
  274. package/static/xhs/xhs_pc/js/xs_common.js +4 -0
  275. package/static/xianyu/et_f.js +2 -0
  276. package/static/xianyu/gen_tfstk.js +410 -0
  277. package/static/xianyu/goofish_js_version_2.js +568 -0
@@ -0,0 +1,455 @@
1
+ /*
2
+ * Pure TikTok Web signing bridge.
3
+ *
4
+ * The downloaded SDK under source/ is never edited. This process supplies a
5
+ * small browser-shaped environment, initializes the SDK, and captures the URL
6
+ * passed to the native fetch. No captured signature is accepted as input.
7
+ */
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const vm = require('vm');
11
+ const readline = require('readline');
12
+ const env = require('./env_core');
13
+ const _process = process;
14
+
15
+ const nativeCrypto = require('crypto').webcrypto;
16
+ const nativeFetch = typeof global.fetch === 'function' ? global.fetch.bind(global) : null;
17
+ const sourcePath = path.join(__dirname, '../source/webmssdk.js');
18
+ const legacySourcePath = path.join(__dirname, '../source/webmssdk_legacy.js');
19
+ const exPath = path.join(__dirname, '../source/webmssdk_ex.js');
20
+ const cachePath = path.join(__dirname, '../sdk_config.json');
21
+ const defaultConfig = fs.existsSync(cachePath)
22
+ ? JSON.parse(fs.readFileSync(cachePath, 'utf8'))
23
+ : {
24
+ aid: 1988, dfp: false, boe: false, intercept: true,
25
+ enablePathList: ['/webcast.*', '/api/post/item_list/', '/api/comment/list/'],
26
+ region: 'sg-tiktok', apiHost: '', mode: 513, isSDK: false,
27
+ custom: { ttwid: '' },
28
+ };
29
+ // Creator Studio project-post paths are signed by WebMssdk. Upload
30
+ // authorization is a separate unsigned GET and is deliberately not enabled
31
+ // here; adding signatures to it would diverge from Chrome's request.
32
+ const signingPaths = [
33
+ ...(defaultConfig.enablePathList || ['/webcast.*']),
34
+ '/tiktok/web/project/post/v1/',
35
+ '/tiktok/web/project/post_retry/v1/',
36
+ ];
37
+ const runtimeConfig = { ...defaultConfig, enablePathList: signingPaths };
38
+
39
+ function makeStorage(initial = {}) {
40
+ const values = new Map();
41
+ for (const [key, value] of Object.entries(initial || {})) values.set(String(key), String(value));
42
+ return {
43
+ get length() { return values.size; },
44
+ key(i) { return Array.from(values.keys())[i] ?? null; },
45
+ getItem(k) { return values.has(String(k)) ? values.get(String(k)) : null; },
46
+ setItem(k, v) { values.set(String(k), String(v)); },
47
+ removeItem(k) { values.delete(String(k)); },
48
+ clear() { values.clear(); },
49
+ };
50
+ }
51
+
52
+ function buildRuntime(input) {
53
+ const listeners = new Map();
54
+ const referer = String(input.referer || 'https://www.tiktok.com/');
55
+ const requestUrl = new URL(String(input.url));
56
+ const metrics = input.metrics || {};
57
+ const screenWidth = Number(metrics.screen_width || 2560);
58
+ const screenHeight = Number(metrics.screen_height || 1440);
59
+ const availWidth = Number(metrics.avail_width || screenWidth);
60
+ const availHeight = Number(metrics.avail_height || 1392);
61
+ const innerWidth = Number(metrics.inner_width || 1297);
62
+ const innerHeight = Number(metrics.inner_height || 800);
63
+ const outerWidth = Number(metrics.outer_width || 1313);
64
+ const outerHeight = Number(metrics.outer_height || 985);
65
+ const webglExtensions = [
66
+ 'ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_clip_control',
67
+ 'EXT_color_buffer_half_float', 'EXT_depth_clamp', 'EXT_disjoint_timer_query',
68
+ 'EXT_float_blend', 'EXT_frag_depth', 'EXT_polygon_offset_clamp',
69
+ 'EXT_shader_texture_lod', 'EXT_texture_compression_bptc',
70
+ 'EXT_texture_compression_rgtc', 'EXT_texture_filter_anisotropic',
71
+ 'EXT_texture_mirror_clamp_to_edge', 'EXT_sRGB', 'KHR_parallel_shader_compile',
72
+ 'OES_element_index_uint', 'OES_fbo_render_mipmap', 'OES_standard_derivatives',
73
+ 'OES_texture_float', 'OES_texture_float_linear', 'OES_texture_half_float',
74
+ 'OES_texture_half_float_linear', 'OES_vertex_array_object',
75
+ 'WEBGL_blend_func_extended', 'WEBGL_color_buffer_float',
76
+ 'WEBGL_compressed_texture_s3tc', 'WEBGL_compressed_texture_s3tc_srgb',
77
+ 'WEBGL_debug_renderer_info', 'WEBGL_debug_shaders', 'WEBGL_depth_texture',
78
+ 'WEBGL_draw_buffers', 'WEBGL_lose_context', 'WEBGL_multi_draw', 'WEBGL_polygon_mode',
79
+ ];
80
+ const webglValues = new Map([
81
+ [0x0D52, 8], [0x0D56, 24], [0x0D53, 8], [0x8B4D, 32], [0x851C, 16384],
82
+ [0x8DFD, 1024], [0x84E8, 16384], [0x8872, 16], [0x0D33, 16384],
83
+ [0x8DFC, 30], [0x8869, 16], [0x8B4C, 16], [0x8DFB, 4095],
84
+ [0x8B8C, 'WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)'],
85
+ [0x0D57, 0], [0x1F02, 'WebGL 1.0 (OpenGL ES 2.0 Chromium)'],
86
+ [0x9245, 'Google Inc. (NVIDIA)'],
87
+ [0x9246, 'ANGLE (NVIDIA, NVIDIA GeForce RTX 5060 Ti (0x00002D04) Direct3D11 vs_5_0 ps_5_0, D3D11)'],
88
+ [0x84FF, 16],
89
+ ]);
90
+ const webglDebugInfo = { UNMASKED_VENDOR_WEBGL: 0x9245, UNMASKED_RENDERER_WEBGL: 0x9246 };
91
+ const anisotropyInfo = { MAX_TEXTURE_MAX_ANISOTROPY_EXT: 0x84FF };
92
+ const fakePerformance = {
93
+ timeOrigin: Date.now() - 1000,
94
+ now: () => Date.now() - (Date.now() - 1000),
95
+ };
96
+ fakePerformance.timing = {
97
+ navigationStart: fakePerformance.timeOrigin,
98
+ unloadEventStart: 0, unloadEventEnd: 0,
99
+ redirectStart: 0, redirectEnd: 0,
100
+ fetchStart: fakePerformance.timeOrigin + 1,
101
+ domainLookupStart: fakePerformance.timeOrigin + 2,
102
+ domainLookupEnd: fakePerformance.timeOrigin + 3,
103
+ connectStart: fakePerformance.timeOrigin + 3,
104
+ secureConnectionStart: fakePerformance.timeOrigin + 4,
105
+ connectEnd: fakePerformance.timeOrigin + 5,
106
+ requestStart: fakePerformance.timeOrigin + 6,
107
+ responseStart: fakePerformance.timeOrigin + 7,
108
+ responseEnd: fakePerformance.timeOrigin + 8,
109
+ domLoading: fakePerformance.timeOrigin + 9,
110
+ domInteractive: fakePerformance.timeOrigin + 10,
111
+ domContentLoadedEventStart: fakePerformance.timeOrigin + 11,
112
+ domContentLoadedEventEnd: fakePerformance.timeOrigin + 12,
113
+ domComplete: fakePerformance.timeOrigin + 13,
114
+ loadEventStart: fakePerformance.timeOrigin + 14,
115
+ loadEventEnd: fakePerformance.timeOrigin + 15,
116
+ };
117
+ fakePerformance.navigation = { type: 0, redirectCount: 0 };
118
+ fakePerformance.getEntriesByType = fakePerformance.getEntriesByType || (() => []);
119
+ fakePerformance.getEntriesByName = fakePerformance.getEntriesByName || (() => []);
120
+ const fakeWebGL = {
121
+ BLUE_BITS: 0x0D52, DEPTH_BITS: 0x0D56, GREEN_BITS: 0x0D53,
122
+ MAX_COMBINED_TEXTURE_IMAGE_UNITS: 0x8B4D, MAX_CUBE_MAP_TEXTURE_SIZE: 0x851C,
123
+ MAX_FRAGMENT_UNIFORM_VECTORS: 0x8DFD, MAX_RENDERBUFFER_SIZE: 0x84E8,
124
+ MAX_TEXTURE_IMAGE_UNITS: 0x8872, MAX_TEXTURE_SIZE: 0x0D33,
125
+ MAX_VARYING_VECTORS: 0x8DFC, MAX_VERTEX_ATTRIBS: 0x8869,
126
+ MAX_VERTEX_TEXTURE_IMAGE_UNITS: 0x8B4C, MAX_VERTEX_UNIFORM_VECTORS: 0x8DFB,
127
+ SHADING_LANGUAGE_VERSION: 0x8B8C, STENCIL_BITS: 0x0D57, VERSION: 0x1F02,
128
+ getSupportedExtensions() { return webglExtensions.slice(); },
129
+ getContextAttributes() { return { alpha: true, antialias: true, depth: true, desynchronized: false, failIfMajorPerformanceCaveat: false, powerPreference: 'default', premultipliedAlpha: true, preserveDrawingBuffer: false, stencil: false, xrCompatible: false }; },
130
+ getParameter(key) { return webglValues.get(key); },
131
+ getExtension(name) { if (name === 'WEBGL_debug_renderer_info') return webglDebugInfo; if (name === 'EXT_texture_filter_anisotropic') return anisotropyInfo; return {}; },
132
+ };
133
+ const userAgent = String(input.user_agent || input.userAgent ||
134
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
135
+ '(KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36');
136
+ const cookie = String(input.cookie || '');
137
+ const cookieTtwid = (cookie.match(/(?:^|;\s*)ttwid=([^;]*)/) || [])[1] || '';
138
+ const fakeDocument = {
139
+ cookie,
140
+ referrer: referer,
141
+ readyState: 'complete',
142
+ visibilityState: 'visible',
143
+ hidden: false,
144
+ documentElement: { dataset: {}, classList: { contains() { return false; } } },
145
+ head: { appendChild() {}, removeChild() {} },
146
+ body: { appendChild() {}, removeChild() {} },
147
+ createElement(tag) {
148
+ const element = {
149
+ tagName: String(tag).toUpperCase(), style: {}, dataset: {},
150
+ setAttribute() {}, getAttribute() { return null; },
151
+ appendChild() {}, remove() {}, addEventListener() {},
152
+ getContext(type) { return /webgl|experimental-webgl/i.test(String(type)) ? fakeWebGL : null; },
153
+ canPlayType() { return ''; },
154
+ pathname: '', protocol: '', host: '', hostname: '', port: '', search: '', hash: '', href: '',
155
+ };
156
+ return element;
157
+ },
158
+ addEventListener(type, fn) { listeners.set(type, fn); },
159
+ removeEventListener(type) { listeners.delete(type); },
160
+ dispatchEvent(evt) { const fn = listeners.get(evt?.type); if (fn) fn(evt); return true; },
161
+ evaluate() { return null; },
162
+ createEvent(type) {
163
+ const event = { type: String(type || ''), bubbles: false, cancelable: false,
164
+ initEvent(name, bubbles, cancelable) {
165
+ this.type = String(name || ''); this.bubbles = Boolean(bubbles); this.cancelable = Boolean(cancelable);
166
+ }, preventDefault() { this.defaultPrevented = true; }, defaultPrevented: false };
167
+ return event;
168
+ },
169
+ querySelector() { return null; },
170
+ };
171
+ const fakeNavigator = {
172
+ userAgent,
173
+ appCodeName: 'Mozilla', appVersion: userAgent,
174
+ language: String(input.language || 'zh-CN'), languages: ['zh-CN', 'zh', 'en'],
175
+ platform: 'Win32', onLine: true, cookieEnabled: true,
176
+ hardwareConcurrency: 20, deviceMemory: 32, maxTouchPoints: Number(metrics.max_touch_points ?? 10), webdriver: false,
177
+ doNotTrack: null,
178
+ permissions: { query: async () => ({ state: 'prompt', onchange: null }) },
179
+ userAgentData: {
180
+ brands: [
181
+ { brand: 'Google Chrome', version: '153' },
182
+ { brand: 'Not_A Brand', version: '8' },
183
+ { brand: 'Chromium', version: '153' },
184
+ ], mobile: false, platform: 'Windows',
185
+ getHighEntropyValues: async () => ({
186
+ platform: 'Windows',
187
+ platformVersion: String(input.platform_version || '19.0.0'),
188
+ architecture: 'x86', bitness: '64', model: '',
189
+ uaFullVersion: String(input.ua_full_version || '153.0.8010.50'),
190
+ fullVersionList: [
191
+ { brand: 'Google Chrome', version: String(input.ua_full_version || '153.0.8010.50') },
192
+ { brand: 'Not_A Brand', version: '8.0.0.0' },
193
+ { brand: 'Chromium', version: String(input.ua_full_version || '153.0.8010.50') },
194
+ ],
195
+ }),
196
+ },
197
+ plugins: [
198
+ { name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
199
+ { name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
200
+ { name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
201
+ { name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
202
+ { name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
203
+ ],
204
+ mimeTypes: [
205
+ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
206
+ { type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
207
+ ],
208
+ getBattery: async () => ({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 }),
209
+ connection: { effectiveType: '4g', rtt: 50, downlink: 10 },
210
+ };
211
+ const fakeLocation = {
212
+ href: referer, origin: requestUrl.origin, protocol: requestUrl.protocol,
213
+ host: requestUrl.host, hostname: requestUrl.hostname,
214
+ pathname: requestUrl.pathname, search: requestUrl.search, hash: '',
215
+ assign() {}, replace() {}, reload() {}, toString() { return this.href; },
216
+ };
217
+ class FakeXHR {
218
+ open(method, url) { this.method = method; this.url = String(url); this.readyState = 1; }
219
+ setRequestHeader() {}
220
+ addEventListener(type, fn) { this[`on${type}`] = fn; }
221
+ getAllResponseHeaders() { return ''; }
222
+ async send(body) {
223
+ if (nativeFetch && /https:\/\/(?:mssdk|mon)\-[^/]+\.tiktok\.com\//i.test(this.url)) {
224
+ try {
225
+ const response = await nativeFetch(this.url, { method: this.method || 'GET', body });
226
+ this.status = response.status;
227
+ this.responseText = await response.text();
228
+ this.readyState = 4;
229
+ this.onload?.(); this.onreadystatechange?.(); this.onloadend?.();
230
+ return;
231
+ } catch (_) {}
232
+ }
233
+ this.status = 200; this.responseText = '{}'; this.readyState = 4;
234
+ this.onload?.(); this.onreadystatechange?.(); this.onloadend?.();
235
+ }
236
+ }
237
+ class FakeWebSocket { addEventListener() {} send() {} close() {} }
238
+ const fakeWindow = {
239
+ document: fakeDocument, navigator: fakeNavigator, location: fakeLocation,
240
+ screen: { width: screenWidth, height: screenHeight, availWidth, availHeight, colorDepth: 24, pixelDepth: 24 },
241
+ innerWidth, innerHeight, outerWidth, outerHeight,
242
+ devicePixelRatio: Number(metrics.device_pixel_ratio || 1), history: { length: Number(metrics.history_length || 7) },
243
+ localStorage: makeStorage(input.local_storage), sessionStorage: makeStorage(input.session_storage),
244
+ performance: fakePerformance, crypto: nativeCrypto,
245
+ ArrayBuffer, Uint8Array, Uint16Array, Uint32Array,
246
+ Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, DataView,
247
+ atob(value) {
248
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
249
+ const input = String(value).replace(/[\t\n\f\r ]/g, '');
250
+ let output = '';
251
+ for (let i = 0; i < input.length;) {
252
+ const c1 = alphabet.indexOf(input.charAt(i++));
253
+ const c2 = alphabet.indexOf(input.charAt(i++));
254
+ const c3 = alphabet.indexOf(input.charAt(i++));
255
+ const c4 = alphabet.indexOf(input.charAt(i++));
256
+ if (c1 < 0 || c2 < 0 || c3 < 0 || c4 < 0) break;
257
+ const b1 = (c1 << 2) | (c2 >> 4);
258
+ output += String.fromCharCode(b1 & 255);
259
+ if (c3 === 64) continue;
260
+ output += String.fromCharCode(((c2 & 15) << 4) | (c3 >> 2));
261
+ if (c4 === 64) continue;
262
+ output += String.fromCharCode(((c3 & 3) << 6) | c4);
263
+ }
264
+ return output;
265
+ },
266
+ btoa(value) {
267
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
268
+ const input = String(value);
269
+ let output = '';
270
+ for (let i = 0; i < input.length; i += 3) {
271
+ const a = input.charCodeAt(i), b = input.charCodeAt(i + 1), c = input.charCodeAt(i + 2);
272
+ output += alphabet[a >> 2];
273
+ output += alphabet[((a & 3) << 4) | (b >> 4)];
274
+ output += Number.isNaN(b) ? '=' : alphabet[((b & 15) << 2) | (c >> 6)];
275
+ output += Number.isNaN(b) ? '=' : (Number.isNaN(c) ? '=' : alphabet[c & 63]);
276
+ }
277
+ return output;
278
+ },
279
+ Request: global.Request, Headers: global.Headers, URL: global.URL, URLSearchParams: global.URLSearchParams,
280
+ TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder,
281
+ setTimeout, clearTimeout, setInterval, clearInterval,
282
+ requestAnimationFrame: fn => setTimeout(fn, 16),
283
+ matchMedia: () => ({ matches: false, media: '', addListener() {}, removeListener() {} }),
284
+ addEventListener(type, fn) { listeners.set(`window:${type}`, fn); },
285
+ removeEventListener(type) { listeners.delete(`window:${type}`); },
286
+ dispatchEvent(evt) { const fn = listeners.get(`window:${evt?.type}`); if (fn) fn(evt); return true; },
287
+ getComputedStyle: () => ({ getPropertyValue: () => '' }),
288
+ console, XMLHttpRequest: FakeXHR, WebSocket: FakeWebSocket,
289
+ Event: class Event { constructor(type, init = {}) { this.type = type; Object.assign(this, init); } },
290
+ Element: class Element {}, HTMLElement: class HTMLElement {},
291
+ HTMLCanvasElement: class HTMLCanvasElement {},
292
+ };
293
+ fakeWindow.CustomEvent = class CustomEvent extends fakeWindow.Event { constructor(type, init = {}) { super(type, init); this.detail = init.detail; } };
294
+ fakeWindow.MouseEvent = fakeWindow.Event;
295
+ fakeWindow.TouchEvent = fakeWindow.Event;
296
+ fakeWindow.window = fakeWindow; fakeWindow.self = fakeWindow; fakeWindow.top = fakeWindow;
297
+ fakeWindow.parent = fakeWindow; fakeWindow.globalThis = fakeWindow; fakeWindow.dwInfl = {};
298
+ fakeWindow._mssdk = {
299
+ _sharedCache: input.shared_cache || { slardarErrs: [], ttwid: cookieTtwid, tt_webid: '', tt_webid_v2: '', msNewTokenList: [], coreTiming: [] },
300
+ _enablePathList: signingPaths,
301
+ _enablePathListRegex: signingPaths.map(x => new RegExp(x)),
302
+ cacheOpts: {}, opts: [], umode: 513, _loaderInit: true,
303
+ _urlRewriteRules: [], pppt: 2, ets: -1,
304
+ };
305
+ env.init({ window: fakeWindow, document: fakeDocument, navigator: fakeNavigator, location: fakeLocation });
306
+ for (const [key, value] of Object.entries({
307
+ crypto: nativeCrypto, performance: fakePerformance, Request: global.Request, Headers: global.Headers,
308
+ URL: global.URL, URLSearchParams: global.URLSearchParams, TextEncoder: global.TextEncoder,
309
+ TextDecoder: global.TextDecoder, setTimeout, clearTimeout, setInterval, clearInterval,
310
+ requestAnimationFrame: fakeWindow.requestAnimationFrame,
311
+ })) Object.defineProperty(global, key, { value, configurable: true, writable: true });
312
+ const sdkPath = requiredSignatureKeys(input.url).includes('X-Dynosaur') && fs.existsSync(legacySourcePath)
313
+ ? legacySourcePath : sourcePath;
314
+ let sdkSource = fs.readFileSync(sdkPath, 'utf8');
315
+ vm.runInNewContext(sdkSource, fakeWindow, { filename: path.basename(sdkPath) });
316
+ // webmssdk_ex is optional for read-only signing, but loading it keeps the
317
+ // runtime aligned with the browser page when the bundle exposes extensions.
318
+ if (fs.existsSync(exPath)) {
319
+ try { vm.runInNewContext(fs.readFileSync(exPath, 'utf8'), fakeWindow, { filename: 'webmssdk_ex.js' }); }
320
+ catch (_) { /* The extension bundle is optional for this endpoint. */ }
321
+ }
322
+ fakeWindow._mssdk = fakeWindow._mssdk || [];
323
+ fakeWindow._mssdk.cacheOpts = fakeWindow._mssdk.cacheOpts || {};
324
+ fakeWindow._mssdk.cacheOpts[String(runtimeConfig.aid)] = runtimeConfig;
325
+ return { fakeWindow, fakeDocument, requestUrl, userAgent };
326
+ }
327
+
328
+ function assertUnsigned(url) {
329
+ const parsed = new URL(url);
330
+ for (const key of ['X-Dynosaur', 'X-Gnarly', 'X-Bogus']) {
331
+ if (parsed.searchParams.has(key)) throw new Error(`输入 URL 不允许携带静态 ${key}`);
332
+ }
333
+ }
334
+
335
+ function requiredSignatureKeys(url) {
336
+ const pathname = new URL(String(url)).pathname;
337
+ // Creator Studio upload authorization is unsigned; project-post requests
338
+ // carry only msToken/X-Bogus/X-Gnarly. The legacy web API paths additionally
339
+ // carry X-Dynosaur. Never invent a missing field just to satisfy a generic
340
+ // four-field schema.
341
+ if (/\/api\/v1\/video\/upload\/auth\//.test(pathname)) return [];
342
+ if (/\/tiktok\/web\/project\/post(?:_retry)?\/v1\//.test(pathname)) {
343
+ return ['msToken', 'X-Bogus', 'X-Gnarly'];
344
+ }
345
+ return ['X-Dynosaur', 'msToken', 'X-Bogus', 'X-Gnarly'];
346
+ }
347
+
348
+ async function sign(input) {
349
+ if (!input || !input.url) throw new Error('signer 需要完整 url');
350
+ const frontierOnly = input.mode === 'frontier_sign';
351
+ if (!frontierOnly && (!input.headers || !Object.keys(input.headers).length)) throw new Error('signer 需要完整 headers');
352
+ const method = String(input.method || 'GET').toUpperCase();
353
+ if (['POST', 'PUT', 'PATCH'].includes(method) && (input.body === undefined || input.body === null)) throw new Error('signer 需要完整 body');
354
+ assertUnsigned(input.url);
355
+ const { fakeWindow, fakeDocument, requestUrl, userAgent } = buildRuntime(input);
356
+ let captured;
357
+ fakeWindow.fetch = async (...args) => {
358
+ const requested = String(args[0] && args[0].url || args[0]);
359
+ // WebMssdk obtains a public, short-lived rule/config response before it
360
+ // signs the first API request. Let that response be fetched normally;
361
+ // only the target TikTok API call is captured and kept offline.
362
+ if (nativeFetch && /https:\/\/(?:mssdk|mon)\-[^/]+\.tiktok\.com\//i.test(requested)) {
363
+ const opts = args[1] || {};
364
+ return nativeFetch(requested, { method: opts.method || 'GET', headers: opts.headers || {} });
365
+ }
366
+ captured = { url: requested, options: args[1] || {} };
367
+ return { status: 200, headers: { get() { return 'application/json'; }, has() { return false; } }, json: async () => ({ status_code: 0 }), text: async () => '{}' };
368
+ };
369
+ if (!fakeWindow.byted_acrawler || typeof fakeWindow.byted_acrawler.init !== 'function') throw new Error('webmssdk 未导出 byted_acrawler');
370
+ await fakeWindow.byted_acrawler.init(runtimeConfig);
371
+ // The real page may initialize the SDK before these lifecycle events. Our
372
+ // VM starts from an already-complete document, so replay the events once to
373
+ // let the SDK collect DOM, screen, WebGL and timing signals.
374
+ for (const type of ['readystatechange', 'DOMContentLoaded', 'load']) {
375
+ fakeDocument.dispatchEvent(new fakeWindow.Event(type));
376
+ fakeWindow.dispatchEvent(new fakeWindow.Event(type));
377
+ }
378
+ // The browser collects the same asynchronous SDK signals before issuing
379
+ // the first API call. Give the local VM one turn to finish those tasks.
380
+ await new Promise(resolve => setTimeout(resolve, 300));
381
+ if (frontierOnly) {
382
+ if (typeof fakeWindow.byted_acrawler.frontierSign !== 'function') throw new Error('SDK 未导出 frontierSign');
383
+ if (input.stub && !/^[0-9a-f]{32}$/.test(String(input.stub))) throw new Error('IM X-MS-STUB 必须为 32 位小写 MD5');
384
+ const result = fakeWindow.byted_acrawler.frontierSign(
385
+ input.stub ? { 'X-MS-STUB': String(input.stub) } : { 'X-MS-PAYLOAD': '' }
386
+ );
387
+ const marker = result && result['X-Bogus'];
388
+ if (typeof marker !== 'string' || marker.length !== 16) throw new Error('frontierSign 未产生浏览器长度 16 的 X-Bogus');
389
+ return { values: { 'X-Bogus': marker }, lengths: { 'X-Bogus': marker.length } };
390
+ }
391
+ const headers = { ...(input.headers || {}) };
392
+ const options = { method, credentials: 'include', headers };
393
+ if (input.body !== undefined && input.body !== null && method !== 'GET' && method !== 'HEAD') options.body = String(input.body);
394
+ await fakeWindow.fetch(String(input.url), options);
395
+ if (!captured?.url) throw new Error('SDK 未产生 native fetch URL');
396
+ // The SDK keeps an internal msToken slot. When the caller already placed
397
+ // the browser cookie token in the unsigned URL, this VM slot is empty and
398
+ // the wrapper appends a second `msToken=` marker. Drop only that empty
399
+ // marker; the non-empty browser token and all calculated fields stay intact.
400
+ const inputToken = new URL(String(input.url)).searchParams.get('msToken') || '';
401
+ let normalizedUrl = String(captured.url).replace(/&msToken=(?=&(?:X-Bogus|X-Gnarly)=)/g, '');
402
+ // Some browser storage snapshots expose an old msToken to the SDK in
403
+ // addition to the fresh token in the unsigned URL. Keep exactly one
404
+ // caller-supplied token while retaining the SDK's calculated signatures.
405
+ let seenMsToken = 0;
406
+ normalizedUrl = normalizedUrl.replace(/([?&])msToken=[^&]*/g, (match, prefix) => {
407
+ if (seenMsToken++ > 0) return '';
408
+ return `${prefix}msToken=${encodeURIComponent(inputToken)}`;
409
+ });
410
+ // Match Chrome's query insertion order when the caller supplied the
411
+ // cookie token before the SDK fields: X-Dynosaur, msToken, X-Bogus,
412
+ // X-Gnarly. This is ordering only; the value is still the caller's token.
413
+ const tokenMatch = normalizedUrl.match(/&msToken=([^&]*)/);
414
+ const dynMatch = normalizedUrl.match(/&X-Dynosaur=[^&]*/);
415
+ if (tokenMatch && dynMatch && tokenMatch.index < dynMatch.index) {
416
+ const tokenPart = tokenMatch[0];
417
+ normalizedUrl = normalizedUrl.replace(tokenPart, '');
418
+ const newDyn = normalizedUrl.match(/&X-Dynosaur=[^&]*/);
419
+ if (newDyn) normalizedUrl = normalizedUrl.slice(0, newDyn.index + newDyn[0].length) + tokenPart + normalizedUrl.slice(newDyn.index + newDyn[0].length);
420
+ }
421
+ const signed = new URL(normalizedUrl);
422
+ const allKeys = ['X-Dynosaur', 'msToken', 'X-Bogus', 'X-Gnarly'];
423
+ const requiredKeys = requiredSignatureKeys(input.url);
424
+ const values = Object.fromEntries(allKeys.map(key => [key, signed.searchParams.get(key) || '']));
425
+ if (requiredKeys.includes('msToken') && signed.searchParams.getAll('msToken').length !== 1) {
426
+ throw new Error('SDK 结果中的 msToken 字段数量不对齐');
427
+ }
428
+ if (inputToken && values.msToken !== inputToken) throw new Error('SDK 结果的 msToken 未与 unsigned 请求对齐');
429
+ const missing = requiredKeys.filter(key => !values[key]);
430
+ if (missing.length) throw new Error(`SDK 纯计算后仍缺少字段: ${missing.join(', ')}`);
431
+ const lengths = Object.fromEntries(Object.entries(values).map(([key, value]) => [key, value.length]));
432
+ if (input.expected_lengths) {
433
+ for (const [key, expected] of Object.entries(input.expected_lengths)) {
434
+ if (lengths[key] !== Number(expected)) throw new Error(`${key} 长度 ${lengths[key]} != 浏览器证据 ${expected}`);
435
+ }
436
+ }
437
+ return { url: normalizedUrl, values, lengths, method, body: options.body || '', user_agent: userAgent, origin: requestUrl.origin };
438
+ }
439
+
440
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
441
+ // Do not call process.exit from the close event before the async signer has
442
+ // handled the final line. The browser SDK schedules background timers, so we
443
+ // serialize input lines and explicitly terminate only after the queue drains.
444
+ let pending = Promise.resolve();
445
+ rl.on('line', line => {
446
+ if (!line.trim()) return;
447
+ pending = pending.then(async () => {
448
+ try { _process.stdout.write(JSON.stringify({ ok: true, result: await sign(JSON.parse(line)) }) + '\n'); }
449
+ catch (err) { _process.stdout.write(JSON.stringify({ ok: false, error: String(err && err.message || err), stack: String(err && err.stack || '') }) + '\n'); }
450
+ });
451
+ });
452
+ rl.on('close', async () => {
453
+ await pending;
454
+ _process.exit(0);
455
+ });