UNPKG

@mochabug/adapt-web

Version:

Browser client library for Adapt automation platform

2 lines (1 loc) 5.49 kB
typeof window<"u"&&!window.CAP_CUSTOM_WASM_URL&&(window.CAP_CUSTOM_WASM_URL=new URL("https://cdn.mochabug.com/adapt/web/1.0.1-rc.33/cap_wasm_bg.wasm",window.location.href).href);import{timestampDate}from"@bufbuild/protobuf/wkt";const adapterRegistrations=new Map,verificationTokens=new Map,redeemedChallengeExpirations=new Map;function automationIdFromCapUrl(url){const urlStr=url.toString();let pathname=urlStr;try{pathname=new URL(urlStr,typeof window<"u"?window.location.href:void 0).pathname}catch{}const match=pathname.match(/(?:^|\/)adapt-cap-([^/]+)\/(?:challenge|redeem)$/);if(!match?.[1])return null;try{return decodeURIComponent(match[1])}catch{return match[1]}}function adapterForRequest(url){const automationId=automationIdFromCapUrl(url);if(automationId){const registration=adapterRegistrations.get(automationId);return registration?{automationId,client:registration.client}:null}if(adapterRegistrations.size===1){const first=adapterRegistrations.entries().next();if(!first.done){const[singleAutomationId,registration]=first.value;return{automationId:singleAutomationId,client:registration.client}}}return null}async function createChallenge(client,id){const response=await client.createChallenge({id}),instrumentation=response.instrumentation;return{count:response.count,size:response.size,difficulty:response.difficulty,expires:response.expires?timestampDate(response.expires):new Date,token:response.token,verificationToken:response.verificationToken,...instrumentation?{instrumentation}:{}}}async function redeemChallenge(client,id,verificationToken,solutions,options={}){const request={id,verificationToken,solutions,...options.instrumentation?{instrumentation:options.instrumentation}:{},...options.instrumentationBlocked?{instrumentationBlocked:!0}:{},...options.instrumentationTimeout?{instrumentationTimeout:!0}:{}},response=await client.redeemChallenge(request);return{token:response.token,expires:response.expires?timestampDate(response.expires):new Date}}function takeRedeemedChallengeExpiration(automationId){const expires=redeemedChallengeExpirations.get(automationId)??null;return expires&&redeemedChallengeExpirations.delete(automationId),expires}typeof window<"u"&&(window.CAP_CUSTOM_FETCH=async(url,options)=>{const urlStr=url.toString();if(urlStr.endsWith("/challenge")){const adapter=adapterForRequest(url);if(!adapter)return new Response(JSON.stringify({error:"Cap adapter not initialized"}),{status:500,headers:{"Content-Type":"application/json"}});try{const challengeInfo=await createChallenge(adapter.client,adapter.automationId);return verificationTokens.set(adapter.automationId,challengeInfo.verificationToken),redeemedChallengeExpirations.delete(adapter.automationId),new Response(JSON.stringify({challenge:{c:challengeInfo.count,s:challengeInfo.size,d:challengeInfo.difficulty},token:challengeInfo.token,expires:challengeInfo.expires.toISOString(),...challengeInfo.instrumentation?{instrumentation:challengeInfo.instrumentation}:{}}),{status:200,headers:{"Content-Type":"application/json"}})}catch(error){return new Response(JSON.stringify({error:String(error)}),{status:500,headers:{"Content-Type":"application/json"}})}}if(urlStr.endsWith("/redeem")){const adapter=adapterForRequest(url);if(!adapter)return new Response(JSON.stringify({error:"Cap adapter not initialized"}),{status:500,headers:{"Content-Type":"application/json"}});const verificationToken=verificationTokens.get(adapter.automationId);if(!verificationToken)return new Response(JSON.stringify({error:"No verification token found - challenge must be created first"}),{status:400,headers:{"Content-Type":"application/json"}});try{const body=options?.body?JSON.parse(options.body):{},solutions=(body.solutions||[]).map(s=>BigInt(s)),instrumentationBlocked=body.instr_blocked===!0||body.instr?.__blocked===!0,instrumentationTimeout=body.instr_timeout===!0||body.instr?.__timeout===!0,instrumentation=body.instr&&!instrumentationBlocked&&!instrumentationTimeout?body.instr:void 0,redeemed=await redeemChallenge(adapter.client,adapter.automationId,verificationToken,solutions,{...instrumentation?{instrumentation}:{},...instrumentationBlocked?{instrumentationBlocked}:{},...instrumentationTimeout?{instrumentationTimeout}:{}});return redeemedChallengeExpirations.set(adapter.automationId,redeemed.expires),verificationTokens.delete(adapter.automationId),new Response(JSON.stringify({success:!0,token:redeemed.token,expires:redeemed.expires.toISOString()}),{status:200,headers:{"Content-Type":"application/json"}})}catch(error){return new Response(JSON.stringify({error:String(error)}),{status:500,headers:{"Content-Type":"application/json"}})}}return fetch(url,options)});function setupCapAdapter(client,automationId){const registration=adapterRegistrations.get(automationId);registration?(registration.client=client,registration.references+=1):adapterRegistrations.set(automationId,{client,references:1}),redeemedChallengeExpirations.delete(automationId)}function cleanupCapAdapter(automationId){if(!automationId){adapterRegistrations.clear(),verificationTokens.clear(),redeemedChallengeExpirations.clear();return}const registration=adapterRegistrations.get(automationId);if(registration&&registration.references>1){registration.references-=1;return}adapterRegistrations.delete(automationId),verificationTokens.delete(automationId),redeemedChallengeExpirations.delete(automationId)}export{cleanupCapAdapter,createChallenge,redeemChallenge,setupCapAdapter,takeRedeemedChallengeExpiration};