import {linkShared} from './helpers/shared-modules.mjs'; import {test} from 'node:test'; import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import ts from 'typescript'; async function moduleUrl(path,imports={}){ let code=ts.transpileModule(await readFile(new URL('../src/lib/'+path+'.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText; for(const [name,url] of Object.entries(imports))code=code.replace("'"+name+"'",JSON.stringify(url)); return 'data:text/javascript;base64,'+Buffer.from(linkShared(code)).toString('base64'); } const uaUrl=await moduleUrl('api/user-agent'); const {buildUserAgent,buildSdkUserAgent,userAgentSid}=await import(uaUrl); const sdkUrl=await moduleUrl('api/sdk-headers'); const {clientHeaders}=await import(await moduleUrl('api/client-headers',{'./user-agent':uaUrl,'./sdk-headers':sdkUrl})); // Generated by running the reference Go BuildXUserAgent function without changing its logic. const samples=JSON.parse(await readFile(new URL('./fixtures/request-user-agents.json',import.meta.url),'utf8')); for(const sample of samples)test('reference Go UA output matches '+sample.name,()=>{ assert.equal(buildUserAgent(sample.ua,sample.sid,sample.version),sample.expected); }); test('existing X-User-Agent wins over SDK fields and network headers remain separate from credentials',()=>{ const xua='DevID=client-device;DevType=iPhone;SysType=h5_ios;Ver=2.0;BuildID=client;sid=client-sid'; const fields={sid:'sdk-sid',device_id:'sdk-device',device:'Android',device_fingerprint:'raw',fp_version:'3',app_version:'1.0.0'}; const headers=clientHeaders(new Headers({'User-Agent':' Browser UA ','X-User-Agent':xua,'X-Real-IP':'203.0.113.9','X-Forwarded-For':'203.0.113.8, 10.0.0.1','X-SDK-Common':Buffer.from(JSON.stringify(fields)).toString('base64'),Cookie:'drama_session=private',Authorization:'private'})); assert.equal(headers['User-Agent'],'Browser UA');assert.equal(headers['X-User-Agent'],xua);assert.equal(headers['X-Real-IP'],'203.0.113.9'); assert.deepEqual(JSON.parse(Buffer.from(headers['X-SDK-Common'],'base64')),{sid:'sdk-sid',device_id:'sdk-device',app_version:'1.0.0'}); for(const key of ['Cookie','Authorization','X-Forwarded-For'])assert.equal(headers[key],undefined); }); test('business drama_device pins DevID so SDK fingerprints cannot invalidate the guest token',()=>{ const xua='DevID=sdk-device;DevType=PC;SysType=h5_pc;Ver=1.0.0;BuildID=web;sid=sdk-sid'; const headers=clientHeaders(new Headers({'User-Agent':'Mozilla/5.0','X-User-Agent':xua,Cookie:'drama_session=secret; drama_device=business-device'})); assert.equal(headers['X-User-Agent'],'DevID=business-device;DevType=PC;SysType=h5_pc;Ver=1.0.0;BuildID=web;sid=sdk-sid'); const sample=samples.find(item=>item.name==='iphone'); const fallback=clientHeaders(new Headers({'User-Agent':sample.ua,Cookie:'sid=reference-sid; drama_device=only-device'})); assert.equal(fallback['X-User-Agent'],sample.expected,'SSR UA without DevID stays unchanged'); }); test('SSR and media fallback use the UA, SID cookie and first forwarded IP without a fabricated identity',()=>{ const sample=samples.find(item=>item.name==='iphone'); const headers=clientHeaders(new Headers({'User-Agent':sample.ua,'X-Real-IP':'invalid','X-Forwarded-For':'203.0.113.7, 10.0.0.1',Cookie:'sid=reference-sid; drama_device=business-device'})); assert.equal(headers['X-User-Agent'],sample.expected);assert.equal(headers['X-Real-IP'],'203.0.113.7'); const empty=clientHeaders(undefined);assert.equal(empty['User-Agent'],undefined);assert.ok(!empty['X-User-Agent'].includes('DevID=')); assert.equal(userAgentSid('sid=first; _s_token_=second'),'first'); assert.equal(userAgentSid('_s_token_={"sid":"json-sid"}'),'json-sid'); assert.equal(userAgentSid('_s_token_=plain-sid'),'plain-sid'); }); test('browser SDK XUA uses the reference field order and cleans header delimiters without altering the snapshot',()=>{ const fields={device_id:'sdk-device',device:'Android',app_version:'2.3',device_brand:'Sam;sung',device_model:'model=1',sid:'sdk-sid'}; assert.equal(buildSdkUserAgent(fields),'DevID=sdk-device;DevType=Android;SysType=h5_android;Ver=2.3;BuildID=web;device_brand=Sam sung;device_model=model 1;sid=sdk-sid'); assert.equal(fields.device_brand,'Sam;sung');assert.equal(fields.device_model,'model=1'); });