@getsupertab/supertab-connect-sdk 1.4.1 → 1.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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var ie=Object.create;var k=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var ce=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,ue=Object.prototype.hasOwnProperty;var de=(n,e)=>{for(var r in e)k(n,r,{get:e[r],enumerable:!0})},V=(n,e,r,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ce(e))!ue.call(n,s)&&s!==r&&k(n,s,{get:()=>e[s],enumerable:!(t=ae(e,s))||t.enumerable});return n};var x=(n,e,r)=>(r=n!=null?ie(le(n)):{},V(e||!n||!n.__esModule?k(r,"default",{value:n,enumerable:!0}):r,n)),fe=n=>V(k({},"__esModule",{value:!0}),n);var ve={};de(ve,{CDNStatusDescription:()=>_,EnforcementMode:()=>C,HandlerAction:()=>S,LicenseTokenInvalidReason:()=>I,SupertabConnect:()=>F,defaultBotDetector:()=>ne});module.exports=fe(ve);var C=(t=>(t.DISABLED="disabled",t.SOFT="soft",t.STRICT="strict",t))(C||{}),I=(l=>(l.MISSING_TOKEN="missing_license_token",l.INVALID_HEADER="invalid_license_header",l.INVALID_ALG="invalid_license_algorithm",l.INVALID_PAYLOAD="invalid_license_payload",l.INVALID_ISSUER="invalid_license_issuer",l.SIGNATURE_VERIFICATION_FAILED="license_signature_verification_failed",l.EXPIRED="license_token_expired",l.INVALID_AUDIENCE="invalid_license_audience",l.SERVER_ERROR="server_error",l))(I||{}),E="stc-backend",S=(r=>(r.ALLOW="allow",r.BLOCK="block",r))(S||{}),_=(o=>(o.Unauthorized="Unauthorized",o.PaymentRequired="Payment Required",o.Forbidden="Forbidden",o.ServiceUnavailable="Service Unavailable",o.Error="Error",o))(_||{});function L(n){let e=null;return()=>(e||(e=n()),e)}var B=L(()=>import("jose/jwt/verify")),v=L(()=>import("jose/jwt/decode")),J=L(()=>import("jose/decode/protected_header")),pe=L(()=>import("jose/key/import")),he=L(()=>import("jose/jwt/sign"));function W(n,e){let r=!1,t=n;t.endsWith("$")&&(r=!0,t=t.slice(0,-1));let s=t.includes("*"),i=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*"),a;return r?a=`^${i}$`:s?a=`^${i}`:t==="/"?a="^/":a=`^${i}(/|$)`,new RegExp(a).test(e)?t.replace(/\*/g,"").length:-1}var R="supertab-connect-sdk-typescript/1.4.1";var U=new Map,D=15*60,A=new Map;function ge(){let n=Math.floor(Date.now()/1e3);for(let[e,r]of A)n-r.fetchedAt>=D&&A.delete(e)}function me(n,e){let r=U.get(n);if(!r)return null;let t=Math.floor(Date.now()/1e3);return r.exp>t+30?(e&&console.debug(`Using cached license token (expires in ${r.exp-t}s)`),r.token):(e&&console.debug("Cached license token expired or expiring soon, refreshing"),U.delete(n),null)}async function Re(n,e,r){try{let t=await fetch(n,e);if(!t.ok){let o=await t.text().catch(()=>""),i=`Failed to obtain license token: ${t.status} ${t.statusText}${o?` - ${o}`:""}`;throw new Error(i)}let s;try{s=await t.json()}catch(o){throw r&&console.error("Failed to parse license token response as JSON:",o),new Error("Failed to parse license token response as JSON")}if(!s?.access_token)throw new Error("License token response missing access_token");return s.access_token}catch(t){throw r&&console.error("Error generating license token:",t),t}}async function ye(n,e){let r=new URL(n).origin,t=A.get(r);if(t){let a=Math.floor(Date.now()/1e3);if(a-t.fetchedAt<D)return e&&console.debug(`Using cached license.xml for origin ${r} (expires in ${D-(a-t.fetchedAt)}s)`),t.xml;e&&console.debug(`Cached license.xml for origin ${r} expired, re-fetching`),A.delete(r)}let s=`${r}/license.xml`,o=await fetch(s,{headers:{"User-Agent":R}});if(!o.ok)throw e&&console.error(`Failed to fetch license.xml from ${s}: ${o.status}`),new Error(`Failed to fetch license.xml from ${s}: ${o.status}`);let i=await o.text();return e&&console.debug("Fetched license.xml from",s),ge(),A.set(r,{xml:i,fetchedAt:Math.floor(Date.now()/1e3)}),i}function be(n,e){let r=[],t=/<content\s([^>]*)>([\s\S]*?)<\/content>/gi,s=/url\s*=\s*"([^"]*)"/i,o=/server\s*=\s*"([^"]*)"/i,i=/<license[^>]*>[\s\S]*?<\/license>/i,a=0,c;for(;(c=t.exec(n))!==null;){a++;let l=c[1],d=c[2],f=l.match(s),p=l.match(o),g=d.match(i);if(f&&p&&g)r.push({urlPattern:f[1],server:p[1],licenseXml:g[0]});else if(e){let b=[!f&&"url",!p&&"server",!g&&"<license>"].filter(Boolean).join(", ");console.debug(`Skipping <content> element #${a}: missing ${b}`)}}return e&&console.debug(`Found ${a} <content> element(s), ${r.length} valid`),r}function Ee(n,e,r){let t=new URL(e),s=t.host,o=t.pathname;r&&console.debug(`Matching resource URL: ${e} (host=${s}, path=${o})`);let i=null,a=-1;for(let c of n){let l;if(c.urlPattern.startsWith("/"))l=c.urlPattern;else{let p;try{p=new URL(c.urlPattern)}catch{r&&console.debug(`Skipping block with invalid URL pattern: ${c.urlPattern}`);continue}if(p.host!==s){r&&console.debug(`Skipping block: host mismatch (pattern=${p.host}, resource=${s})`);continue}l=p.pathname}if(l===o)return r&&console.debug(`Exact match found: ${c.urlPattern}`),c;let f=W(l,o);f>a&&(a=f,i=c)}return r&&console.debug(i?`Wildcard match found: ${i.urlPattern} (specificity=${a})`:`No matching content block found for ${e}`),i}async function M({clientId:n,clientSecret:e,resourceUrl:r,debug:t}){let s=await ye(r,t);t&&console.debug(`Fetched license.xml (${s.length} chars)`);let o=be(s,t);if(o.length===0)throw t&&console.error("No valid <content> elements with <license> found in license.xml"),new Error("No valid <content> elements with <license> found in license.xml");let i=Ee(o,r,t);if(!i){if(t){let g=o.map(b=>b.urlPattern).join(", ");console.error(`No <content> element matches resource URL: ${r}. Available patterns: ${g}`)}throw new Error(`No <content> element in license.xml matches resource URL: ${r}`)}t&&(console.debug("Matched content block for resource URL:",r),console.debug("Using license XML:",i.licenseXml));let a=`${n}:${i.server}:${i.urlPattern}`,c=me(a,t);if(c)return c;let l=i.server+"/token";t&&console.debug(`Requesting license token from ${l}`);let d=new URLSearchParams({grant_type:"client_credentials",license:i.licenseXml,resource:i.urlPattern}),f={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${n}:${e}`),"User-Agent":R},body:d.toString()},p=await Re(l,f,t);try{let{decodeJwt:g}=await v(),b=g(p);b.exp&&U.set(a,{token:p,exp:b.exp})}catch{t&&console.debug("Failed to decode token for caching, skipping cache")}return p}var P=new Map,we=48*60*60*1e3,w=class extends Error{constructor(e){super(`No matching platform key found: ${e}`),this.name="JwksKeyNotFoundError"}};function xe(){let n={method:"GET",headers:{"User-Agent":R}};return globalThis.fastly&&(n={...n,backend:E}),n}async function Se({cacheKey:n,url:e,debug:r,failureMessage:t,logLabel:s}){let o=P.get(n);if(o&&Date.now()-o.cachedAt<we)return o.data;try{let i=await fetch(e,xe());if(!i.ok)throw new Error(`${t}: ${i.status}`);let a=await i.json();return P.set(n,{data:a,cachedAt:Date.now()}),a}catch(i){throw r&&console.error(s,i),i}}async function j(n,e){let r=`${n}/.well-known/jwks.json/platform`;return e&&console.debug(`Fetching platform JWKS from URL: ${r}`),Se({cacheKey:"platform_jwks",url:r,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}function G(){P.clear()}async function X({apiKey:n,baseUrl:e,eventName:r,properties:t,licenseId:s,debug:o=!1}){let i={event_name:r,license_id:s,properties:t};try{let a={method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":R},body:JSON.stringify(i)};globalThis.fastly&&(a={...a,backend:E});let c=await fetch(`${e}/events`,a);!c.ok&&o&&console.error(`Failed to record event: ${c.status}`)}catch(a){o&&console.error("Error recording event:",a)}}var Le=new Set(["authorization","cookie","set-cookie","proxy-authorization","x-api-key","x-amz-security-token","user-agent","x-license-auth"]);function z(n){let e={};for(let[r,t]of Object.entries(n)){let s=r.toLowerCase();Le.has(s)||(e[`h_${s}`]=t)}return e}var T=n=>n.trim().replace(/\/+$/,"");function y(n){switch(n){case"missing_license_token":return"Authorization header missing or malformed";case"invalid_license_algorithm":return"Unsupported token algorithm";case"license_token_expired":return"The license token has expired";case"license_signature_verification_failed":return"The license token signature is invalid";case"invalid_license_header":return"The license token header is malformed";case"invalid_license_payload":return"The license token payload is malformed";case"invalid_license_issuer":return"The license token issuer is not recognized";case"invalid_license_audience":return"The license does not grant access to this resource";case"server_error":return"The server encountered an error validating the license";default:return"License token missing, expired, revoked, or malformed"}}async function q({licenseToken:n,requestUrl:e,supertabBaseUrl:r,debug:t}){let{decodeProtectedHeader:s}=await J(),{decodeJwt:o}=await v(),{jwtVerify:i}=await B();if(!n)return{valid:!1,reason:"missing_license_token",error:y("missing_license_token")};let a;try{a=s(n)}catch(h){return t&&console.error("Invalid license JWT header:",h),{valid:!1,reason:"invalid_license_header",error:y("invalid_license_header")}}if(a.alg!=="ES256")return t&&console.error("Unsupported license JWT alg:",a.alg),{valid:!1,reason:"invalid_license_algorithm",error:y("invalid_license_algorithm")};let c;try{c=o(n)}catch(h){return t&&console.error("Invalid license JWT payload:",h),{valid:!1,reason:"invalid_license_payload",error:y("invalid_license_payload")}}let l=c.license_id,d=c.iss,f=d?T(d):void 0,p=T(r);if(!f||!f.startsWith(p))return t&&console.error("License JWT issuer is missing or malformed:",d),{valid:!1,reason:"invalid_license_issuer",error:y("invalid_license_issuer"),licenseId:l};let g=Array.isArray(c.aud)?c.aud.filter(h=>typeof h=="string"):typeof c.aud=="string"?[c.aud]:[],b=T(e);if(!g.some(h=>{let m=T(h);return m?b.startsWith(m):!1}))return t&&console.error("License JWT audience does not match request URL:",c.aud),{valid:!1,reason:"invalid_license_audience",error:y("invalid_license_audience"),licenseId:l};let H=async()=>{let h;try{h=await j(r,t)}catch(m){return t&&console.error("Failed to fetch platform JWKS:",m),{valid:!1,reason:"server_error",error:y("server_error"),licenseId:l}}try{let se=await i(n,async $=>{let K=h.keys.find(oe=>oe.kid===$.kid);if(!K)throw new w($.kid);return K},{issuer:d,algorithms:[a.alg],clockTolerance:"1m"});return{valid:!0,licenseId:l,payload:se.payload}}catch(m){if(t&&console.error("License JWT verification failed:",m),m instanceof w)throw m;return m instanceof Error&&m.message?.includes("exp")?{valid:!1,reason:"license_token_expired",error:y("license_token_expired"),licenseId:l}:{valid:!1,reason:"license_signature_verification_failed",error:y("license_signature_verification_failed"),licenseId:l}}};try{return await H()}catch(h){if(h instanceof w)return t&&console.debug("Key not found in cached JWKS, clearing cache and retrying..."),G(),await H();throw h}}function Y({requestUrl:n}){try{let e=new URL(n);return`${e.protocol}//${e.host}/license.xml`}catch(e){return console.error("[SupertabConnect] generateLicenseLink failed to parse URL:",e),"/license.xml"}}function Q(n){let e=Y({requestUrl:n});return{action:"allow",headers:{Link:`<${e}>; rel="license"; type="application/rsl+xml"`,"X-RSL-Status":"token_required","X-RSL-Reason":"missing"}}}function Ae(n){switch(n){case"missing_license_token":case"invalid_license_algorithm":return{rslError:"invalid_request",status:401};case"license_token_expired":case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":case"invalid_license_issuer":return{rslError:"invalid_token",status:401};case"invalid_license_audience":return{rslError:"insufficient_scope",status:403};case"server_error":return{rslError:"server_error",status:503};default:return{rslError:"invalid_token",status:401}}}function ke(n){return n.replace(/[\r\n]/g,"").replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function N({reason:n,error:e,requestUrl:r}){let{rslError:t,status:s}=Ae(n),o=ke(e),i=Y({requestUrl:r});return{action:"block",status:s,body:`Access to this resource requires a valid license token. Error: ${t} - ${e}`,headers:{"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${t}", error_description="${o}"`,Link:`<${i}>; rel="license"; type="application/rsl+xml"`}}}function Ie(){let n={method:"GET",headers:{"User-Agent":R}};return globalThis.fastly&&(n={...n,backend:E}),n}async function Z(n,e){try{let r=`${n}/merchants/systems/${e}/license.xml`,t=await fetch(r,Ie());if(!t.ok)return new Response("License not found",{status:404});let s=await t.text();return new Response(s,{status:200,headers:new Headers({"Content-Type":"application/rsl+xml"})})}catch(r){return console.error("[SupertabConnect] hostRSLicenseXML failed:",r),new Response("Bad Gateway",{status:502})}}async function O(n){let e=await q({licenseToken:n.token,requestUrl:n.url,supertabBaseUrl:n.supertabBaseUrl,debug:n.debug}),r=X({apiKey:n.apiKey,baseUrl:n.supertabBaseUrl,eventName:e.valid?"license_used":e.reason,properties:{page_url:n.url,user_agent:n.userAgent,sdk_user_agent:R,verification_status:e.valid?"valid":"invalid",verification_reason:e.valid?"success":e.reason,...z(n.requestHeaders??{})},licenseId:e.licenseId,debug:n.debug});return n.ctx?.waitUntil?n.ctx.waitUntil(r):await r,e}async function ee(n,e,r){let t=await n.handleRequest(e,r);if(t.action==="block")return new Response(t.body,{status:t.status,headers:new Headers(t.headers)});let s=await fetch(e);if(t.headers){let o=new Response(s.body,s);for(let[i,a]of Object.entries(t.headers))o.headers.set(i,a);return o}return s}async function te(n,e,r,t){let s=e.headers.get("x-original-request-url")||e.url;if(t&&new URL(s).pathname==="/license.xml")return await Z(t.baseUrl,t.merchantSystemUrn);let o=new Request(s,{method:e.method,headers:e.headers}),i=await n.handleRequest(o);if(i.action==="block")return new Response(i.body,{status:i.status,headers:new Headers(i.headers)});let a=await fetch(e,{backend:r});if(i.headers){let c=new Response(a.body,a);for(let[l,d]of Object.entries(i.headers))c.headers.set(l,d);return c}return a}function _e(n){switch(n){case 401:return"Unauthorized";case 402:return"Payment Required";case 403:return"Forbidden";case 503:return"Service Unavailable";default:return"Error"}}async function re(n,e){let r=e.Records[0].cf.request,t=r.headers?.["x-original-request-url"]?.[0]?.value,s=`${r.headers.host[0].value}${r.uri}`,o=`https://${t||s}${r.querystring?"?"+r.querystring:""}`,i=new Headers;Object.entries(r.headers).forEach(([l,d])=>{d.forEach(({value:f})=>i.append(l,f))});let a=new Request(o,{method:r.method,headers:i}),c=await n.handleRequest(a);if(c.action==="block"){let l={};return Object.entries(c.headers).forEach(([d,f])=>{l[d.toLowerCase()]=[{key:d,value:f}]}),{status:c.status.toString(),statusDescription:_e(c.status),headers:l,body:c.body}}return r}function ne(n){let e=n.headers.get("User-Agent")||"",r=n.headers.get("accept")||"",t=n.headers.get("sec-ch-ua"),s=n.headers.get("accept-language"),o=n.cf?.botManagement?.score,i=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],a=e.toLowerCase(),c=i.some(g=>a.includes(g)),l=a.includes("headless")||a.includes("puppeteer")||!t,d=!a.includes("headless")&&!a.includes("puppeteer")&&!t,f=!r||!s,p=typeof o=="number"&&o<30;return(a.includes("safari")||a.includes("mozilla"))&&l&&d?!1:c||l||f||p}var u=class u{constructor(e,r=!1){if(!r&&u._instance){if(e.apiKey!==u._instance.apiKey)throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(r&&u._instance&&u.resetInstance(),!e.apiKey)throw new Error("Missing required configuration: apiKey is required");this.apiKey=e.apiKey,this.enforcement=e.enforcement??"soft",this.botDetector=e.botDetector,this.debug=e.debug??!1,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}static getBaseUrl(){return u.baseUrl}static async verify(e){let r=e.baseUrl??u.baseUrl,t=await q({licenseToken:e.token,requestUrl:e.resourceUrl,supertabBaseUrl:r,debug:e.debug??!1});return t.valid?{valid:!0}:{valid:!1,error:t.error}}async verifyAndRecord(e){let r=await O({token:e.token,url:e.resourceUrl,userAgent:e.userAgent??"unknown",supertabBaseUrl:u.baseUrl,debug:e.debug??this.debug,apiKey:this.apiKey,ctx:e.ctx,requestHeaders:e.requestHeaders});return r.valid?{valid:!0}:{valid:!1,error:r.error}}async handleRequest(e,r){let t=e.headers.get("Authorization")||"",s=t.startsWith("License ")?t.slice(8):null,o=e.url,i=e.headers.get("User-Agent")||"unknown";if(s){if(this.enforcement==="disabled")return{action:"allow"};let c=await O({token:s,url:o,userAgent:i,supertabBaseUrl:u.baseUrl,debug:this.debug,apiKey:this.apiKey,ctx:r,requestHeaders:Object.fromEntries(e.headers.entries())});return c.valid?{action:"allow"}:N({reason:c.reason,error:c.error,requestUrl:o})}if(!(this.botDetector?.(e,r)??!1))return{action:"allow"};switch(this.enforcement){case"strict":return N({reason:"missing_license_token",error:"Authorization header missing or malformed",requestUrl:o});case"soft":return Q(o);default:return{action:"allow"}}}static async obtainLicenseToken(e){return M({clientId:e.clientId,clientSecret:e.clientSecret,resourceUrl:e.resourceUrl,debug:e.debug})}static async cloudflareHandleRequests(e,r,t,s){try{let o=new u({apiKey:r.MERCHANT_API_KEY,botDetector:s?.botDetector,enforcement:s?.enforcement});return await ee(o,e,t)}catch(o){return console.error("[SupertabConnect] cloudflareHandleRequests failed:",o),await fetch(e)}}static async fastlyHandleRequests(e,r,t,s){try{let{botDetector:o,enforcement:i}=s??{},a=new u({apiKey:r,botDetector:o,enforcement:i}),c;return s?.enableRSL&&(c={baseUrl:u.baseUrl,merchantSystemUrn:s.merchantSystemUrn}),await te(a,e,t,c)}catch(o){return console.error("[SupertabConnect] fastlyHandleRequests failed:",o),await fetch(e,{backend:t})}}static async cloudfrontHandleRequests(e,r){let t=e?.Records?.[0]?.cf?.request??{};try{if(!t.headers?.["x-license-auth"])return t;let o=new u({apiKey:r.apiKey,enforcement:r.enforcement});return await re(o,e)}catch(s){return console.error("[SupertabConnect] cloudfrontHandleRequests failed:",s),t}}};u.baseUrl="https://api-connect.supertab.co",u._instance=null;var F=u;0&&(module.exports={CDNStatusDescription,EnforcementMode,HandlerAction,LicenseTokenInvalidReason,SupertabConnect,defaultBotDetector});
1
+ "use strict";var le=Object.create;var v=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var fe=Object.getPrototypeOf,pe=Object.prototype.hasOwnProperty;var ge=(r,e)=>{for(var t in e)v(r,t,{get:e[t],enumerable:!0})},J=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of de(e))!pe.call(r,s)&&s!==t&&v(r,s,{get:()=>e[s],enumerable:!(n=ue(e,s))||n.enumerable});return r};var A=(r,e,t)=>(t=r!=null?le(fe(r)):{},J(e||!r||!r.__esModule?v(t,"default",{value:r,enumerable:!0}):t,r)),he=r=>J(v({},"__esModule",{value:!0}),r);var De={};ge(De,{CDNStatusDescription:()=>U,EnforcementMode:()=>D,HandlerAction:()=>S,LicenseTokenInvalidReason:()=>_,SupertabConnect:()=>K,UsageType:()=>N,defaultBotDetector:()=>ie});module.exports=he(De);var D=(n=>(n.DISABLED="disabled",n.SOFT="soft",n.STRICT="strict",n))(D||{}),_=(l=>(l.MISSING_TOKEN="missing_license_token",l.INVALID_HEADER="invalid_license_header",l.INVALID_ALG="invalid_license_algorithm",l.INVALID_PAYLOAD="invalid_license_payload",l.INVALID_ISSUER="invalid_license_issuer",l.SIGNATURE_VERIFICATION_FAILED="license_signature_verification_failed",l.EXPIRED="license_token_expired",l.INVALID_AUDIENCE="invalid_license_audience",l.SERVER_ERROR="server_error",l))(_||{}),L="stc-backend",S=(t=>(t.ALLOW="allow",t.BLOCK="block",t))(S||{}),U=(o=>(o.Unauthorized="Unauthorized",o.PaymentRequired="Payment Required",o.Forbidden="Forbidden",o.ServiceUnavailable="Service Unavailable",o.Error="Error",o))(U||{});function k(r){let e=null;return()=>(e||(e=r()),e)}var W=k(()=>import("jose/jwt/verify")),C=k(()=>import("jose/jwt/decode")),M=k(()=>import("jose/decode/protected_header")),me=k(()=>import("jose/key/import")),Re=k(()=>import("jose/jwt/sign"));function j(r,e){let t=!1,n=r;n.endsWith("$")&&(t=!0,n=n.slice(0,-1));let s=n.includes("*"),i=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*"),a;return t?a=`^${i}$`:s?a=`^${i}`:n==="/"?a="^/":a=`^${i}(/|$)`,new RegExp(a).test(e)?n.replace(/\*/g,"").length:-1}var m="supertab-connect-sdk-typescript/1.5.0";var P=new Map,q=15*60,I=new Map;function ye(){let r=Math.floor(Date.now()/1e3);for(let[e,t]of I)r-t.fetchedAt>=q&&I.delete(e)}function be(r,e){let t=P.get(r);if(!t)return null;let n=Math.floor(Date.now()/1e3);return t.exp>n+30?(e&&console.debug(`Using cached license token (expires in ${t.exp-n}s)`),t.token):(e&&console.debug("Cached license token expired or expiring soon, refreshing"),P.delete(r),null)}var N=(i=>(i.ALL="all",i.SEARCH="search",i.AI_ALL="ai-all",i.AI_TRAIN="ai-train",i.AI_INDEX="ai-index",i.AI_INPUT="ai-input",i))(N||{});async function Ee(r,e,t){try{let n=await fetch(r,e);if(!n.ok){let o=await n.text().catch(()=>""),i=`Failed to obtain license token: ${n.status} ${n.statusText}${o?` - ${o}`:""}`;throw new Error(i)}let s;try{s=await n.json()}catch(o){throw t&&console.error("Failed to parse license token response as JSON:",o),new Error("Failed to parse license token response as JSON")}if(!s?.access_token)throw new Error("License token response missing access_token");return s.access_token}catch(n){throw t&&console.error("Error generating license token:",n),n}}async function we(r,e){let t=new URL(r).origin,n=I.get(t);if(n){let a=Math.floor(Date.now()/1e3);if(a-n.fetchedAt<q)return e&&console.debug(`Using cached license.xml for origin ${t} (expires in ${q-(a-n.fetchedAt)}s)`),n.xml;e&&console.debug(`Cached license.xml for origin ${t} expired, re-fetching`),I.delete(t)}let s=`${t}/license.xml`,o=await fetch(s,{headers:{"User-Agent":m}});if(!o.ok)throw e&&console.error(`Failed to fetch license.xml from ${s}: ${o.status}`),new Error(`Failed to fetch license.xml from ${s}: ${o.status}`);let i=await o.text();return e&&console.debug("Fetched license.xml from",s),ye(),I.set(t,{xml:i,fetchedAt:Math.floor(Date.now()/1e3)}),i}function Le(r,e){let t=[],n=/<content\s([^>]*)>([\s\S]*?)<\/content>/gi,s=/url\s*=\s*"([^"]*)"/i,o=/server\s*=\s*"([^"]*)"/i,i=/<license[^>]*>[\s\S]*?<\/license>/i,a=0,c;for(;(c=n.exec(r))!==null;){a++;let l=c[1],d=c[2],f=l.match(s),g=l.match(o),R=d.match(i);if(f&&R)t.push({urlPattern:f[1],server:g?.[1],licenseXml:R[0]});else if(e){let b=[!f&&"url",!R&&"<license>"].filter(Boolean).join(", ");console.debug(`Skipping <content> element #${a}: missing ${b}`)}}return e&&console.debug(`Found ${a} <content> element(s), ${t.length} valid`),t}function xe(r,e){let t=/<permits\b[^>]*type\s*=\s*"usage"[^>]*>([\s\S]*?)<\/permits>/gi,n=/<prohibits\b[^>]*type\s*=\s*"usage"[^>]*>([\s\S]*?)<\/prohibits>/gi,s;for(;(s=n.exec(r))!==null;){let o=s[1].trim().split(/\s+/).filter(Boolean);if(o.includes("all")||o.includes(e))return!1}for(;(s=t.exec(r))!==null;){let o=s[1].trim().split(/\s+/).filter(Boolean);if(o.includes("all")||o.includes(e))return!0}return!1}function G(r,e,t){let n=new URL(e),s=n.host,o=n.pathname;t&&console.debug(`Matching resource URL: ${e} (host=${s}, path=${o})`);let i=null,a=-1;for(let c of r){let l;if(c.urlPattern.startsWith("/"))l=c.urlPattern;else{let g;try{g=new URL(c.urlPattern)}catch{t&&console.debug(`Skipping block with invalid URL pattern: ${c.urlPattern}`);continue}if(g.host!==s){t&&console.debug(`Skipping block: host mismatch (pattern=${g.host}, resource=${s})`);continue}l=g.pathname}if(l===o)return t&&console.debug(`Exact match found: ${c.urlPattern}`),c;let f=j(l,o);f>a&&(a=f,i=c)}return t&&console.debug(i?`Wildcard match found: ${i.urlPattern} (specificity=${a})`:`No matching content block found for ${e}`),i}function Ae(r,e,t,n){let s=r.filter(o=>!o.server&&xe(o.licenseXml,t));return G(s,e,n)}async function X({clientId:r,clientSecret:e,resourceUrl:t,usage:n,debug:s}){let o=await we(t,s);s&&console.debug(`Fetched license.xml (${o.length} chars)`);let i=Le(o,s);if(i.length===0)throw s&&console.error("No valid <content> elements with <license> found in license.xml"),new Error("No valid <content> elements with <license> found in license.xml");if(n&&Ae(i,t,n,s)){s&&(console.debug("Matched serverless content to usage and resource URL combination, skipping license token request. "),console.debug("URL: "+t+", Usage: "+n));return}let a=i.filter(E=>!!E.server),c=G(a,t,s);if(!c){if(s){let E=a.map(w=>w.urlPattern).join(", ");console.error(`No <content> element matches resource URL: ${t}. Available patterns: ${E}`)}throw new Error(`No <content> element in license.xml matches resource URL: ${t}`)}s&&(console.debug("Matched content block for resource URL:",t),console.debug("Using license XML:",c.licenseXml));let l=`${r}:${c.server}:${c.urlPattern}`,d=be(l,s);if(d)return d;let f=c.server+"/token";s&&console.debug(`Requesting license token from ${f}`);let g=new URLSearchParams({grant_type:"client_credentials",license:c.licenseXml,resource:c.urlPattern}),R={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${r}:${e}`),"User-Agent":m},body:g.toString()},b=await Ee(f,R,s);try{let{decodeJwt:E}=await C(),w=E(b);w.exp&&P.set(l,{token:b,exp:w.exp})}catch{s&&console.debug("Failed to decode token for caching, skipping cache")}return b}var O=new Map,Se=48*60*60*1e3,x=class extends Error{constructor(e){super(`No matching platform key found: ${e}`),this.name="JwksKeyNotFoundError"}};function ke(){let r={method:"GET",headers:{"User-Agent":m}};return globalThis.fastly&&(r={...r,backend:L}),r}async function Ie({cacheKey:r,url:e,debug:t,failureMessage:n,logLabel:s}){let o=O.get(r);if(o&&Date.now()-o.cachedAt<Se)return o.data;try{let i=await fetch(e,ke());if(!i.ok)throw new Error(`${n}: ${i.status}`);let a=await i.json();return O.set(r,{data:a,cachedAt:Date.now()}),a}catch(i){throw t&&console.error(s,i),i}}async function z(r,e){let t=`${r}/.well-known/jwks.json/platform`;return e&&console.debug(`Fetching platform JWKS from URL: ${t}`),Ie({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}function Y(){O.clear()}async function Q({apiKey:r,baseUrl:e,eventName:t,properties:n,licenseId:s,debug:o=!1}){let i={event_name:t,license_id:s,properties:n};try{let a={method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json","User-Agent":m},body:JSON.stringify(i)};globalThis.fastly&&(a={...a,backend:L});let c=await fetch(`${e}/events`,a);!c.ok&&o&&console.error(`Failed to record event: ${c.status}`)}catch(a){o&&console.error("Error recording event:",a)}}var ve=new Set(["authorization","cookie","set-cookie","proxy-authorization","x-api-key","x-amz-security-token","user-agent","x-license-auth"]);function Z(r){let e={};for(let[t,n]of Object.entries(r)){let s=t.toLowerCase();ve.has(s)||(e[`h_${s}`]=n)}return e}var T=r=>r.trim().replace(/\/+$/,"");function y(r){switch(r){case"missing_license_token":return"Authorization header missing or malformed";case"invalid_license_algorithm":return"Unsupported token algorithm";case"license_token_expired":return"The license token has expired";case"license_signature_verification_failed":return"The license token signature is invalid";case"invalid_license_header":return"The license token header is malformed";case"invalid_license_payload":return"The license token payload is malformed";case"invalid_license_issuer":return"The license token issuer is not recognized";case"invalid_license_audience":return"The license does not grant access to this resource";case"server_error":return"The server encountered an error validating the license";default:return"License token missing, expired, revoked, or malformed"}}async function F({licenseToken:r,requestUrl:e,supertabBaseUrl:t,debug:n}){let{decodeProtectedHeader:s}=await M(),{decodeJwt:o}=await C(),{jwtVerify:i}=await W();if(!r)return{valid:!1,reason:"missing_license_token",error:y("missing_license_token")};let a;try{a=s(r)}catch(p){return n&&console.error("Invalid license JWT header:",p),{valid:!1,reason:"invalid_license_header",error:y("invalid_license_header")}}if(a.alg!=="ES256")return n&&console.error("Unsupported license JWT alg:",a.alg),{valid:!1,reason:"invalid_license_algorithm",error:y("invalid_license_algorithm")};let c;try{c=o(r)}catch(p){return n&&console.error("Invalid license JWT payload:",p),{valid:!1,reason:"invalid_license_payload",error:y("invalid_license_payload")}}let l=c.license_id,d=c.iss,f=d?T(d):void 0,g=T(t);if(!f||!f.startsWith(g))return n&&console.error("License JWT issuer is missing or malformed:",d),{valid:!1,reason:"invalid_license_issuer",error:y("invalid_license_issuer"),licenseId:l};let R=Array.isArray(c.aud)?c.aud.filter(p=>typeof p=="string"):typeof c.aud=="string"?[c.aud]:[],b=T(e);if(!R.some(p=>{let h=T(p);return h?b.startsWith(h):!1}))return n&&console.error("License JWT audience does not match request URL:",c.aud),{valid:!1,reason:"invalid_license_audience",error:y("invalid_license_audience"),licenseId:l};let w=async()=>{let p;try{p=await z(t,n)}catch(h){return n&&console.error("Failed to fetch platform JWKS:",h),{valid:!1,reason:"server_error",error:y("server_error"),licenseId:l}}try{let ae=await i(r,async B=>{let V=p.keys.find(ce=>ce.kid===B.kid);if(!V)throw new x(B.kid);return V},{issuer:d,algorithms:[a.alg],clockTolerance:"1m"});return{valid:!0,licenseId:l,payload:ae.payload}}catch(h){if(n&&console.error("License JWT verification failed:",h),h instanceof x)throw h;return h instanceof Error&&h.message?.includes("exp")?{valid:!1,reason:"license_token_expired",error:y("license_token_expired"),licenseId:l}:{valid:!1,reason:"license_signature_verification_failed",error:y("license_signature_verification_failed"),licenseId:l}}};try{return await w()}catch(p){if(p instanceof x)return n&&console.debug("Key not found in cached JWKS, clearing cache and retrying..."),Y(),await w();throw p}}function ee({requestUrl:r}){try{let e=new URL(r);return`${e.protocol}//${e.host}/license.xml`}catch(e){return console.error("[SupertabConnect] generateLicenseLink failed to parse URL:",e),"/license.xml"}}function te(r){let e=ee({requestUrl:r});return{action:"allow",headers:{Link:`<${e}>; rel="license"; type="application/rsl+xml"`,"X-RSL-Status":"token_required","X-RSL-Reason":"missing"}}}function _e(r){switch(r){case"missing_license_token":case"invalid_license_algorithm":return{rslError:"invalid_request",status:401};case"license_token_expired":case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":case"invalid_license_issuer":return{rslError:"invalid_token",status:401};case"invalid_license_audience":return{rslError:"insufficient_scope",status:403};case"server_error":return{rslError:"server_error",status:503};default:return{rslError:"invalid_token",status:401}}}function Ue(r){return r.replace(/[\r\n]/g,"").replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function H({reason:r,error:e,requestUrl:t}){let{rslError:n,status:s}=_e(r),o=Ue(e),i=ee({requestUrl:t});return{action:"block",status:s,body:`Access to this resource requires a valid license token. Error: ${n} - ${e}`,headers:{"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${n}", error_description="${o}"`,Link:`<${i}>; rel="license"; type="application/rsl+xml"`}}}function Ce(){let r={method:"GET",headers:{"User-Agent":m}};return globalThis.fastly&&(r={...r,backend:L}),r}async function re(r,e){try{let t=`${r}/merchants/systems/${e}/license.xml`,n=await fetch(t,Ce());if(!n.ok)return new Response("License not found",{status:404});let s=await n.text();return new Response(s,{status:200,headers:new Headers({"Content-Type":"application/rsl+xml"})})}catch(t){return console.error("[SupertabConnect] hostRSLicenseXML failed:",t),new Response("Bad Gateway",{status:502})}}async function $(r){let e=await F({licenseToken:r.token,requestUrl:r.url,supertabBaseUrl:r.supertabBaseUrl,debug:r.debug}),t=Q({apiKey:r.apiKey,baseUrl:r.supertabBaseUrl,eventName:e.valid?"license_used":e.reason,properties:{page_url:r.url,user_agent:r.userAgent,sdk_user_agent:m,verification_status:e.valid?"valid":"invalid",verification_reason:e.valid?"success":e.reason,...Z(r.requestHeaders??{})},licenseId:e.licenseId,debug:r.debug});return r.ctx?.waitUntil?r.ctx.waitUntil(t):await t,e}async function ne(r,e,t){let n=await r.handleRequest(e,t);if(n.action==="block")return new Response(n.body,{status:n.status,headers:new Headers(n.headers)});let s=await fetch(e);if(n.headers){let o=new Response(s.body,s);for(let[i,a]of Object.entries(n.headers))o.headers.set(i,a);return o}return s}async function se(r,e,t,n){let s=e.headers.get("x-original-request-url")||e.url;if(n&&new URL(s).pathname==="/license.xml")return await re(n.baseUrl,n.merchantSystemUrn);let o=new Request(s,{method:e.method,headers:e.headers}),i=await r.handleRequest(o);if(i.action==="block")return new Response(i.body,{status:i.status,headers:new Headers(i.headers)});let a=await fetch(e,{backend:t});if(i.headers){let c=new Response(a.body,a);for(let[l,d]of Object.entries(i.headers))c.headers.set(l,d);return c}return a}function Te(r){switch(r){case 401:return"Unauthorized";case 402:return"Payment Required";case 403:return"Forbidden";case 503:return"Service Unavailable";default:return"Error"}}async function oe(r,e){let t=e.Records[0].cf.request,n=t.headers?.["x-original-request-url"]?.[0]?.value,s=`${t.headers.host[0].value}${t.uri}`,o=`https://${n||s}${t.querystring?"?"+t.querystring:""}`,i=new Headers;Object.entries(t.headers).forEach(([l,d])=>{d.forEach(({value:f})=>i.append(l,f))});let a=new Request(o,{method:t.method,headers:i}),c=await r.handleRequest(a);if(c.action==="block"){let l={};return Object.entries(c.headers).forEach(([d,f])=>{l[d.toLowerCase()]=[{key:d,value:f}]}),{status:c.status.toString(),statusDescription:Te(c.status),headers:l,body:c.body}}return t}function ie(r){let e=r.headers.get("User-Agent")||"",t=r.headers.get("accept")||"",n=r.headers.get("sec-ch-ua"),s=r.headers.get("accept-language"),o=r.cf?.botManagement?.score,i=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],a=e.toLowerCase(),c=i.some(R=>a.includes(R)),l=a.includes("headless")||a.includes("puppeteer")||!n,d=!a.includes("headless")&&!a.includes("puppeteer")&&!n,f=!t||!s,g=typeof o=="number"&&o<30;return(a.includes("safari")||a.includes("mozilla"))&&l&&d?!1:c||l||f||g}var u=class u{constructor(e,t=!1){if(!t&&u._instance){if(e.apiKey!==u._instance.apiKey)throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(t&&u._instance&&u.resetInstance(),!e.apiKey)throw new Error("Missing required configuration: apiKey is required");this.apiKey=e.apiKey,this.enforcement=e.enforcement??"soft",this.botDetector=e.botDetector,this.debug=e.debug??!1,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}static getBaseUrl(){return u.baseUrl}static async verify(e){let t=e.baseUrl??u.baseUrl,n=await F({licenseToken:e.token,requestUrl:e.resourceUrl,supertabBaseUrl:t,debug:e.debug??!1});return n.valid?{valid:!0}:{valid:!1,error:n.error}}async verifyAndRecord(e){let t=await $({token:e.token,url:e.resourceUrl,userAgent:e.userAgent??"unknown",supertabBaseUrl:u.baseUrl,debug:e.debug??this.debug,apiKey:this.apiKey,ctx:e.ctx,requestHeaders:e.requestHeaders});return t.valid?{valid:!0}:{valid:!1,error:t.error}}async handleRequest(e,t){let n=e.headers.get("Authorization")||"",s=n.startsWith("License ")?n.slice(8):null,o=e.url,i=e.headers.get("User-Agent")||"unknown";if(s){if(this.enforcement==="disabled")return{action:"allow"};let c=await $({token:s,url:o,userAgent:i,supertabBaseUrl:u.baseUrl,debug:this.debug,apiKey:this.apiKey,ctx:t,requestHeaders:Object.fromEntries(e.headers.entries())});return c.valid?{action:"allow"}:H({reason:c.reason,error:c.error,requestUrl:o})}if(!(this.botDetector?.(e,t)??!1))return{action:"allow"};switch(this.enforcement){case"strict":return H({reason:"missing_license_token",error:"Authorization header missing or malformed",requestUrl:o});case"soft":return te(o);default:return{action:"allow"}}}static async obtainLicenseToken(e){return X({clientId:e.clientId,clientSecret:e.clientSecret,resourceUrl:e.resourceUrl,usage:e.usage,debug:e.debug})}static async cloudflareHandleRequests(e,t,n,s){try{let o=new u({apiKey:t.MERCHANT_API_KEY,botDetector:s?.botDetector,enforcement:s?.enforcement});return await ne(o,e,n)}catch(o){return console.error("[SupertabConnect] cloudflareHandleRequests failed:",o),await fetch(e)}}static async fastlyHandleRequests(e,t,n,s){try{let{botDetector:o,enforcement:i}=s??{},a=new u({apiKey:t,botDetector:o,enforcement:i}),c;return s?.enableRSL&&(c={baseUrl:u.baseUrl,merchantSystemUrn:s.merchantSystemUrn}),await se(a,e,n,c)}catch(o){return console.error("[SupertabConnect] fastlyHandleRequests failed:",o),await fetch(e,{backend:n})}}static async cloudfrontHandleRequests(e,t){let n=e?.Records?.[0]?.cf?.request??{};try{if(!n.headers?.["x-license-auth"])return n;let o=new u({apiKey:t.apiKey,enforcement:t.enforcement});return await oe(o,e)}catch(s){return console.error("[SupertabConnect] cloudfrontHandleRequests failed:",s),n}}};u.baseUrl="https://api-connect.supertab.co",u._instance=null;var K=u;0&&(module.exports={CDNStatusDescription,EnforcementMode,HandlerAction,LicenseTokenInvalidReason,SupertabConnect,UsageType,defaultBotDetector});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/jose.ts","../src/url-pattern.ts","../src/version.ts","../src/customer.ts","../src/jwks.ts","../src/events.ts","../src/headers.ts","../src/license.ts","../src/cdn.ts","../src/bots.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n EnforcementMode,\n BotDetector,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n FastlyHandlerOptions,\n} from \"./types\";\nimport { obtainLicenseToken as obtainLicenseTokenHelper } from \"./customer\";\nimport {\n buildBlockResult,\n buildSignalResult,\n verifyLicenseToken as verifyLicenseTokenHelper,\n verifyAndRecordEvent,\n} from \"./license\";\nimport {\n handleCloudflareRequest,\n handleFastlyRequest,\n handleCloudfrontRequest,\n} from \"./cdn\";\nimport {\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n} from \"./types\";\n\nexport { EnforcementMode, HandlerAction, LicenseTokenInvalidReason, CDNStatusDescription };\nexport type {\n SupertabConnectConfig,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n BotDetector,\n HandlerResult,\n FastlyHandlerOptions,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n};\nexport { defaultBotDetector } from \"./bots\";\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private enforcement!: EnforcementMode;\n private botDetector?: BotDetector;\n private debug!: boolean;\n\n private static _instance: SupertabConnect | null = null;\n\n /**\n * Create a new SupertabConnect instance (singleton).\n * Returns the existing instance if one exists with the same config.\n * @param config SDK configuration including apiKey\n * @param reset Pass true to replace an existing instance with different config\n * @throws If an instance with different config already exists and reset is false\n */\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (config.apiKey !== SupertabConnect._instance.apiKey) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey) {\n throw new Error(\n \"Missing required configuration: apiKey is required\"\n );\n }\n this.apiKey = config.apiKey;\n this.enforcement = config.enforcement ?? EnforcementMode.SOFT;\n this.botDetector = config.botDetector;\n this.debug = config.debug ?? false;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n /**\n * Clear the singleton instance, allowing a new one to be created with different config.\n */\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Get the current base URL for API requests.\n */\n public static getBaseUrl(): string {\n return SupertabConnect.baseUrl;\n }\n\n /**\n * Pure token verification — verifies a license token without recording any events.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.baseUrl Optional override for the Supertab Connect API base URL\n * @param options.debug Enable debug logging (default: false)\n * @returns A promise that resolves with the verification result\n */\n static async verify(options: {\n token: string;\n resourceUrl: string;\n baseUrl?: string;\n debug?: boolean;\n }): Promise<RSLVerificationResult> {\n const baseUrl = options.baseUrl ?? SupertabConnect.baseUrl;\n\n const result = await verifyLicenseTokenHelper({\n licenseToken: options.token,\n requestUrl: options.resourceUrl,\n supertabBaseUrl: baseUrl,\n debug: options.debug ?? false,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Verify a license token and record an analytics event.\n * Uses the instance's apiKey for event recording.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.userAgent Optional user agent string for event recording\n * @param options.requestHeaders Optional request headers to include in the event properties\n * @param options.debug Enable debug logging (default: false)\n * @param options.ctx Optional execution context with waitUntil for non-blocking event recording\n * @returns A promise that resolves with the verification result\n */\n async verifyAndRecord(options: {\n token: string;\n resourceUrl: string;\n userAgent?: string;\n requestHeaders?: Record<string, string>;\n debug?: boolean;\n ctx?: ExecutionContext;\n }): Promise<RSLVerificationResult> {\n const result = await verifyAndRecordEvent({\n token: options.token,\n url: options.resourceUrl,\n userAgent: options.userAgent ?? \"unknown\",\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: options.debug ?? this.debug,\n apiKey: this.apiKey!,\n ctx: options.ctx,\n requestHeaders: options.requestHeaders,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Handle an incoming request by extracting the license token, verifying it, and recording an analytics event.\n * When no token is present, bot detection and enforcement mode determine the response.\n * @param request The incoming HTTP request\n * @param ctx Execution context for non-blocking event recording.\n * Pass this from your platform which has/requires this context (e.g. Cloudflare Workers)\n * @returns A promise that resolves with the handler result indicating ALLOW or BLOCK request\n */\n async handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult> {\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"License \") ? auth.slice(8) : null;\n const url = request.url;\n const userAgent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // Token present → ALWAYS validate, regardless of mode or bot detection\n if (token) {\n if (this.enforcement === EnforcementMode.DISABLED) {\n return { action: HandlerAction.ALLOW };\n }\n const verification = await verifyAndRecordEvent({\n token,\n url,\n userAgent,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: this.debug,\n apiKey: this.apiKey!,\n ctx,\n requestHeaders: Object.fromEntries(request.headers.entries()),\n });\n if (!verification.valid) {\n return buildBlockResult({\n reason: verification.reason,\n error: verification.error,\n requestUrl: url,\n });\n }\n return { action: HandlerAction.ALLOW };\n }\n\n // No token from here on\n const isBot = this.botDetector?.(request, ctx) ?? false;\n\n if (!isBot) {\n return { action: HandlerAction.ALLOW };\n }\n\n // Bot detected, no token — enforcement mode decides\n switch (this.enforcement) {\n case EnforcementMode.STRICT:\n return buildBlockResult({\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: \"Authorization header missing or malformed\",\n requestUrl: url,\n });\n case EnforcementMode.SOFT:\n return buildSignalResult(url);\n default: // DISABLED\n return { action: HandlerAction.ALLOW };\n }\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param options.clientId OAuth client identifier.\n * @param options.clientSecret OAuth client secret for client_credentials flow.\n * @param options.resourceUrl Resource URL attempting to access with a License.\n * @param options.debug Enable debug logging (default: false).\n * @returns Promise resolving to the issued license access token string.\n */\n static async obtainLicenseToken(options: {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n debug?: boolean;\n }): Promise<string> {\n return obtainLicenseTokenHelper({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n resourceUrl: options.resourceUrl,\n debug: options.debug,\n });\n }\n\n /**\n * Handle incoming requests for Cloudflare Workers.\n * Pass this directly as your Worker's fetch handler.\n * @param request The incoming Worker request\n * @param env Worker environment bindings containing MERCHANT_API_KEY\n * @param ctx Worker execution context for non-blocking event recording\n * @param options Optional configuration items\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: ExecutionContext,\n options?: {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n }\n ): Promise<Response> {\n try {\n const instance = new SupertabConnect({\n apiKey: env.MERCHANT_API_KEY,\n botDetector: options?.botDetector,\n enforcement: options?.enforcement,\n });\n return await handleCloudflareRequest(instance, request, ctx);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudflareHandleRequests failed:\", err);\n return await fetch(request);\n }\n }\n\n /**\n * Handle incoming requests for Fastly Compute.\n * @param request The incoming Fastly request\n * @param merchantApiKey The merchant API key for authentication\n * @param originBackend The Fastly backend name to forward allowed requests to\n * @param options Optional configuration items\n * @param options.enableRSL Serve license.xml at /license.xml for RSL-compliant clients (default: false)\n * @param options.merchantSystemUrn Required when enableRSL is true; the merchant system URN used to fetch license.xml\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async fastlyHandleRequests(\n request: Request,\n merchantApiKey: string,\n originBackend: string,\n options?: FastlyHandlerOptions\n ): Promise<Response> {\n try {\n const { botDetector, enforcement } = options ?? {};\n\n const instance = new SupertabConnect({\n apiKey: merchantApiKey,\n botDetector,\n enforcement,\n });\n\n let rslOptions: { baseUrl: string; merchantSystemUrn: string } | undefined;\n if (options?.enableRSL) {\n rslOptions = {\n baseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: options.merchantSystemUrn,\n };\n }\n\n return await handleFastlyRequest(\n instance,\n request,\n originBackend,\n rslOptions\n );\n } catch (err) {\n console.error(\"[SupertabConnect] fastlyHandleRequests failed:\", err);\n return await fetch(request, { backend: originBackend } as RequestInit);\n }\n }\n\n /**\n * Handle incoming requests for AWS CloudFront Lambda@Edge.\n * Use as the handler for an origin-request LambdaEdge function.\n * @param event The CloudFront origin-request event\n * @param options Configuration including apiKey and optional botDetector/enforcement\n */\n static async cloudfrontHandleRequests<TRequest extends Record<string, any>>(\n event: CloudFrontRequestEvent<TRequest>,\n options: CloudfrontHandlerOptions\n ): Promise<CloudFrontRequestResult<TRequest>> {\n const request = event?.Records?.[0]?.cf?.request as TRequest ?? {} as CloudFrontRequestResult<TRequest>;\n try {\n const license_auth_header = request.headers?.[\"x-license-auth\"];\n if (!license_auth_header) {\n // No license auth header means the request is either from a human or from an unidentifiable bot.\n // No reasons to waste compute resources on the rest of the checks.\n return request;\n }\n const instance = new SupertabConnect({\n apiKey: options.apiKey,\n enforcement: options.enforcement\n });\n return await handleCloudfrontRequest(instance, event);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudfrontHandleRequests failed:\", err);\n return request;\n }\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nexport enum EnforcementMode {\n DISABLED = \"disabled\",\n SOFT = \"soft\",\n STRICT = \"strict\",\n}\n\nexport interface ExecutionContext {\n waitUntil(promise: Promise<void>): void;\n}\n\nexport type BotDetector = (request: Request, ctx?: ExecutionContext) => boolean;\n\nexport interface SupertabConnectConfig {\n apiKey: string;\n enforcement?: EnforcementMode;\n botDetector?: BotDetector;\n debug?: boolean;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n properties: Record<string, string>;\n}\n\nexport type LicenseTokenVerificationResult =\n | { valid: true; licenseId?: string; payload: JWTPayload }\n | { valid: false; reason: LicenseTokenInvalidReason; error: string; licenseId?: string };\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n SERVER_ERROR = \"server_error\",\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var fastly: object | undefined;\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}\n\nexport enum HandlerAction {\n ALLOW = \"allow\",\n BLOCK = \"block\",\n}\n\nexport type HandlerResult =\n | { action: HandlerAction.ALLOW; headers?: Record<string, string> }\n | { action: HandlerAction.BLOCK; status: number; body: string; headers: Record<string, string> };\n\nexport enum CDNStatusDescription {\n Unauthorized = \"Unauthorized\",\n PaymentRequired = \"Payment Required\",\n Forbidden = \"Forbidden\",\n ServiceUnavailable = \"Service Unavailable\",\n Error = \"Error\",\n}\n\n// CloudFront Lambda@Edge types\n// Uses permissive types to be compatible with aws-lambda package types\nexport interface CloudFrontHeaders {\n [key: string]: Array<{ key?: string; value: string }>;\n}\n\nexport interface CloudFrontResultResponse {\n status: string;\n statusDescription?: CDNStatusDescription;\n headers?: CloudFrontHeaders;\n bodyEncoding?: \"text\" | \"base64\";\n body?: string;\n}\n\n// CloudFrontRequestEvent uses a generic request type to accept aws-lambda's CloudFrontRequest\nexport interface CloudFrontRequestEvent<TRequest = Record<string, any>> {\n Records: Array<{\n cf: {\n config?: {\n distributionDomainName?: string;\n distributionId?: string;\n eventType?: string;\n requestId?: string;\n };\n request: TRequest & {\n uri: string;\n method: string;\n querystring: string;\n headers: CloudFrontHeaders;\n };\n };\n }>;\n}\n\n// Result can be either the original request (pass-through) or a response\n// Using generic to preserve the original request type for pass-through\nexport type CloudFrontRequestResult<TRequest = Record<string, any>> = TRequest | CloudFrontResultResponse;\n\nexport interface CloudfrontHandlerOptions {\n apiKey: string;\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\nexport type RSLVerificationResult = {\n valid: boolean;\n error?: string;\n};\n\ninterface FastlyHandlerBaseOptions {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\ninterface FastlyHandlerWithRSL extends FastlyHandlerBaseOptions {\n enableRSL: true;\n merchantSystemUrn: string;\n}\n\ninterface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {\n enableRSL?: false;\n merchantSystemUrn?: never;\n}\n\nexport type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;\n","// Per-subpath lazy loaders for jose — each caches its own promise to avoid redundant imports.\n\nfunction lazyImport<T>(load: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n return () => {\n if (!cached) {\n cached = load();\n }\n return cached;\n };\n}\n\nexport const loadJwtVerify = lazyImport(() => import(\"jose/jwt/verify\"));\nexport const loadDecodeJwt = lazyImport(() => import(\"jose/jwt/decode\"));\nexport const loadDecodeProtectedHeader = lazyImport(() => import(\"jose/decode/protected_header\"));\nexport const loadKeyImport = lazyImport(() => import(\"jose/key/import\"));\nexport const loadJwtSign = lazyImport(() => import(\"jose/jwt/sign\"));\n","/**\n * Match a URL path against a robots.txt-style pattern.\n *\n * - `*` matches zero or more characters (including `/`)\n * - Trailing `$` anchors the match to the end of the path\n * - Without `$`, patterns without `*` match as prefix at segment boundaries\n * (e.g. `/content` matches `/content/article` but not `/content-other`)\n * - Without `$`, patterns with `*` are prefix-matched from the start\n *\n * Returns specificity (number of literal characters) on match, or -1 on no match.\n */\nexport function scorePathPattern(pattern: string, path: string): number {\n let anchored = false;\n let pat = pattern;\n\n if (pat.endsWith(\"$\")) {\n anchored = true;\n pat = pat.slice(0, -1);\n }\n\n const hasWildcard = pat.includes(\"*\");\n\n // Escape regex special chars (except *) and treat them as literals\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Converts wildcard * to regex equivalent .*\n const regexBody = escaped.replace(/\\*/g, \".*\");\n\n let regexStr: string;\n if (anchored) {\n regexStr = `^${regexBody}$`;\n } else if (hasWildcard) {\n regexStr = `^${regexBody}`;\n } else {\n // No wildcards, no anchor: prefix match at segment boundary\n // Special case: \"/\" matches all paths\n if (pat === \"/\") {\n regexStr = `^/`;\n } else {\n regexStr = `^${regexBody}(/|$)`;\n }\n }\n\n if (new RegExp(regexStr).test(path)) {\n return pat.replace(/\\*/g, \"\").length;\n }\n\n return -1;\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_USER_AGENT = `supertab-connect-sdk-typescript/${typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"unknown\"}`;\n","import { loadKeyImport, loadJwtSign, loadDecodeJwt } from \"./jose\";\nimport { scorePathPattern } from \"./url-pattern\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype CachedToken = { token: string; exp: number };\n\n// In-memory cache for license tokens, keyed by \"clientId:server:urlPattern\"\nconst licenseTokenCache = new Map<string, CachedToken>();\n\ntype CachedLicenseXml = { xml: string; fetchedAt: number };\nconst LICENSE_XML_TTL_SECONDS = 15 * 60; // 15 minutes\n\n// In-memory cache for license.xml content, keyed by origin (e.g. \"https://example.com\")\nconst licenseXmlCache = new Map<string, CachedLicenseXml>();\n\nfunction evictExpiredLicenseXml(): void {\n const now = Math.floor(Date.now() / 1000);\n for (const [origin, entry] of licenseXmlCache) {\n if (now - entry.fetchedAt >= LICENSE_XML_TTL_SECONDS) {\n licenseXmlCache.delete(origin);\n }\n }\n}\n\nfunction getCachedToken(\n cacheKey: string,\n debug?: boolean\n): string | null {\n const cached = licenseTokenCache.get(cacheKey);\n if (!cached) return null;\n\n const now = Math.floor(Date.now() / 1000);\n if (cached.exp > now + 30) {\n if (debug) {\n console.debug(\n `Using cached license token (expires in ${cached.exp - now}s)`\n );\n }\n return cached.token;\n }\n\n if (debug) {\n console.debug(\"Cached license token expired or expiring soon, refreshing\");\n }\n licenseTokenCache.delete(cacheKey);\n return null;\n}\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n debug?: boolean;\n};\n\ntype ContentBlock = {\n urlPattern: string;\n licenseXml: string;\n server: string;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\n) {\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: { access_token?: string };\n try {\n data = await response.json() as { access_token?: string };\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const { importPKCS8 } = await loadKeyImport();\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { SignJWT } = await loadJwtSign();\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nasync function fetchLicenseXml(\n resourceUrl: string,\n debug: boolean | undefined\n): Promise<string> {\n const origin = new URL(resourceUrl).origin;\n\n const cached = licenseXmlCache.get(origin);\n if (cached) {\n const now = Math.floor(Date.now() / 1000);\n if (now - cached.fetchedAt < LICENSE_XML_TTL_SECONDS) {\n if (debug) {\n console.debug(`Using cached license.xml for origin ${origin} (expires in ${LICENSE_XML_TTL_SECONDS - (now - cached.fetchedAt)}s)`);\n }\n return cached.xml;\n }\n if (debug) {\n console.debug(`Cached license.xml for origin ${origin} expired, re-fetching`);\n }\n licenseXmlCache.delete(origin);\n }\n\n const licenseXmlUrl = `${origin}/license.xml`;\n const response = await fetch(licenseXmlUrl, {\n headers: { \"User-Agent\": SDK_USER_AGENT },\n });\n if (!response.ok) {\n if (debug) {\n console.error(`Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`);\n }\n throw new Error(\n `Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`\n );\n }\n\n const xml = await response.text();\n if (debug) {\n console.debug(\"Fetched license.xml from\", licenseXmlUrl);\n }\n evictExpiredLicenseXml();\n licenseXmlCache.set(origin, { xml, fetchedAt: Math.floor(Date.now() / 1000) });\n return xml;\n}\n\nfunction parseContentElements(xml: string, debug?: boolean): ContentBlock[] {\n const contentBlocks: ContentBlock[] = [];\n const contentRegex = /<content\\s([^>]*)>([\\s\\S]*?)<\\/content>/gi;\n const urlRegex = /url\\s*=\\s*\"([^\"]*)\"/i;\n const serverRegex = /server\\s*=\\s*\"([^\"]*)\"/i;\n const licenseRegex = /<license[^>]*>[\\s\\S]*?<\\/license>/i;\n\n let elementCount = 0;\n let match;\n while ((match = contentRegex.exec(xml)) !== null) {\n elementCount++;\n const attrs = match[1];\n const body = match[2];\n const urlMatch = attrs.match(urlRegex);\n const serverMatch = attrs.match(serverRegex);\n const licenseMatch = body.match(licenseRegex);\n\n if (urlMatch && serverMatch && licenseMatch) {\n contentBlocks.push({\n urlPattern: urlMatch[1],\n server: serverMatch[1],\n licenseXml: licenseMatch[0],\n });\n } else if (debug) {\n const missing = [\n !urlMatch && \"url\",\n !serverMatch && \"server\",\n !licenseMatch && \"<license>\",\n ].filter(Boolean).join(\", \");\n console.debug(`Skipping <content> element #${elementCount}: missing ${missing}`);\n }\n }\n\n if (debug) {\n console.debug(`Found ${elementCount} <content> element(s), ${contentBlocks.length} valid`);\n }\n\n return contentBlocks;\n}\n\nfunction findBestMatchingContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n debug?: boolean\n): ContentBlock | null {\n const parsed = new URL(resourceUrl);\n const host = parsed.host;\n const path = parsed.pathname;\n\n if (debug) {\n console.debug(`Matching resource URL: ${resourceUrl} (host=${host}, path=${path})`);\n }\n\n let bestMatch: ContentBlock | null = null;\n let bestSpecificity = -1;\n\n for (const block of contentBlocks) {\n let patternPath: string;\n const isPathOnly = block.urlPattern.startsWith(\"/\");\n\n if (isPathOnly) {\n patternPath = block.urlPattern;\n } else {\n let patternUrl: URL;\n try {\n patternUrl = new URL(block.urlPattern);\n } catch {\n if (debug) {\n console.debug(`Skipping block with invalid URL pattern: ${block.urlPattern}`);\n }\n continue;\n }\n\n if (patternUrl.host !== host) {\n if (debug) {\n console.debug(`Skipping block: host mismatch (pattern=${patternUrl.host}, resource=${host})`);\n }\n continue;\n }\n\n patternPath = patternUrl.pathname;\n }\n\n // Exact match — highest priority, return immediately\n if (patternPath === path) {\n if (debug) {\n console.debug(`Exact match found: ${block.urlPattern}`);\n }\n return block;\n }\n\n // Pattern match (wildcards, prefix, anchored)\n const specificity = scorePathPattern(patternPath, path);\n if (specificity > bestSpecificity) {\n bestSpecificity = specificity;\n bestMatch = block;\n }\n }\n\n if (debug) {\n if (bestMatch) {\n console.debug(`Wildcard match found: ${bestMatch.urlPattern} (specificity=${bestSpecificity})`);\n } else {\n console.debug(`No matching content block found for ${resourceUrl}`);\n }\n }\n\n return bestMatch;\n}\n\nexport { parseContentElements, findBestMatchingContent };\nexport type { ContentBlock };\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n resourceUrl,\n debug,\n}: ObtainLicenseTokenParams): Promise<string> {\n const xml = await fetchLicenseXml(resourceUrl, debug);\n if (debug) {\n console.debug(`Fetched license.xml (${xml.length} chars)`);\n }\n const contentBlocks = parseContentElements(xml, debug);\n\n if (contentBlocks.length === 0) {\n if (debug) {\n console.error(\"No valid <content> elements with <license> found in license.xml\");\n }\n throw new Error(\n \"No valid <content> elements with <license> found in license.xml\"\n );\n }\n\n const matchedContent = findBestMatchingContent(contentBlocks, resourceUrl, debug);\n if (!matchedContent) {\n if (debug) {\n const patterns = contentBlocks.map(b => b.urlPattern).join(\", \");\n console.error(`No <content> element matches resource URL: ${resourceUrl}. Available patterns: ${patterns}`);\n }\n throw new Error(\n `No <content> element in license.xml matches resource URL: ${resourceUrl}`\n );\n }\n\n if (debug) {\n console.debug(\"Matched content block for resource URL:\", resourceUrl);\n console.debug(\"Using license XML:\", matchedContent.licenseXml);\n }\n\n // Cache tokens by server + urlPattern so path-only patterns (e.g. \"/articles/*\")\n // on different origins/servers don't collide with each other.\n const cacheKey = `${clientId}:${matchedContent.server}:${matchedContent.urlPattern}`;\n const cached = getCachedToken(cacheKey, debug);\n if (cached) return cached;\n\n const tokenEndpoint = matchedContent.server + '/token';\n if (debug) {\n console.debug(`Requesting license token from ${tokenEndpoint}`);\n }\n\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\n license: matchedContent.licenseXml,\n resource: matchedContent.urlPattern,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n const token = await retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n\n try {\n const { decodeJwt } = await loadDecodeJwt();\n const claims = decodeJwt(token);\n if (claims.exp) {\n licenseTokenCache.set(cacheKey, { token, exp: claims.exp });\n }\n } catch {\n if (debug) {\n console.debug(\"Failed to decode token for caching, skipping cache\");\n }\n }\n\n return token;\n}\n\nexport type { ObtainLicenseTokenParams };\n","import type { JSONWebKeySet } from \"jose\";\nimport { FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype JwksCacheEntry = { data: JSONWebKeySet; cachedAt: number };\nconst jwksCache = new Map<string, JwksCacheEntry>();\nconst JWKS_CACHE_TTL_MS = 48 * 60 * 60 * 1000; // 48 hours\n\nexport class JwksKeyNotFoundError extends Error {\n constructor(kid: string | undefined) {\n super(`No matching platform key found: ${kid}`);\n this.name = \"JwksKeyNotFoundError\";\n }\n}\n\ntype FetchJwksParams = {\n cacheKey: string;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<JSONWebKeySet> {\n const cached = jwksCache.get(cacheKey);\n if (cached && (Date.now() - cached.cachedAt) < JWKS_CACHE_TTL_MS) {\n return cached.data;\n }\n\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json() as JSONWebKeySet;\n jwksCache.set(cacheKey, { data: jwksData, cachedAt: Date.now() });\n return jwksData;\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<JSONWebKeySet> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n if (debug) {\n console.debug(`Fetching platform JWKS from URL: ${jwksUrl}`);\n }\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n","import { EventPayload, FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\nexport async function recordEvent({\n apiKey,\n baseUrl,\n eventName,\n properties,\n licenseId,\n debug = false,\n}: {\n apiKey: string;\n baseUrl: string;\n eventName: string;\n properties: Record<string, string>;\n licenseId?: string;\n debug?: boolean;\n}): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: FetchOptions = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: JSON.stringify(payload),\n };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(`${baseUrl}/events`, options);\n\n if (!response.ok && debug) {\n console.error(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n if (debug) {\n console.error(\"Error recording event:\", error);\n }\n }\n}\n","const DENIED_HEADERS = new Set([\n // Credentials\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n \"x-api-key\",\n \"x-amz-security-token\",\n // Already captured as properties.user_agent — avoid duplication\n \"user-agent\",\n // SDK-internal plumbing (not useful as analytics signal)\n \"x-license-auth\",\n]);\n\n/**\n * Transform a raw headers record into event properties: lowercase keys,\n * drop sensitive headers, and apply an `h_` prefix. Called from\n * verifyAndRecordEvent so both automatic and manual paths enforce the\n * same rules.\n */\nexport function toEventProperties(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase();\n if (!DENIED_HEADERS.has(lowerKey)) {\n result[`h_${lowerKey}`] = value;\n }\n }\n return result;\n}\n","import type { JWTPayload, JWTHeaderParameters } from \"jose\";\nimport { loadJwtVerify, loadDecodeJwt, loadDecodeProtectedHeader } from \"./jose\";\n\ninterface LicenseJWTPayload extends JWTPayload {\n license_id?: string;\n}\nimport {\n ExecutionContext,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks, clearJwksCache, JwksKeyNotFoundError } from \"./jwks\";\nimport { recordEvent } from \"./events\";\nimport { SDK_USER_AGENT } from \"./version\";\nimport { toEventProperties } from \"./headers\";\n\nconst stripTrailingSlash = (value: string) => value.trim().replace(/\\/+$/, \"\");\n\nfunction reasonToErrorDescription(reason: LicenseTokenInvalidReason): string {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n return \"Authorization header missing or malformed\";\n case LicenseTokenInvalidReason.INVALID_ALG:\n return \"Unsupported token algorithm\";\n case LicenseTokenInvalidReason.EXPIRED:\n return \"The license token has expired\";\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n return \"The license token signature is invalid\";\n case LicenseTokenInvalidReason.INVALID_HEADER:\n return \"The license token header is malformed\";\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n return \"The license token payload is malformed\";\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return \"The license token issuer is not recognized\";\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return \"The license does not grant access to this resource\";\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return \"The server encountered an error validating the license\";\n default:\n return \"License token missing, expired, revoked, or malformed\";\n }\n}\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n const { decodeProtectedHeader } = await loadDecodeProtectedHeader();\n const { decodeJwt } = await loadDecodeJwt();\n const { jwtVerify } = await loadJwtVerify();\n\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.MISSING_TOKEN),\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_HEADER),\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ALG),\n };\n }\n\n let payload: LicenseJWTPayload;\n try {\n payload = decodeJwt(licenseToken) as LicenseJWTPayload;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_PAYLOAD),\n };\n }\n\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n const normalizedIssuer = issuer ? stripTrailingSlash(issuer) : undefined;\n const normalizedBaseUrl = stripTrailingSlash(supertabBaseUrl);\n\n if (!normalizedIssuer || !normalizedIssuer.startsWith(normalizedBaseUrl)) {\n if (debug) {\n console.error(\"License JWT issuer is missing or malformed:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ISSUER),\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_AUDIENCE),\n licenseId,\n };\n }\n\n const verify = async (): Promise<LicenseTokenVerificationResult> => {\n let jwks;\n try {\n jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n } catch (error) {\n if (debug) {\n console.error(\"Failed to fetch platform JWKS:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SERVER_ERROR,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SERVER_ERROR),\n licenseId,\n };\n }\n\n try {\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new JwksKeyNotFoundError(jwtHeader.kid);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof JwksKeyNotFoundError) {\n throw error;\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.EXPIRED),\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED),\n licenseId,\n };\n }\n };\n\n try {\n return await verify();\n } catch (error) {\n if (error instanceof JwksKeyNotFoundError) {\n if (debug) {\n console.debug(\"Key not found in cached JWKS, clearing cache and retrying...\");\n }\n clearJwksCache();\n return await verify();\n }\n throw error;\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n try {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n } catch (err) {\n console.error(\"[SupertabConnect] generateLicenseLink failed to parse URL:\", err);\n return \"/license.xml\";\n }\n}\n\n/**\n * Build a HandlerResult that signals a missing token in soft enforcement mode.\n * Returns headers indicating a license is required without blocking the request.\n */\nexport function buildSignalResult(requestUrl: string): HandlerResult {\n const licenseLink = generateLicenseLink({ requestUrl });\n return {\n action: HandlerAction.ALLOW,\n headers: {\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n \"X-RSL-Status\": \"token_required\",\n \"X-RSL-Reason\": \"missing\",\n },\n };\n}\n\nfunction reasonToRslError(reason: LicenseTokenInvalidReason | string): { rslError: string; status: number } {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n case LicenseTokenInvalidReason.INVALID_ALG:\n return { rslError: \"invalid_request\", status: 401 };\n case LicenseTokenInvalidReason.EXPIRED:\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return { rslError: \"invalid_token\", status: 401 };\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return { rslError: \"insufficient_scope\", status: 403 };\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return { rslError: \"server_error\", status: 503 };\n default:\n return { rslError: \"invalid_token\", status: 401 };\n }\n}\n\n/**\n * Sanitize a string for safe use in an HTTP header quoted-string (RFC 7230).\n * Strips CR/LF to prevent header injection and escapes backslashes and quotes.\n */\nfunction sanitizeHeaderValue(value: string): string {\n return value\n .replace(/[\\r\\n]/g, \"\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"');\n}\n\nexport function buildBlockResult({\n reason,\n error,\n requestUrl,\n}: {\n reason: LicenseTokenInvalidReason | string;\n error: string;\n requestUrl: string;\n}): HandlerResult {\n const { rslError, status } = reasonToRslError(reason);\n const errorDescription = sanitizeHeaderValue(error);\n const licenseLink = generateLicenseLink({ requestUrl });\n\n return {\n action: HandlerAction.BLOCK,\n status,\n body: `Access to this resource requires a valid license token. Error: ${rslError} - ${error}`,\n headers: {\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\"`,\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n },\n };\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n try {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/rsl+xml\" }),\n });\n } catch (err) {\n console.error(\"[SupertabConnect] hostRSLicenseXML failed:\", err);\n return new Response(\"Bad Gateway\", { status: 502 });\n }\n}\n\nexport type VerifyAndRecordEventParams = {\n token: string;\n url: string;\n userAgent: string;\n supertabBaseUrl: string;\n debug: boolean;\n apiKey: string;\n ctx?: ExecutionContext;\n requestHeaders?: Record<string, string>;\n};\n\nexport async function verifyAndRecordEvent(\n params: VerifyAndRecordEventParams\n): Promise<LicenseTokenVerificationResult> {\n const verification = await verifyLicenseToken({\n licenseToken: params.token,\n requestUrl: params.url,\n supertabBaseUrl: params.supertabBaseUrl,\n debug: params.debug,\n });\n\n const eventPromise = recordEvent({\n apiKey: params.apiKey,\n baseUrl: params.supertabBaseUrl,\n eventName: verification.valid ? \"license_used\" : verification.reason,\n properties: {\n page_url: params.url,\n user_agent: params.userAgent,\n sdk_user_agent: SDK_USER_AGENT,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.valid ? \"success\" : verification.reason,\n ...toEventProperties(params.requestHeaders ?? {}),\n },\n licenseId: verification.licenseId,\n debug: params.debug,\n });\n if (params.ctx?.waitUntil) {\n params.ctx.waitUntil(eventPromise);\n } else {\n await eventPromise;\n }\n\n return verification;\n}\n","import {\n HandlerAction,\n HandlerResult,\n ExecutionContext,\n CDNStatusDescription,\n CloudFrontHeaders,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n} from \"./types\";\nimport { hostRSLicenseXML } from \"./license\";\n\n// Interface for what the CDN handlers need - avoids circular dependency\ninterface RequestHandler {\n handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;\n}\n\nexport async function handleCloudflareRequest(\n handler: RequestHandler,\n request: Request,\n ctx: ExecutionContext\n): Promise<Response> {\n const result = await handler.handleRequest(request, ctx);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\n/**\n * Handles an Origin request in Fastly. Expects `X-Original-Request-URL` header to contain the original viewer request URL.\n * @param handler Request handler instance that inspects the request and decides whether to allow or block it.\n * @param request Fastly request to process.\n * @param originBackend Fastly backend name used when forwarding allowed requests to origin.\n * @param rslOptions Optional configuration for serving `/license.xml` directly from the edge.\n * @param rslOptions.baseUrl Base URL used when generating the hosted license XML response.\n * @param rslOptions.merchantSystemUrn Merchant system URN for fetching License from Supertab Connect\n */\nexport async function handleFastlyRequest(\n handler: RequestHandler,\n request: Request,\n originBackend: string,\n rslOptions?: {\n baseUrl: string;\n merchantSystemUrn: string;\n }\n): Promise<Response> {\n const originalUrl = request.headers.get(\"x-original-request-url\") || request.url;\n\n if (rslOptions && new URL(originalUrl).pathname === \"/license.xml\") {\n return await hostRSLicenseXML(\n rslOptions.baseUrl,\n rslOptions.merchantSystemUrn\n );\n }\n\n const webRequest = new Request(originalUrl, {\n method: request.method,\n headers: request.headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request, {\n backend: originBackend,\n } as RequestInit);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\nfunction statusDescription(status: number): CDNStatusDescription {\n switch (status) {\n case 401: return CDNStatusDescription.Unauthorized;\n case 402: return CDNStatusDescription.PaymentRequired;\n case 403: return CDNStatusDescription.Forbidden;\n case 503: return CDNStatusDescription.ServiceUnavailable;\n default: return CDNStatusDescription.Error;\n }\n}\n\n/**\n * Handles an Origin request in CloudFront. Expects X-Original-Request-URL header to contain the original viewer request URL.\n * @param handler\n * @param event\n */\nexport async function handleCloudfrontRequest<TRequest extends Record<string, any>>(\n handler: RequestHandler,\n event: CloudFrontRequestEvent<TRequest>\n): Promise<CloudFrontRequestResult<TRequest>> {\n const cfRequest = event.Records[0].cf.request;\n\n // Convert CloudFront request to Web API Request\n const viewerRequestUrl = cfRequest.headers?.[\"x-original-request-url\"]?.[0]?.value;\n const originRequestUrl = `${cfRequest.headers.host[0].value}${cfRequest.uri}`;\n const url = `https://${viewerRequestUrl ? viewerRequestUrl : originRequestUrl}${cfRequest.querystring ? \"?\" + cfRequest.querystring : \"\"}`;\n\n const headers = new Headers();\n Object.entries(cfRequest.headers).forEach(([key, values]) => {\n values.forEach(({ value }) => headers.append(key, value));\n });\n\n const webRequest = new Request(url, {\n method: cfRequest.method,\n headers: headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n const responseHeaders: CloudFrontHeaders = {};\n Object.entries(result.headers).forEach(([key, value]) => {\n responseHeaders[key.toLowerCase()] = [{ key, value }];\n });\n\n return {\n status: result.status.toString(),\n statusDescription: statusDescription(result.status),\n headers: responseHeaders,\n body: result.body,\n };\n }\n\n // Allow request to continue to origin\n return cfRequest;\n}\n","/**\n * Default bot detection logic using multiple signals.\n * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.\n * @param request The incoming request to analyze\n * @returns true if the request appears to be from a bot, false otherwise\n */\nexport function defaultBotDetector(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n lowerCaseUserAgent.includes(\"headless\") ||\n lowerCaseUserAgent.includes(\"puppeteer\") ||\n !secChUa;\n\n const isBrowserMissingSecChUa =\n !lowerCaseUserAgent.includes(\"headless\") &&\n !lowerCaseUserAgent.includes(\"puppeteer\") &&\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && isBrowserMissingSecChUa) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n}\n"],"mappings":"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","names":["index_exports","__export","CDNStatusDescription","EnforcementMode","HandlerAction","LicenseTokenInvalidReason","SupertabConnect","defaultBotDetector","__toCommonJS","EnforcementMode","LicenseTokenInvalidReason","FASTLY_BACKEND","HandlerAction","CDNStatusDescription","lazyImport","load","cached","loadJwtVerify","loadDecodeJwt","loadDecodeProtectedHeader","loadKeyImport","loadJwtSign","scorePathPattern","pattern","path","anchored","pat","hasWildcard","regexBody","regexStr","SDK_USER_AGENT","licenseTokenCache","LICENSE_XML_TTL_SECONDS","licenseXmlCache","evictExpiredLicenseXml","now","origin","entry","getCachedToken","cacheKey","debug","cached","retrieveLicenseToken","tokenEndpoint","requestOptions","response","errorBody","errorMessage","data","parseError","error","fetchLicenseXml","resourceUrl","debug","origin","cached","licenseXmlCache","now","LICENSE_XML_TTL_SECONDS","licenseXmlUrl","response","SDK_USER_AGENT","xml","evictExpiredLicenseXml","parseContentElements","contentBlocks","contentRegex","urlRegex","serverRegex","licenseRegex","elementCount","match","attrs","body","urlMatch","serverMatch","licenseMatch","missing","findBestMatchingContent","parsed","host","path","bestMatch","bestSpecificity","block","patternPath","patternUrl","specificity","scorePathPattern","obtainLicenseToken","clientId","clientSecret","resourceUrl","debug","xml","fetchLicenseXml","contentBlocks","parseContentElements","matchedContent","findBestMatchingContent","patterns","cacheKey","cached","getCachedToken","tokenEndpoint","payload","requestOptions","SDK_USER_AGENT","token","retrieveLicenseToken","decodeJwt","loadDecodeJwt","claims","licenseTokenCache","jwksCache","JWKS_CACHE_TTL_MS","JwksKeyNotFoundError","kid","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","cached","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","clearJwksCache","recordEvent","apiKey","baseUrl","eventName","properties","licenseId","debug","payload","options","SDK_USER_AGENT","FASTLY_BACKEND","response","error","DENIED_HEADERS","toEventProperties","headers","result","key","value","lowerKey","stripTrailingSlash","value","reasonToErrorDescription","reason","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","decodeProtectedHeader","loadDecodeProtectedHeader","decodeJwt","loadDecodeJwt","jwtVerify","loadJwtVerify","header","error","payload","licenseId","issuer","normalizedIssuer","normalizedBaseUrl","audienceValues","entry","requestUrlNormalized","normalizedAudience","verify","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","JwksKeyNotFoundError","clearJwksCache","generateLicenseLink","baseURL","err","buildSignalResult","licenseLink","reasonToRslError","sanitizeHeaderValue","buildBlockResult","rslError","status","errorDescription","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","hostRSLicenseXML","merchantSystemUrn","licenseUrl","response","licenseXml","verifyAndRecordEvent","params","verification","eventPromise","recordEvent","toEventProperties","handleCloudflareRequest","handler","request","ctx","result","originResponse","response","key","value","handleFastlyRequest","originBackend","rslOptions","originalUrl","hostRSLicenseXML","webRequest","statusDescription","status","handleCloudfrontRequest","event","cfRequest","viewerRequestUrl","originRequestUrl","url","headers","values","responseHeaders","defaultBotDetector","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","isBrowserMissingSecChUa","missingHeaders","lowBotScore","_SupertabConnect","config","reset","url","options","baseUrl","result","verifyLicenseToken","verifyAndRecordEvent","request","ctx","auth","token","userAgent","verification","buildBlockResult","buildSignalResult","obtainLicenseToken","env","instance","handleCloudflareRequest","err","merchantApiKey","originBackend","botDetector","enforcement","rslOptions","handleFastlyRequest","event","handleCloudfrontRequest","SupertabConnect"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/jose.ts","../src/url-pattern.ts","../src/version.ts","../src/customer.ts","../src/jwks.ts","../src/events.ts","../src/headers.ts","../src/license.ts","../src/cdn.ts","../src/bots.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n EnforcementMode,\n BotDetector,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n FastlyHandlerOptions,\n} from \"./types\";\nimport {\n obtainLicenseToken as obtainLicenseTokenHelper,\n UsageType,\n} from \"./customer\";\nimport {\n buildBlockResult,\n buildSignalResult,\n verifyLicenseToken as verifyLicenseTokenHelper,\n verifyAndRecordEvent,\n} from \"./license\";\nimport {\n handleCloudflareRequest,\n handleFastlyRequest,\n handleCloudfrontRequest,\n} from \"./cdn\";\nimport {\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n} from \"./types\";\n\nexport {\n EnforcementMode,\n HandlerAction,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n UsageType,\n};\nexport type {\n SupertabConnectConfig,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n BotDetector,\n HandlerResult,\n FastlyHandlerOptions,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n};\nexport { defaultBotDetector } from \"./bots\";\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private enforcement!: EnforcementMode;\n private botDetector?: BotDetector;\n private debug!: boolean;\n\n private static _instance: SupertabConnect | null = null;\n\n /**\n * Create a new SupertabConnect instance (singleton).\n * Returns the existing instance if one exists with the same config.\n * @param config SDK configuration including apiKey\n * @param reset Pass true to replace an existing instance with different config\n * @throws If an instance with different config already exists and reset is false\n */\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (config.apiKey !== SupertabConnect._instance.apiKey) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey) {\n throw new Error(\n \"Missing required configuration: apiKey is required\"\n );\n }\n this.apiKey = config.apiKey;\n this.enforcement = config.enforcement ?? EnforcementMode.SOFT;\n this.botDetector = config.botDetector;\n this.debug = config.debug ?? false;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n /**\n * Clear the singleton instance, allowing a new one to be created with different config.\n */\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Get the current base URL for API requests.\n */\n public static getBaseUrl(): string {\n return SupertabConnect.baseUrl;\n }\n\n /**\n * Pure token verification — verifies a license token without recording any events.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.baseUrl Optional override for the Supertab Connect API base URL\n * @param options.debug Enable debug logging (default: false)\n * @returns A promise that resolves with the verification result\n */\n static async verify(options: {\n token: string;\n resourceUrl: string;\n baseUrl?: string;\n debug?: boolean;\n }): Promise<RSLVerificationResult> {\n const baseUrl = options.baseUrl ?? SupertabConnect.baseUrl;\n\n const result = await verifyLicenseTokenHelper({\n licenseToken: options.token,\n requestUrl: options.resourceUrl,\n supertabBaseUrl: baseUrl,\n debug: options.debug ?? false,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Verify a license token and record an analytics event.\n * Uses the instance's apiKey for event recording.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.userAgent Optional user agent string for event recording\n * @param options.requestHeaders Optional request headers to include in the event properties\n * @param options.debug Enable debug logging (default: false)\n * @param options.ctx Optional execution context with waitUntil for non-blocking event recording\n * @returns A promise that resolves with the verification result\n */\n async verifyAndRecord(options: {\n token: string;\n resourceUrl: string;\n userAgent?: string;\n requestHeaders?: Record<string, string>;\n debug?: boolean;\n ctx?: ExecutionContext;\n }): Promise<RSLVerificationResult> {\n const result = await verifyAndRecordEvent({\n token: options.token,\n url: options.resourceUrl,\n userAgent: options.userAgent ?? \"unknown\",\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: options.debug ?? this.debug,\n apiKey: this.apiKey!,\n ctx: options.ctx,\n requestHeaders: options.requestHeaders,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Handle an incoming request by extracting the license token, verifying it, and recording an analytics event.\n * When no token is present, bot detection and enforcement mode determine the response.\n * @param request The incoming HTTP request\n * @param ctx Execution context for non-blocking event recording.\n * Pass this from your platform which has/requires this context (e.g. Cloudflare Workers)\n * @returns A promise that resolves with the handler result indicating ALLOW or BLOCK request\n */\n async handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult> {\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"License \") ? auth.slice(8) : null;\n const url = request.url;\n const userAgent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // Token present → ALWAYS validate, regardless of mode or bot detection\n if (token) {\n if (this.enforcement === EnforcementMode.DISABLED) {\n return { action: HandlerAction.ALLOW };\n }\n const verification = await verifyAndRecordEvent({\n token,\n url,\n userAgent,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: this.debug,\n apiKey: this.apiKey!,\n ctx,\n requestHeaders: Object.fromEntries(request.headers.entries()),\n });\n if (!verification.valid) {\n return buildBlockResult({\n reason: verification.reason,\n error: verification.error,\n requestUrl: url,\n });\n }\n return { action: HandlerAction.ALLOW };\n }\n\n // No token from here on\n const isBot = this.botDetector?.(request, ctx) ?? false;\n\n if (!isBot) {\n return { action: HandlerAction.ALLOW };\n }\n\n // Bot detected, no token — enforcement mode decides\n switch (this.enforcement) {\n case EnforcementMode.STRICT:\n return buildBlockResult({\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: \"Authorization header missing or malformed\",\n requestUrl: url,\n });\n case EnforcementMode.SOFT:\n return buildSignalResult(url);\n default: // DISABLED\n return { action: HandlerAction.ALLOW };\n }\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * If usage type is specified and matching serverless content permits it, skips token request and returns undefined.\n * @param options.clientId OAuth client identifier.\n * @param options.clientSecret OAuth client secret for client_credentials flow.\n * @param options.resourceUrl Resource URL attempting to access with a License.\n * @param options.usage Optional usage type.\n * If specified and a matching serverless content exists in license, no token is issued\n * @param options.debug Enable debug logging (default: false).\n * @returns Promise resolving to the issued license access token string, or `undefined` when no token is needed.\n */\n static async obtainLicenseToken(options: {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n usage?: UsageType;\n debug?: boolean;\n }): Promise<string | undefined> {\n return obtainLicenseTokenHelper({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n resourceUrl: options.resourceUrl,\n usage: options.usage,\n debug: options.debug,\n });\n }\n\n /**\n * Handle incoming requests for Cloudflare Workers.\n * Pass this directly as your Worker's fetch handler.\n * @param request The incoming Worker request\n * @param env Worker environment bindings containing MERCHANT_API_KEY\n * @param ctx Worker execution context for non-blocking event recording\n * @param options Optional configuration items\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: ExecutionContext,\n options?: {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n }\n ): Promise<Response> {\n try {\n const instance = new SupertabConnect({\n apiKey: env.MERCHANT_API_KEY,\n botDetector: options?.botDetector,\n enforcement: options?.enforcement,\n });\n return await handleCloudflareRequest(instance, request, ctx);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudflareHandleRequests failed:\", err);\n return await fetch(request);\n }\n }\n\n /**\n * Handle incoming requests for Fastly Compute.\n * @param request The incoming Fastly request\n * @param merchantApiKey The merchant API key for authentication\n * @param originBackend The Fastly backend name to forward allowed requests to\n * @param options Optional configuration items\n * @param options.enableRSL Serve license.xml at /license.xml for RSL-compliant clients (default: false)\n * @param options.merchantSystemUrn Required when enableRSL is true; the merchant system URN used to fetch license.xml\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async fastlyHandleRequests(\n request: Request,\n merchantApiKey: string,\n originBackend: string,\n options?: FastlyHandlerOptions\n ): Promise<Response> {\n try {\n const { botDetector, enforcement } = options ?? {};\n\n const instance = new SupertabConnect({\n apiKey: merchantApiKey,\n botDetector,\n enforcement,\n });\n\n let rslOptions: { baseUrl: string; merchantSystemUrn: string } | undefined;\n if (options?.enableRSL) {\n rslOptions = {\n baseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: options.merchantSystemUrn,\n };\n }\n\n return await handleFastlyRequest(\n instance,\n request,\n originBackend,\n rslOptions\n );\n } catch (err) {\n console.error(\"[SupertabConnect] fastlyHandleRequests failed:\", err);\n return await fetch(request, { backend: originBackend } as RequestInit);\n }\n }\n\n /**\n * Handle incoming requests for AWS CloudFront Lambda@Edge.\n * Use as the handler for an origin-request LambdaEdge function.\n * @param event The CloudFront origin-request event\n * @param options Configuration including apiKey and optional botDetector/enforcement\n */\n static async cloudfrontHandleRequests<TRequest extends Record<string, any>>(\n event: CloudFrontRequestEvent<TRequest>,\n options: CloudfrontHandlerOptions\n ): Promise<CloudFrontRequestResult<TRequest>> {\n const request = event?.Records?.[0]?.cf?.request as TRequest ?? {} as CloudFrontRequestResult<TRequest>;\n try {\n const license_auth_header = request.headers?.[\"x-license-auth\"];\n if (!license_auth_header) {\n // No license auth header means the request is either from a human or from an unidentifiable bot.\n // No reasons to waste compute resources on the rest of the checks.\n return request;\n }\n const instance = new SupertabConnect({\n apiKey: options.apiKey,\n enforcement: options.enforcement\n });\n return await handleCloudfrontRequest(instance, event);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudfrontHandleRequests failed:\", err);\n return request;\n }\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nexport enum EnforcementMode {\n DISABLED = \"disabled\",\n SOFT = \"soft\",\n STRICT = \"strict\",\n}\n\nexport interface ExecutionContext {\n waitUntil(promise: Promise<void>): void;\n}\n\nexport type BotDetector = (request: Request, ctx?: ExecutionContext) => boolean;\n\nexport interface SupertabConnectConfig {\n apiKey: string;\n enforcement?: EnforcementMode;\n botDetector?: BotDetector;\n debug?: boolean;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n properties: Record<string, string>;\n}\n\nexport type LicenseTokenVerificationResult =\n | { valid: true; licenseId?: string; payload: JWTPayload }\n | { valid: false; reason: LicenseTokenInvalidReason; error: string; licenseId?: string };\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n SERVER_ERROR = \"server_error\",\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var fastly: object | undefined;\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}\n\nexport enum HandlerAction {\n ALLOW = \"allow\",\n BLOCK = \"block\",\n}\n\nexport type HandlerResult =\n | { action: HandlerAction.ALLOW; headers?: Record<string, string> }\n | { action: HandlerAction.BLOCK; status: number; body: string; headers: Record<string, string> };\n\nexport enum CDNStatusDescription {\n Unauthorized = \"Unauthorized\",\n PaymentRequired = \"Payment Required\",\n Forbidden = \"Forbidden\",\n ServiceUnavailable = \"Service Unavailable\",\n Error = \"Error\",\n}\n\n// CloudFront Lambda@Edge types\n// Uses permissive types to be compatible with aws-lambda package types\nexport interface CloudFrontHeaders {\n [key: string]: Array<{ key?: string; value: string }>;\n}\n\nexport interface CloudFrontResultResponse {\n status: string;\n statusDescription?: CDNStatusDescription;\n headers?: CloudFrontHeaders;\n bodyEncoding?: \"text\" | \"base64\";\n body?: string;\n}\n\n// CloudFrontRequestEvent uses a generic request type to accept aws-lambda's CloudFrontRequest\nexport interface CloudFrontRequestEvent<TRequest = Record<string, any>> {\n Records: Array<{\n cf: {\n config?: {\n distributionDomainName?: string;\n distributionId?: string;\n eventType?: string;\n requestId?: string;\n };\n request: TRequest & {\n uri: string;\n method: string;\n querystring: string;\n headers: CloudFrontHeaders;\n };\n };\n }>;\n}\n\n// Result can be either the original request (pass-through) or a response\n// Using generic to preserve the original request type for pass-through\nexport type CloudFrontRequestResult<TRequest = Record<string, any>> = TRequest | CloudFrontResultResponse;\n\nexport interface CloudfrontHandlerOptions {\n apiKey: string;\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\nexport type RSLVerificationResult = {\n valid: boolean;\n error?: string;\n};\n\ninterface FastlyHandlerBaseOptions {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\ninterface FastlyHandlerWithRSL extends FastlyHandlerBaseOptions {\n enableRSL: true;\n merchantSystemUrn: string;\n}\n\ninterface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {\n enableRSL?: false;\n merchantSystemUrn?: never;\n}\n\nexport type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;\n","// Per-subpath lazy loaders for jose — each caches its own promise to avoid redundant imports.\n\nfunction lazyImport<T>(load: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n return () => {\n if (!cached) {\n cached = load();\n }\n return cached;\n };\n}\n\nexport const loadJwtVerify = lazyImport(() => import(\"jose/jwt/verify\"));\nexport const loadDecodeJwt = lazyImport(() => import(\"jose/jwt/decode\"));\nexport const loadDecodeProtectedHeader = lazyImport(() => import(\"jose/decode/protected_header\"));\nexport const loadKeyImport = lazyImport(() => import(\"jose/key/import\"));\nexport const loadJwtSign = lazyImport(() => import(\"jose/jwt/sign\"));\n","/**\n * Match a URL path against a robots.txt-style pattern.\n *\n * - `*` matches zero or more characters (including `/`)\n * - Trailing `$` anchors the match to the end of the path\n * - Without `$`, patterns without `*` match as prefix at segment boundaries\n * (e.g. `/content` matches `/content/article` but not `/content-other`)\n * - Without `$`, patterns with `*` are prefix-matched from the start\n *\n * Returns specificity (number of literal characters) on match, or -1 on no match.\n */\nexport function scorePathPattern(pattern: string, path: string): number {\n let anchored = false;\n let pat = pattern;\n\n if (pat.endsWith(\"$\")) {\n anchored = true;\n pat = pat.slice(0, -1);\n }\n\n const hasWildcard = pat.includes(\"*\");\n\n // Escape regex special chars (except *) and treat them as literals\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Converts wildcard * to regex equivalent .*\n const regexBody = escaped.replace(/\\*/g, \".*\");\n\n let regexStr: string;\n if (anchored) {\n regexStr = `^${regexBody}$`;\n } else if (hasWildcard) {\n regexStr = `^${regexBody}`;\n } else {\n // No wildcards, no anchor: prefix match at segment boundary\n // Special case: \"/\" matches all paths\n if (pat === \"/\") {\n regexStr = `^/`;\n } else {\n regexStr = `^${regexBody}(/|$)`;\n }\n }\n\n if (new RegExp(regexStr).test(path)) {\n return pat.replace(/\\*/g, \"\").length;\n }\n\n return -1;\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_USER_AGENT = `supertab-connect-sdk-typescript/${typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"unknown\"}`;\n","import { loadKeyImport, loadJwtSign, loadDecodeJwt } from \"./jose\";\nimport { scorePathPattern } from \"./url-pattern\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype CachedToken = { token: string; exp: number };\n\n// In-memory cache for license tokens, keyed by \"clientId:server:urlPattern\"\nconst licenseTokenCache = new Map<string, CachedToken>();\n\ntype CachedLicenseXml = { xml: string; fetchedAt: number };\nconst LICENSE_XML_TTL_SECONDS = 15 * 60; // 15 minutes\n\n// In-memory cache for license.xml content, keyed by origin (e.g. \"https://example.com\")\nconst licenseXmlCache = new Map<string, CachedLicenseXml>();\n\nfunction evictExpiredLicenseXml(): void {\n const now = Math.floor(Date.now() / 1000);\n for (const [origin, entry] of licenseXmlCache) {\n if (now - entry.fetchedAt >= LICENSE_XML_TTL_SECONDS) {\n licenseXmlCache.delete(origin);\n }\n }\n}\n\nfunction getCachedToken(\n cacheKey: string,\n debug?: boolean\n): string | null {\n const cached = licenseTokenCache.get(cacheKey);\n if (!cached) return null;\n\n const now = Math.floor(Date.now() / 1000);\n if (cached.exp > now + 30) {\n if (debug) {\n console.debug(\n `Using cached license token (expires in ${cached.exp - now}s)`\n );\n }\n return cached.token;\n }\n\n if (debug) {\n console.debug(\"Cached license token expired or expiring soon, refreshing\");\n }\n licenseTokenCache.delete(cacheKey);\n return null;\n}\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nexport enum UsageType {\n ALL = \"all\",\n SEARCH = \"search\",\n AI_ALL = \"ai-all\",\n AI_TRAIN = \"ai-train\",\n AI_INDEX = \"ai-index\",\n AI_INPUT = \"ai-input\",\n}\n\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n usage?: UsageType;\n debug?: boolean;\n};\n\ntype ContentBlock = {\n urlPattern: string;\n licenseXml: string;\n server?: string;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\n) {\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: { access_token?: string };\n try {\n data = await response.json() as { access_token?: string };\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const { importPKCS8 } = await loadKeyImport();\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { SignJWT } = await loadJwtSign();\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nasync function fetchLicenseXml(\n resourceUrl: string,\n debug: boolean | undefined\n): Promise<string> {\n const origin = new URL(resourceUrl).origin;\n\n const cached = licenseXmlCache.get(origin);\n if (cached) {\n const now = Math.floor(Date.now() / 1000);\n if (now - cached.fetchedAt < LICENSE_XML_TTL_SECONDS) {\n if (debug) {\n console.debug(`Using cached license.xml for origin ${origin} (expires in ${LICENSE_XML_TTL_SECONDS - (now - cached.fetchedAt)}s)`);\n }\n return cached.xml;\n }\n if (debug) {\n console.debug(`Cached license.xml for origin ${origin} expired, re-fetching`);\n }\n licenseXmlCache.delete(origin);\n }\n\n const licenseXmlUrl = `${origin}/license.xml`;\n const response = await fetch(licenseXmlUrl, {\n headers: { \"User-Agent\": SDK_USER_AGENT },\n });\n if (!response.ok) {\n if (debug) {\n console.error(`Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`);\n }\n throw new Error(\n `Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`\n );\n }\n\n const xml = await response.text();\n if (debug) {\n console.debug(\"Fetched license.xml from\", licenseXmlUrl);\n }\n evictExpiredLicenseXml();\n licenseXmlCache.set(origin, { xml, fetchedAt: Math.floor(Date.now() / 1000) });\n return xml;\n}\n\nfunction parseContentElements(xml: string, debug?: boolean): ContentBlock[] {\n const contentBlocks: ContentBlock[] = [];\n const contentRegex = /<content\\s([^>]*)>([\\s\\S]*?)<\\/content>/gi;\n const urlRegex = /url\\s*=\\s*\"([^\"]*)\"/i;\n const serverRegex = /server\\s*=\\s*\"([^\"]*)\"/i;\n const licenseRegex = /<license[^>]*>[\\s\\S]*?<\\/license>/i;\n\n let elementCount = 0;\n let match;\n while ((match = contentRegex.exec(xml)) !== null) {\n elementCount++;\n const attrs = match[1];\n const body = match[2];\n const urlMatch = attrs.match(urlRegex);\n const serverMatch = attrs.match(serverRegex);\n const licenseMatch = body.match(licenseRegex);\n\n if (urlMatch && licenseMatch) {\n contentBlocks.push({\n urlPattern: urlMatch[1],\n server: serverMatch?.[1],\n licenseXml: licenseMatch[0],\n });\n } else if (debug) {\n const missing = [\n !urlMatch && \"url\",\n !licenseMatch && \"<license>\",\n ].filter(Boolean).join(\", \");\n console.debug(`Skipping <content> element #${elementCount}: missing ${missing}`);\n }\n }\n\n if (debug) {\n console.debug(`Found ${elementCount} <content> element(s), ${contentBlocks.length} valid`);\n }\n\n return contentBlocks;\n}\n\n/**\n * Check if <license> section permits the chosen usage type without prohibiting it\n * @param licenseXml\n * @param usage\n */\nfunction licensePermitsUsage(\n licenseXml: string,\n usage: UsageType\n): boolean {\n const permitsRegex = /<permits\\b[^>]*type\\s*=\\s*\"usage\"[^>]*>([\\s\\S]*?)<\\/permits>/gi;\n const prohibitsRegex = /<prohibits\\b[^>]*type\\s*=\\s*\"usage\"[^>]*>([\\s\\S]*?)<\\/prohibits>/gi;\n\n let match: RegExpExecArray | null;\n\n // Check for <prohibits> first - it takes precedence\n while ((match = prohibitsRegex.exec(licenseXml)) !== null) {\n const prohibitedUsages = match[1]\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (\n prohibitedUsages.includes(UsageType.ALL) ||\n prohibitedUsages.includes(usage)\n ) {\n return false;\n }\n }\n\n // Now we can safely look for <permits>\n while ((match = permitsRegex.exec(licenseXml)) !== null) {\n const permittedUsages = match[1]\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (\n permittedUsages.includes(UsageType.ALL) ||\n permittedUsages.includes(usage)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction findBestMatchingContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n debug?: boolean\n): ContentBlock | null {\n const parsed = new URL(resourceUrl);\n const host = parsed.host;\n const path = parsed.pathname;\n\n if (debug) {\n console.debug(`Matching resource URL: ${resourceUrl} (host=${host}, path=${path})`);\n }\n\n let bestMatch: ContentBlock | null = null;\n let bestSpecificity = -1;\n\n for (const block of contentBlocks) {\n let patternPath: string;\n const isPathOnly = block.urlPattern.startsWith(\"/\");\n\n if (isPathOnly) {\n patternPath = block.urlPattern;\n } else {\n let patternUrl: URL;\n try {\n patternUrl = new URL(block.urlPattern);\n } catch {\n if (debug) {\n console.debug(`Skipping block with invalid URL pattern: ${block.urlPattern}`);\n }\n continue;\n }\n\n if (patternUrl.host !== host) {\n if (debug) {\n console.debug(`Skipping block: host mismatch (pattern=${patternUrl.host}, resource=${host})`);\n }\n continue;\n }\n\n patternPath = patternUrl.pathname;\n }\n\n // Exact match — highest priority, return immediately\n if (patternPath === path) {\n if (debug) {\n console.debug(`Exact match found: ${block.urlPattern}`);\n }\n return block;\n }\n\n // Pattern match (wildcards, prefix, anchored)\n const specificity = scorePathPattern(patternPath, path);\n if (specificity > bestSpecificity) {\n bestSpecificity = specificity;\n bestMatch = block;\n }\n }\n\n if (debug) {\n if (bestMatch) {\n console.debug(`Wildcard match found: ${bestMatch.urlPattern} (specificity=${bestSpecificity})`);\n } else {\n console.debug(`No matching content block found for ${resourceUrl}`);\n }\n }\n\n return bestMatch;\n}\n\nexport { parseContentElements, findBestMatchingContent };\nexport type { ContentBlock };\n\n/**\n * Find serverless content with <permits> section for the selected usage type that matches with the requested resource.\n * @param contentBlocks Parsed content blocks of the processed License XML\n * @param resourceUrl Requested resource\n * @param usage One of usage types as defined in RSL Specification\n * @param debug Enables debug printouts if true\n */\nfunction findServerlessUsageContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n usage: UsageType,\n debug?: boolean\n): ContentBlock | null {\n const matchingUsageBlocks = contentBlocks.filter(\n (block) => !block.server && licensePermitsUsage(block.licenseXml, usage)\n );\n\n return findBestMatchingContent(matchingUsageBlocks, resourceUrl, debug);\n}\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n resourceUrl,\n usage,\n debug,\n}: ObtainLicenseTokenParams): Promise<string | undefined> {\n const xml = await fetchLicenseXml(resourceUrl, debug);\n if (debug) {\n console.debug(`Fetched license.xml (${xml.length} chars)`);\n }\n const contentBlocks = parseContentElements(xml, debug);\n\n if (contentBlocks.length === 0) {\n if (debug) {\n console.error(\"No valid <content> elements with <license> found in license.xml\");\n }\n throw new Error(\n \"No valid <content> elements with <license> found in license.xml\"\n );\n }\n\n if (usage) {\n const serverlessUsageContent = findServerlessUsageContent(\n contentBlocks,\n resourceUrl,\n usage,\n debug\n );\n\n if (serverlessUsageContent) {\n if (debug) {\n console.debug(\"Matched serverless content to usage and resource URL combination, skipping license token request. \");\n console.debug(\"URL: \" + resourceUrl + \", Usage: \" + usage);\n }\n return undefined;\n }\n }\n\n const tokenContentBlocks = contentBlocks.filter((block) => !!block.server);\n const matchedContent = findBestMatchingContent(tokenContentBlocks, resourceUrl, debug);\n if (!matchedContent) {\n if (debug) {\n const patterns = tokenContentBlocks.map(b => b.urlPattern).join(\", \");\n console.error(`No <content> element matches resource URL: ${resourceUrl}. Available patterns: ${patterns}`);\n }\n throw new Error(\n `No <content> element in license.xml matches resource URL: ${resourceUrl}`\n );\n }\n\n if (debug) {\n console.debug(\"Matched content block for resource URL:\", resourceUrl);\n console.debug(\"Using license XML:\", matchedContent.licenseXml);\n }\n\n // Cache tokens by server + urlPattern so path-only patterns (e.g. \"/articles/*\")\n // on different origins/servers don't collide with each other.\n const cacheKey = `${clientId}:${matchedContent.server}:${matchedContent.urlPattern}`;\n const cached = getCachedToken(cacheKey, debug);\n if (cached) return cached;\n\n const tokenEndpoint = matchedContent.server + '/token';\n if (debug) {\n console.debug(`Requesting license token from ${tokenEndpoint}`);\n }\n\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\n license: matchedContent.licenseXml,\n resource: matchedContent.urlPattern,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n const token = await retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n\n try {\n const { decodeJwt } = await loadDecodeJwt();\n const claims = decodeJwt(token);\n if (claims.exp) {\n licenseTokenCache.set(cacheKey, { token, exp: claims.exp });\n }\n } catch {\n if (debug) {\n console.debug(\"Failed to decode token for caching, skipping cache\");\n }\n }\n\n return token;\n}\n\nexport type { ObtainLicenseTokenParams };\n","import type { JSONWebKeySet } from \"jose\";\nimport { FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype JwksCacheEntry = { data: JSONWebKeySet; cachedAt: number };\nconst jwksCache = new Map<string, JwksCacheEntry>();\nconst JWKS_CACHE_TTL_MS = 48 * 60 * 60 * 1000; // 48 hours\n\nexport class JwksKeyNotFoundError extends Error {\n constructor(kid: string | undefined) {\n super(`No matching platform key found: ${kid}`);\n this.name = \"JwksKeyNotFoundError\";\n }\n}\n\ntype FetchJwksParams = {\n cacheKey: string;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<JSONWebKeySet> {\n const cached = jwksCache.get(cacheKey);\n if (cached && (Date.now() - cached.cachedAt) < JWKS_CACHE_TTL_MS) {\n return cached.data;\n }\n\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json() as JSONWebKeySet;\n jwksCache.set(cacheKey, { data: jwksData, cachedAt: Date.now() });\n return jwksData;\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<JSONWebKeySet> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n if (debug) {\n console.debug(`Fetching platform JWKS from URL: ${jwksUrl}`);\n }\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n","import { EventPayload, FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\nexport async function recordEvent({\n apiKey,\n baseUrl,\n eventName,\n properties,\n licenseId,\n debug = false,\n}: {\n apiKey: string;\n baseUrl: string;\n eventName: string;\n properties: Record<string, string>;\n licenseId?: string;\n debug?: boolean;\n}): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: FetchOptions = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: JSON.stringify(payload),\n };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(`${baseUrl}/events`, options);\n\n if (!response.ok && debug) {\n console.error(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n if (debug) {\n console.error(\"Error recording event:\", error);\n }\n }\n}\n","const DENIED_HEADERS = new Set([\n // Credentials\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n \"x-api-key\",\n \"x-amz-security-token\",\n // Already captured as properties.user_agent — avoid duplication\n \"user-agent\",\n // SDK-internal plumbing (not useful as analytics signal)\n \"x-license-auth\",\n]);\n\n/**\n * Transform a raw headers record into event properties: lowercase keys,\n * drop sensitive headers, and apply an `h_` prefix. Called from\n * verifyAndRecordEvent so both automatic and manual paths enforce the\n * same rules.\n */\nexport function toEventProperties(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase();\n if (!DENIED_HEADERS.has(lowerKey)) {\n result[`h_${lowerKey}`] = value;\n }\n }\n return result;\n}\n","import type { JWTPayload, JWTHeaderParameters } from \"jose\";\nimport { loadJwtVerify, loadDecodeJwt, loadDecodeProtectedHeader } from \"./jose\";\n\ninterface LicenseJWTPayload extends JWTPayload {\n license_id?: string;\n}\nimport {\n ExecutionContext,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks, clearJwksCache, JwksKeyNotFoundError } from \"./jwks\";\nimport { recordEvent } from \"./events\";\nimport { SDK_USER_AGENT } from \"./version\";\nimport { toEventProperties } from \"./headers\";\n\nconst stripTrailingSlash = (value: string) => value.trim().replace(/\\/+$/, \"\");\n\nfunction reasonToErrorDescription(reason: LicenseTokenInvalidReason): string {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n return \"Authorization header missing or malformed\";\n case LicenseTokenInvalidReason.INVALID_ALG:\n return \"Unsupported token algorithm\";\n case LicenseTokenInvalidReason.EXPIRED:\n return \"The license token has expired\";\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n return \"The license token signature is invalid\";\n case LicenseTokenInvalidReason.INVALID_HEADER:\n return \"The license token header is malformed\";\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n return \"The license token payload is malformed\";\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return \"The license token issuer is not recognized\";\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return \"The license does not grant access to this resource\";\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return \"The server encountered an error validating the license\";\n default:\n return \"License token missing, expired, revoked, or malformed\";\n }\n}\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n const { decodeProtectedHeader } = await loadDecodeProtectedHeader();\n const { decodeJwt } = await loadDecodeJwt();\n const { jwtVerify } = await loadJwtVerify();\n\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.MISSING_TOKEN),\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_HEADER),\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ALG),\n };\n }\n\n let payload: LicenseJWTPayload;\n try {\n payload = decodeJwt(licenseToken) as LicenseJWTPayload;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_PAYLOAD),\n };\n }\n\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n const normalizedIssuer = issuer ? stripTrailingSlash(issuer) : undefined;\n const normalizedBaseUrl = stripTrailingSlash(supertabBaseUrl);\n\n if (!normalizedIssuer || !normalizedIssuer.startsWith(normalizedBaseUrl)) {\n if (debug) {\n console.error(\"License JWT issuer is missing or malformed:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ISSUER),\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_AUDIENCE),\n licenseId,\n };\n }\n\n const verify = async (): Promise<LicenseTokenVerificationResult> => {\n let jwks;\n try {\n jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n } catch (error) {\n if (debug) {\n console.error(\"Failed to fetch platform JWKS:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SERVER_ERROR,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SERVER_ERROR),\n licenseId,\n };\n }\n\n try {\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new JwksKeyNotFoundError(jwtHeader.kid);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof JwksKeyNotFoundError) {\n throw error;\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.EXPIRED),\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED),\n licenseId,\n };\n }\n };\n\n try {\n return await verify();\n } catch (error) {\n if (error instanceof JwksKeyNotFoundError) {\n if (debug) {\n console.debug(\"Key not found in cached JWKS, clearing cache and retrying...\");\n }\n clearJwksCache();\n return await verify();\n }\n throw error;\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n try {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n } catch (err) {\n console.error(\"[SupertabConnect] generateLicenseLink failed to parse URL:\", err);\n return \"/license.xml\";\n }\n}\n\n/**\n * Build a HandlerResult that signals a missing token in soft enforcement mode.\n * Returns headers indicating a license is required without blocking the request.\n */\nexport function buildSignalResult(requestUrl: string): HandlerResult {\n const licenseLink = generateLicenseLink({ requestUrl });\n return {\n action: HandlerAction.ALLOW,\n headers: {\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n \"X-RSL-Status\": \"token_required\",\n \"X-RSL-Reason\": \"missing\",\n },\n };\n}\n\nfunction reasonToRslError(reason: LicenseTokenInvalidReason | string): { rslError: string; status: number } {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n case LicenseTokenInvalidReason.INVALID_ALG:\n return { rslError: \"invalid_request\", status: 401 };\n case LicenseTokenInvalidReason.EXPIRED:\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return { rslError: \"invalid_token\", status: 401 };\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return { rslError: \"insufficient_scope\", status: 403 };\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return { rslError: \"server_error\", status: 503 };\n default:\n return { rslError: \"invalid_token\", status: 401 };\n }\n}\n\n/**\n * Sanitize a string for safe use in an HTTP header quoted-string (RFC 7230).\n * Strips CR/LF to prevent header injection and escapes backslashes and quotes.\n */\nfunction sanitizeHeaderValue(value: string): string {\n return value\n .replace(/[\\r\\n]/g, \"\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"');\n}\n\nexport function buildBlockResult({\n reason,\n error,\n requestUrl,\n}: {\n reason: LicenseTokenInvalidReason | string;\n error: string;\n requestUrl: string;\n}): HandlerResult {\n const { rslError, status } = reasonToRslError(reason);\n const errorDescription = sanitizeHeaderValue(error);\n const licenseLink = generateLicenseLink({ requestUrl });\n\n return {\n action: HandlerAction.BLOCK,\n status,\n body: `Access to this resource requires a valid license token. Error: ${rslError} - ${error}`,\n headers: {\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\"`,\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n },\n };\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n try {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/rsl+xml\" }),\n });\n } catch (err) {\n console.error(\"[SupertabConnect] hostRSLicenseXML failed:\", err);\n return new Response(\"Bad Gateway\", { status: 502 });\n }\n}\n\nexport type VerifyAndRecordEventParams = {\n token: string;\n url: string;\n userAgent: string;\n supertabBaseUrl: string;\n debug: boolean;\n apiKey: string;\n ctx?: ExecutionContext;\n requestHeaders?: Record<string, string>;\n};\n\nexport async function verifyAndRecordEvent(\n params: VerifyAndRecordEventParams\n): Promise<LicenseTokenVerificationResult> {\n const verification = await verifyLicenseToken({\n licenseToken: params.token,\n requestUrl: params.url,\n supertabBaseUrl: params.supertabBaseUrl,\n debug: params.debug,\n });\n\n const eventPromise = recordEvent({\n apiKey: params.apiKey,\n baseUrl: params.supertabBaseUrl,\n eventName: verification.valid ? \"license_used\" : verification.reason,\n properties: {\n page_url: params.url,\n user_agent: params.userAgent,\n sdk_user_agent: SDK_USER_AGENT,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.valid ? \"success\" : verification.reason,\n ...toEventProperties(params.requestHeaders ?? {}),\n },\n licenseId: verification.licenseId,\n debug: params.debug,\n });\n if (params.ctx?.waitUntil) {\n params.ctx.waitUntil(eventPromise);\n } else {\n await eventPromise;\n }\n\n return verification;\n}\n","import {\n HandlerAction,\n HandlerResult,\n ExecutionContext,\n CDNStatusDescription,\n CloudFrontHeaders,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n} from \"./types\";\nimport { hostRSLicenseXML } from \"./license\";\n\n// Interface for what the CDN handlers need - avoids circular dependency\ninterface RequestHandler {\n handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;\n}\n\nexport async function handleCloudflareRequest(\n handler: RequestHandler,\n request: Request,\n ctx: ExecutionContext\n): Promise<Response> {\n const result = await handler.handleRequest(request, ctx);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\n/**\n * Handles an Origin request in Fastly. Expects `X-Original-Request-URL` header to contain the original viewer request URL.\n * @param handler Request handler instance that inspects the request and decides whether to allow or block it.\n * @param request Fastly request to process.\n * @param originBackend Fastly backend name used when forwarding allowed requests to origin.\n * @param rslOptions Optional configuration for serving `/license.xml` directly from the edge.\n * @param rslOptions.baseUrl Base URL used when generating the hosted license XML response.\n * @param rslOptions.merchantSystemUrn Merchant system URN for fetching License from Supertab Connect\n */\nexport async function handleFastlyRequest(\n handler: RequestHandler,\n request: Request,\n originBackend: string,\n rslOptions?: {\n baseUrl: string;\n merchantSystemUrn: string;\n }\n): Promise<Response> {\n const originalUrl = request.headers.get(\"x-original-request-url\") || request.url;\n\n if (rslOptions && new URL(originalUrl).pathname === \"/license.xml\") {\n return await hostRSLicenseXML(\n rslOptions.baseUrl,\n rslOptions.merchantSystemUrn\n );\n }\n\n const webRequest = new Request(originalUrl, {\n method: request.method,\n headers: request.headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request, {\n backend: originBackend,\n } as RequestInit);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\nfunction statusDescription(status: number): CDNStatusDescription {\n switch (status) {\n case 401: return CDNStatusDescription.Unauthorized;\n case 402: return CDNStatusDescription.PaymentRequired;\n case 403: return CDNStatusDescription.Forbidden;\n case 503: return CDNStatusDescription.ServiceUnavailable;\n default: return CDNStatusDescription.Error;\n }\n}\n\n/**\n * Handles an Origin request in CloudFront. Expects X-Original-Request-URL header to contain the original viewer request URL.\n * @param handler\n * @param event\n */\nexport async function handleCloudfrontRequest<TRequest extends Record<string, any>>(\n handler: RequestHandler,\n event: CloudFrontRequestEvent<TRequest>\n): Promise<CloudFrontRequestResult<TRequest>> {\n const cfRequest = event.Records[0].cf.request;\n\n // Convert CloudFront request to Web API Request\n const viewerRequestUrl = cfRequest.headers?.[\"x-original-request-url\"]?.[0]?.value;\n const originRequestUrl = `${cfRequest.headers.host[0].value}${cfRequest.uri}`;\n const url = `https://${viewerRequestUrl ? viewerRequestUrl : originRequestUrl}${cfRequest.querystring ? \"?\" + cfRequest.querystring : \"\"}`;\n\n const headers = new Headers();\n Object.entries(cfRequest.headers).forEach(([key, values]) => {\n values.forEach(({ value }) => headers.append(key, value));\n });\n\n const webRequest = new Request(url, {\n method: cfRequest.method,\n headers: headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n const responseHeaders: CloudFrontHeaders = {};\n Object.entries(result.headers).forEach(([key, value]) => {\n responseHeaders[key.toLowerCase()] = [{ key, value }];\n });\n\n return {\n status: result.status.toString(),\n statusDescription: statusDescription(result.status),\n headers: responseHeaders,\n body: result.body,\n };\n }\n\n // Allow request to continue to origin\n return cfRequest;\n}\n","/**\n * Default bot detection logic using multiple signals.\n * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.\n * @param request The incoming request to analyze\n * @returns true if the request appears to be from a bot, false otherwise\n */\nexport function defaultBotDetector(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n lowerCaseUserAgent.includes(\"headless\") ||\n lowerCaseUserAgent.includes(\"puppeteer\") ||\n !secChUa;\n\n const isBrowserMissingSecChUa =\n !lowerCaseUserAgent.includes(\"headless\") &&\n !lowerCaseUserAgent.includes(\"puppeteer\") &&\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && isBrowserMissingSecChUa) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n}\n"],"mappings":"skBAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,0BAAAE,EAAA,oBAAAC,EAAA,kBAAAC,EAAA,8BAAAC,EAAA,oBAAAC,EAAA,cAAAC,EAAA,uBAAAC,KAAA,eAAAC,GAAAT,ICEO,IAAKU,OACVA,EAAA,SAAW,WACXA,EAAA,KAAO,OACPA,EAAA,OAAS,SAHCA,OAAA,IAuCAC,OACVA,EAAA,cAAgB,wBAChBA,EAAA,eAAiB,yBACjBA,EAAA,YAAc,4BACdA,EAAA,gBAAkB,0BAClBA,EAAA,eAAiB,yBACjBA,EAAA,8BAAgC,wCAChCA,EAAA,QAAU,wBACVA,EAAA,iBAAmB,2BACnBA,EAAA,aAAe,eATLA,OAAA,IAiBCC,EAAiB,cAOlBC,OACVA,EAAA,MAAQ,QACRA,EAAA,MAAQ,QAFEA,OAAA,IASAC,OACVA,EAAA,aAAe,eACfA,EAAA,gBAAkB,mBAClBA,EAAA,UAAY,YACZA,EAAA,mBAAqB,sBACrBA,EAAA,MAAQ,QALEA,OAAA,ICxEZ,SAASC,EAAcC,EAA0C,CAC/D,IAAIC,EAA4B,KAChC,MAAO,KACAA,IACHA,EAASD,EAAK,GAETC,EAEX,CAEO,IAAMC,EAAgBH,EAAW,IAAM,OAAO,iBAAiB,CAAC,EAC1DI,EAAgBJ,EAAW,IAAM,OAAO,iBAAiB,CAAC,EAC1DK,EAA4BL,EAAW,IAAM,OAAO,8BAA8B,CAAC,EACnFM,GAAgBN,EAAW,IAAM,OAAO,iBAAiB,CAAC,EAC1DO,GAAcP,EAAW,IAAM,OAAO,eAAe,CAAC,ECL5D,SAASQ,EAAiBC,EAAiBC,EAAsB,CACtE,IAAIC,EAAW,GACXC,EAAMH,EAENG,EAAI,SAAS,GAAG,IAClBD,EAAW,GACXC,EAAMA,EAAI,MAAM,EAAG,EAAE,GAGvB,IAAMC,EAAcD,EAAI,SAAS,GAAG,EAK9BE,EAFUF,EAAI,QAAQ,qBAAsB,MAAM,EAE9B,QAAQ,MAAO,IAAI,EAEzCG,EAeJ,OAdIJ,EACFI,EAAW,IAAID,CAAS,IACfD,EACTE,EAAW,IAAID,CAAS,GAIpBF,IAAQ,IACVG,EAAW,KAEXA,EAAW,IAAID,CAAS,QAIxB,IAAI,OAAOC,CAAQ,EAAE,KAAKL,CAAI,EACzBE,EAAI,QAAQ,MAAO,EAAE,EAAE,OAGzB,EACT,CC7CO,IAAMI,EAAiB,wCCO9B,IAAMC,EAAoB,IAAI,IAGxBC,EAA0B,GAAK,GAG/BC,EAAkB,IAAI,IAE5B,SAASC,IAA+B,CACtC,IAAMC,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACxC,OAAW,CAACC,EAAQC,CAAK,IAAKJ,EACxBE,EAAME,EAAM,WAAaL,GAC3BC,EAAgB,OAAOG,CAAM,CAGnC,CAEA,SAASE,GACPC,EACAC,EACe,CACf,IAAMC,EAASV,EAAkB,IAAIQ,CAAQ,EAC7C,GAAI,CAACE,EAAQ,OAAO,KAEpB,IAAMN,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACxC,OAAIM,EAAO,IAAMN,EAAM,IACjBK,GACF,QAAQ,MACN,0CAA0CC,EAAO,IAAMN,CAAG,IAC5D,EAEKM,EAAO,QAGZD,GACF,QAAQ,MAAM,2DAA2D,EAE3ET,EAAkB,OAAOQ,CAAQ,EAC1B,KACT,CAYO,IAAKG,OACVA,EAAA,IAAM,MACNA,EAAA,OAAS,SACTA,EAAA,OAAS,SACTA,EAAA,SAAW,WACXA,EAAA,SAAW,WACXA,EAAA,SAAW,WANDA,OAAA,IAuBZ,eAAeC,GACXC,EACAC,EACAL,EACF,CACA,GAAI,CACF,IAAMM,EAAW,MAAM,MAAMF,EAAeC,CAAc,EAE1D,GAAI,CAACC,EAAS,GAAI,CAChB,IAAMC,EAAY,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EAChDE,EAAe,mCACnBF,EAAS,MACX,IAAIA,EAAS,UAAU,GAAGC,EAAY,MAAMA,CAAS,GAAK,EAAE,GAC5D,MAAM,IAAI,MAAMC,CAAY,CAC9B,CAEA,IAAIC,EACJ,GAAI,CACFA,EAAO,MAAMH,EAAS,KAAK,CAC7B,OAASI,EAAY,CACnB,MAAIV,GACF,QAAQ,MACN,kDACAU,CACF,EAEI,IAAI,MAAM,gDAAgD,CAClE,CAEA,GAAI,CAACD,GAAM,aACT,MAAM,IAAI,MAAM,6CAA6C,EAG/D,OAAOA,EAAK,YACd,OAASE,EAAO,CACd,MAAIX,GACF,QAAQ,MAAM,kCAAmCW,CAAK,EAElDA,CACR,CACF,CAyEA,eAAeC,GACbC,EACAC,EACiB,CACjB,IAAMC,EAAS,IAAI,IAAIF,CAAW,EAAE,OAE9BG,EAASC,EAAgB,IAAIF,CAAM,EACzC,GAAIC,EAAQ,CACV,IAAME,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACxC,GAAIA,EAAMF,EAAO,UAAYG,EAC3B,OAAIL,GACF,QAAQ,MAAM,uCAAuCC,CAAM,gBAAgBI,GAA2BD,EAAMF,EAAO,UAAU,IAAI,EAE5HA,EAAO,IAEZF,GACF,QAAQ,MAAM,iCAAiCC,CAAM,uBAAuB,EAE9EE,EAAgB,OAAOF,CAAM,CAC/B,CAEA,IAAMK,EAAgB,GAAGL,CAAM,eACzBM,EAAW,MAAM,MAAMD,EAAe,CAC1C,QAAS,CAAE,aAAcE,CAAe,CAC1C,CAAC,EACD,GAAI,CAACD,EAAS,GACZ,MAAIP,GACF,QAAQ,MAAM,oCAAoCM,CAAa,KAAKC,EAAS,MAAM,EAAE,EAEjF,IAAI,MACR,oCAAoCD,CAAa,KAAKC,EAAS,MAAM,EACvE,EAGF,IAAME,EAAM,MAAMF,EAAS,KAAK,EAChC,OAAIP,GACF,QAAQ,MAAM,2BAA4BM,CAAa,EAEzDI,GAAuB,EACvBP,EAAgB,IAAIF,EAAQ,CAAE,IAAAQ,EAAK,UAAW,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,CAAE,CAAC,EACtEA,CACT,CAEA,SAASE,GAAqBF,EAAaT,EAAiC,CAC1E,IAAMY,EAAgC,CAAC,EACjCC,EAAe,4CACfC,EAAW,uBACXC,EAAc,0BACdC,EAAe,qCAEjBC,EAAe,EACfC,EACJ,MAAQA,EAAQL,EAAa,KAAKJ,CAAG,KAAO,MAAM,CAChDQ,IACA,IAAME,EAAQD,EAAM,CAAC,EACfE,EAAOF,EAAM,CAAC,EACdG,EAAWF,EAAM,MAAML,CAAQ,EAC/BQ,EAAcH,EAAM,MAAMJ,CAAW,EACrCQ,EAAeH,EAAK,MAAMJ,CAAY,EAE5C,GAAIK,GAAYE,EACdX,EAAc,KAAK,CACjB,WAAYS,EAAS,CAAC,EACtB,OAAQC,IAAc,CAAC,EACvB,WAAYC,EAAa,CAAC,CAC5B,CAAC,UACQvB,EAAO,CAChB,IAAMwB,EAAU,CACd,CAACH,GAAY,MACb,CAACE,GAAgB,WACnB,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,EAC3B,QAAQ,MAAM,+BAA+BN,CAAY,aAAaO,CAAO,EAAE,CACjF,CACF,CAEA,OAAIxB,GACF,QAAQ,MAAM,SAASiB,CAAY,0BAA0BL,EAAc,MAAM,QAAQ,EAGpFA,CACT,CAOA,SAASa,GACPC,EACAC,EACS,CACT,IAAMC,EAAe,iEACfC,EAAiB,qEAEnBX,EAGJ,MAAQA,EAAQW,EAAe,KAAKH,CAAU,KAAO,MAAM,CACzD,IAAMI,EAAmBZ,EAAM,CAAC,EAC7B,KAAK,EACL,MAAM,KAAK,EACX,OAAO,OAAO,EAEjB,GACEY,EAAiB,SAAS,KAAa,GACvCA,EAAiB,SAASH,CAAK,EAE/B,MAAO,EAEX,CAGA,MAAQT,EAAQU,EAAa,KAAKF,CAAU,KAAO,MAAM,CACvD,IAAMK,EAAkBb,EAAM,CAAC,EAC5B,KAAK,EACL,MAAM,KAAK,EACX,OAAO,OAAO,EAEjB,GACEa,EAAgB,SAAS,KAAa,GACtCA,EAAgB,SAASJ,CAAK,EAE9B,MAAO,EAEX,CAEA,MAAO,EACT,CAEA,SAASK,EACPpB,EACAb,EACAC,EACqB,CACrB,IAAMiC,EAAS,IAAI,IAAIlC,CAAW,EAC5BmC,EAAOD,EAAO,KACdE,EAAOF,EAAO,SAEhBjC,GACF,QAAQ,MAAM,0BAA0BD,CAAW,UAAUmC,CAAI,UAAUC,CAAI,GAAG,EAGpF,IAAIC,EAAiC,KACjCC,EAAkB,GAEtB,QAAWC,KAAS1B,EAAe,CACjC,IAAI2B,EAGJ,GAFmBD,EAAM,WAAW,WAAW,GAAG,EAGhDC,EAAcD,EAAM,eACf,CACL,IAAIE,EACJ,GAAI,CACFA,EAAa,IAAI,IAAIF,EAAM,UAAU,CACvC,MAAQ,CACFtC,GACF,QAAQ,MAAM,4CAA4CsC,EAAM,UAAU,EAAE,EAE9E,QACF,CAEA,GAAIE,EAAW,OAASN,EAAM,CACxBlC,GACF,QAAQ,MAAM,0CAA0CwC,EAAW,IAAI,cAAcN,CAAI,GAAG,EAE9F,QACF,CAEAK,EAAcC,EAAW,QAC3B,CAGA,GAAID,IAAgBJ,EAClB,OAAInC,GACF,QAAQ,MAAM,sBAAsBsC,EAAM,UAAU,EAAE,EAEjDA,EAIT,IAAMG,EAAcC,EAAiBH,EAAaJ,CAAI,EAClDM,EAAcJ,IAChBA,EAAkBI,EAClBL,EAAYE,EAEhB,CAEA,OAAItC,GAEA,QAAQ,MADNoC,EACY,yBAAyBA,EAAU,UAAU,iBAAiBC,CAAe,IAE7E,uCAAuCtC,CAAW,EAF8B,EAM3FqC,CACT,CAYA,SAASO,GACPC,EACAC,EACAC,EACAC,EACqB,CACrB,IAAMC,EAAsBJ,EAAc,OACvCK,GAAU,CAACA,EAAM,QAAUC,GAAoBD,EAAM,WAAYH,CAAK,CACzE,EAEA,OAAOK,EAAwBH,EAAqBH,EAAaE,CAAK,CACxE,CAEA,eAAsBK,EAAmB,CACvC,SAAAC,EACA,aAAAC,EACA,YAAAT,EACA,MAAAC,EACA,MAAAC,CACF,EAA0D,CACxD,IAAMQ,EAAM,MAAMC,GAAgBX,EAAaE,CAAK,EAChDA,GACF,QAAQ,MAAM,wBAAwBQ,EAAI,MAAM,SAAS,EAE3D,IAAMX,EAAgBa,GAAqBF,EAAKR,CAAK,EAErD,GAAIH,EAAc,SAAW,EAC3B,MAAIG,GACF,QAAQ,MAAM,iEAAiE,EAE3E,IAAI,MACR,iEACF,EAGF,GAAID,GAC6BH,GAC7BC,EACAC,EACAC,EACAC,CACF,EAE4B,CACtBA,IACF,QAAQ,MAAM,oGAAoG,EAClH,QAAQ,MAAM,QAAUF,EAAc,YAAcC,CAAK,GAE3D,MACF,CAGF,IAAMY,EAAqBd,EAAc,OAAQK,GAAU,CAAC,CAACA,EAAM,MAAM,EACnEU,EAAiBR,EAAwBO,EAAoBb,EAAaE,CAAK,EACrF,GAAI,CAACY,EAAgB,CACnB,GAAIZ,EAAO,CACT,IAAMa,EAAWF,EAAmB,IAAIG,GAAKA,EAAE,UAAU,EAAE,KAAK,IAAI,EACpE,QAAQ,MAAM,8CAA8ChB,CAAW,yBAAyBe,CAAQ,EAAE,CAC5G,CACA,MAAM,IAAI,MACR,6DAA6Df,CAAW,EAC1E,CACF,CAEIE,IACF,QAAQ,MAAM,0CAA2CF,CAAW,EACpE,QAAQ,MAAM,qBAAsBc,EAAe,UAAU,GAK/D,IAAMG,EAAW,GAAGT,CAAQ,IAAIM,EAAe,MAAM,IAAIA,EAAe,UAAU,GAC5EI,EAASC,GAAeF,EAAUf,CAAK,EAC7C,GAAIgB,EAAQ,OAAOA,EAEnB,IAAME,EAAgBN,EAAe,OAAS,SAC1CZ,GACF,QAAQ,MAAM,iCAAiCkB,CAAa,EAAE,EAGhE,IAAMC,EAAU,IAAI,gBAAgB,CAClC,WAAY,qBACZ,QAASP,EAAe,WACxB,SAAUA,EAAe,UAC3B,CAAC,EAEKQ,EAA8B,CAClC,OAAQ,OACR,QAAS,CACP,eAAgB,oCAChB,OAAQ,mBACR,cAAe,SAAW,KAAK,GAAGd,CAAQ,IAAIC,CAAY,EAAE,EAC5D,aAAcc,CAChB,EACA,KAAMF,EAAQ,SAAS,CACzB,EAEMG,EAAQ,MAAMC,GAAqBL,EAAeE,EAAgBpB,CAAK,EAE7E,GAAI,CACF,GAAM,CAAE,UAAAwB,CAAU,EAAI,MAAMC,EAAc,EACpCC,EAASF,EAAUF,CAAK,EAC1BI,EAAO,KACTC,EAAkB,IAAIZ,EAAU,CAAE,MAAAO,EAAO,IAAKI,EAAO,GAAI,CAAC,CAE9D,MAAQ,CACF1B,GACF,QAAQ,MAAM,oDAAoD,CAEtE,CAEA,OAAOsB,CACT,CChgBA,IAAMM,EAAY,IAAI,IAChBC,GAAoB,GAAK,GAAK,GAAK,IAE5BC,EAAN,cAAmC,KAAM,CAC9C,YAAYC,EAAyB,CACnC,MAAM,mCAAmCA,CAAG,EAAE,EAC9C,KAAK,KAAO,sBACd,CACF,EAUA,SAASC,IAAkC,CACzC,IAAIC,EAAwB,CAAE,OAAQ,MAAO,QAAS,CAAE,aAAcC,CAAe,CAAE,EACvF,OAAI,WAAW,SACbD,EAAU,CAAE,GAAGA,EAAS,QAASE,CAAe,GAE3CF,CACT,CAEA,eAAeG,GAAkB,CAC/B,SAAAC,EACA,IAAAC,EACA,MAAAC,EACA,eAAAC,EACA,SAAAC,CACF,EAA4C,CAC1C,IAAMC,EAASd,EAAU,IAAIS,CAAQ,EACrC,GAAIK,GAAW,KAAK,IAAI,EAAIA,EAAO,SAAYb,GAC7C,OAAOa,EAAO,KAGhB,GAAI,CACF,IAAMC,EAAW,MAAM,MAAML,EAAKN,GAAkB,CAAC,EAErD,GAAI,CAACW,EAAS,GACZ,MAAM,IAAI,MAAM,GAAGH,CAAc,KAAKG,EAAS,MAAM,EAAE,EAGzD,IAAMC,EAAW,MAAMD,EAAS,KAAK,EACrC,OAAAf,EAAU,IAAIS,EAAU,CAAE,KAAMO,EAAU,SAAU,KAAK,IAAI,CAAE,CAAC,EACzDA,CACT,OAASC,EAAO,CACd,MAAIN,GACF,QAAQ,MAAME,EAAUI,CAAK,EAEzBA,CACR,CACF,CAEA,eAAsBC,EACpBC,EACAR,EACwB,CACxB,IAAMS,EAAU,GAAGD,CAAO,kCAC1B,OAAIR,GACF,QAAQ,MAAM,oCAAoCS,CAAO,EAAE,EAGtDZ,GAAkB,CACvB,SAAU,gBACV,IAAKY,EACL,MAAAT,EACA,eAAgB,gCAChB,SAAU,+BACZ,CAAC,CACH,CAEO,SAASU,GAAuB,CACrCrB,EAAU,MAAM,CAClB,CC9EA,eAAsBsB,EAAY,CAChC,OAAAC,EACA,QAAAC,EACA,UAAAC,EACA,WAAAC,EACA,UAAAC,EACA,MAAAC,EAAQ,EACV,EAOkB,CAChB,IAAMC,EAAwB,CAC5B,WAAYJ,EACZ,WAAYE,EACZ,WAAAD,CACF,EAEA,GAAI,CACF,IAAII,EAAwB,CAC1B,OAAQ,OACR,QAAS,CACP,cAAe,UAAUP,CAAM,GAC/B,eAAgB,mBAChB,aAAcQ,CAChB,EACA,KAAM,KAAK,UAAUF,CAAO,CAC9B,EACI,WAAW,SACbC,EAAU,CAAE,GAAGA,EAAS,QAASE,CAAe,GAElD,IAAMC,EAAW,MAAM,MAAM,GAAGT,CAAO,UAAWM,CAAO,EAErD,CAACG,EAAS,IAAML,GAClB,QAAQ,MAAM,2BAA2BK,EAAS,MAAM,EAAE,CAE9D,OAASC,EAAO,CACVN,GACF,QAAQ,MAAM,yBAA0BM,CAAK,CAEjD,CACF,CC/CA,IAAMC,GAAiB,IAAI,IAAI,CAE7B,gBACA,SACA,aACA,sBACA,YACA,uBAEA,aAEA,gBACF,CAAC,EAQM,SAASC,EAAkBC,EAAyD,CACzF,IAAMC,EAAiC,CAAC,EACxC,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQH,CAAO,EAAG,CAClD,IAAMI,EAAWF,EAAI,YAAY,EAC5BJ,GAAe,IAAIM,CAAQ,IAC9BH,EAAO,KAAKG,CAAQ,EAAE,EAAID,EAE9B,CACA,OAAOF,CACT,CCTA,IAAMI,EAAsBC,GAAkBA,EAAM,KAAK,EAAE,QAAQ,OAAQ,EAAE,EAE7E,SAASC,EAAyBC,EAA2C,CAC3E,OAAQA,EAAQ,CACd,4BACE,MAAO,4CACT,gCACE,MAAO,8BACT,4BACE,MAAO,gCACT,4CACE,MAAO,yCACT,6BACE,MAAO,wCACT,8BACE,MAAO,yCACT,6BACE,MAAO,6CACT,+BACE,MAAO,qDACT,mBACE,MAAO,yDACT,QACE,MAAO,uDACX,CACF,CASA,eAAsBC,EAAmB,CACvC,aAAAC,EACA,WAAAC,EACA,gBAAAC,EACA,MAAAC,CACF,EAAsE,CACpE,GAAM,CAAE,sBAAAC,CAAsB,EAAI,MAAMC,EAA0B,EAC5D,CAAE,UAAAC,CAAU,EAAI,MAAMC,EAAc,EACpC,CAAE,UAAAC,CAAU,EAAI,MAAMC,EAAc,EAE1C,GAAI,CAACT,EACH,MAAO,CACL,MAAO,GACP,+BACA,MAAOH,yBAAgE,CACzE,EAGF,IAAIa,EACJ,GAAI,CACFA,EAASN,EAAsBJ,CAAY,CAC7C,OAASW,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,8BAA+BQ,CAAK,EAE7C,CACL,MAAO,GACP,gCACA,MAAOd,0BAAiE,CAC1E,CACF,CAEA,GAAIa,EAAO,MAAQ,QACjB,OAAIP,GACF,QAAQ,MAAM,+BAAgCO,EAAO,GAAG,EAEnD,CACL,MAAO,GACP,mCACA,MAAOb,6BAA8D,CACvE,EAGF,IAAIe,EACJ,GAAI,CACFA,EAAUN,EAAUN,CAAY,CAClC,OAASW,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,+BAAgCQ,CAAK,EAE9C,CACL,MAAO,GACP,iCACA,MAAOd,2BAAkE,CAC3E,CACF,CAEA,IAAMgB,EAAgCD,EAAQ,WAExCE,EAA6BF,EAAQ,IACrCG,EAAmBD,EAASnB,EAAmBmB,CAAM,EAAI,OACzDE,EAAoBrB,EAAmBO,CAAe,EAE5D,GAAI,CAACa,GAAoB,CAACA,EAAiB,WAAWC,CAAiB,EACrE,OAAIb,GACF,QAAQ,MAAM,8CAA+CW,CAAM,EAE9D,CACL,MAAO,GACP,gCACA,MAAOjB,0BAAiE,EACxE,UAAAgB,CACF,EAGF,IAAMI,EAAiB,MAAM,QAAQL,EAAQ,GAAG,EAC5CA,EAAQ,IAAI,OAAQM,GAA2B,OAAOA,GAAU,QAAQ,EACxE,OAAON,EAAQ,KAAQ,SACvB,CAACA,EAAQ,GAAG,EACZ,CAAC,EAECO,EAAuBxB,EAAmBM,CAAU,EAO1D,GAAI,CANsBgB,EAAe,KAAMrB,GAAU,CACvD,IAAMwB,EAAqBzB,EAAmBC,CAAK,EACnD,OAAKwB,EACED,EAAqB,WAAWC,CAAkB,EADzB,EAElC,CAAC,EAGC,OAAIjB,GACF,QAAQ,MACN,mDACAS,EAAQ,GACV,EAEK,CACL,MAAO,GACP,kCACA,MAAOf,4BAAmE,EAC1E,UAAAgB,CACF,EAGF,IAAMQ,EAAS,SAAqD,CAClE,IAAIC,EACJ,GAAI,CACFA,EAAO,MAAMC,EAAkBrB,EAAiBC,CAAK,CACvD,OAASQ,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,iCAAkCQ,CAAK,EAEhD,CACL,MAAO,GACP,sBACA,MAAOd,gBAA+D,EACtE,UAAAgB,CACF,CACF,CAEA,GAAI,CASF,IAAMW,GAAS,MAAMhB,EAAUR,EARhB,MAAOyB,GAAmC,CACvD,IAAMC,EAAMJ,EAAK,KAAK,KAAMK,IAAQA,GAAI,MAAQF,EAAU,GAAG,EAC7D,GAAI,CAACC,EACH,MAAM,IAAIE,EAAqBH,EAAU,GAAG,EAE9C,OAAOC,CACT,EAEqD,CACnD,OAAAZ,EACA,WAAY,CAACJ,EAAO,GAAG,EACvB,eAAgB,IAClB,CAAC,EAED,MAAO,CACL,MAAO,GACP,UAAAG,EACA,QAASW,GAAO,OAClB,CACF,OAASb,EAAO,CAKd,GAJIR,GACF,QAAQ,MAAM,mCAAoCQ,CAAK,EAGrDA,aAAiBiB,EACnB,MAAMjB,EAGR,OAAIA,aAAiB,OAASA,EAAM,SAAS,SAAS,KAAK,EAClD,CACL,MAAO,GACP,+BACA,MAAOd,yBAA0D,EACjE,UAAAgB,CACF,EAGK,CACL,MAAO,GACP,+CACA,MAAOhB,yCAAgF,EACvF,UAAAgB,CACF,CACF,CACF,EAEA,GAAI,CACF,OAAO,MAAMQ,EAAO,CACtB,OAASV,EAAO,CACd,GAAIA,aAAiBiB,EACnB,OAAIzB,GACF,QAAQ,MAAM,8DAA8D,EAE9E0B,EAAe,EACR,MAAMR,EAAO,EAEtB,MAAMV,CACR,CACF,CAEO,SAASmB,GAAoB,CAClC,WAAA7B,CACF,EAEW,CACT,GAAI,CACF,IAAM8B,EAAU,IAAI,IAAI9B,CAAU,EAClC,MAAO,GAAG8B,EAAQ,QAAQ,KAAKA,EAAQ,IAAI,cAC7C,OAASC,EAAK,CACZ,eAAQ,MAAM,6DAA8DA,CAAG,EACxE,cACT,CACF,CAMO,SAASC,GAAkBhC,EAAmC,CACnE,IAAMiC,EAAcJ,GAAoB,CAAE,WAAA7B,CAAW,CAAC,EACtD,MAAO,CACL,eACA,QAAS,CACP,KAAM,IAAIiC,CAAW,+CACrB,eAAgB,iBAChB,eAAgB,SAClB,CACF,CACF,CAEA,SAASC,GAAiBrC,EAAkF,CAC1G,OAAQA,EAAQ,CACd,4BACA,gCACE,MAAO,CAAE,SAAU,kBAAmB,OAAQ,GAAI,EACpD,4BACA,4CACA,6BACA,8BACA,6BACE,MAAO,CAAE,SAAU,gBAAiB,OAAQ,GAAI,EAClD,+BACE,MAAO,CAAE,SAAU,qBAAsB,OAAQ,GAAI,EACvD,mBACE,MAAO,CAAE,SAAU,eAAgB,OAAQ,GAAI,EACjD,QACE,MAAO,CAAE,SAAU,gBAAiB,OAAQ,GAAI,CACpD,CACF,CAMA,SAASsC,GAAoBxC,EAAuB,CAClD,OAAOA,EACJ,QAAQ,UAAW,EAAE,EACrB,QAAQ,MAAO,MAAM,EACrB,QAAQ,KAAM,KAAK,CACxB,CAEO,SAASyC,EAAiB,CAC/B,OAAAvC,EACA,MAAAa,EACA,WAAAV,CACF,EAIkB,CAChB,GAAM,CAAE,SAAAqC,EAAU,OAAAC,CAAO,EAAIJ,GAAiBrC,CAAM,EAC9C0C,EAAmBJ,GAAoBzB,CAAK,EAC5CuB,EAAcJ,GAAoB,CAAE,WAAA7B,CAAW,CAAC,EAEtD,MAAO,CACL,eACA,OAAAsC,EACA,KAAM,kEAAkED,CAAQ,MAAM3B,CAAK,GAC3F,QAAS,CACP,eAAgB,4BAChB,mBAAoB,kBAAkB2B,CAAQ,yBAAyBE,CAAgB,IACvF,KAAM,IAAIN,CAAW,8CACvB,CACF,CACF,CAEA,SAASO,IAAkC,CACzC,IAAIC,EAAwB,CAAE,OAAQ,MAAO,QAAS,CAAE,aAAcC,CAAe,CAAE,EACvF,OAAI,WAAW,SACbD,EAAU,CAAE,GAAGA,EAAS,QAASE,CAAe,GAE3CF,CACT,CAEA,eAAsBG,GACpB3C,EACA4C,EACmB,CACnB,GAAI,CACF,IAAMC,EAAa,GAAG7C,CAAe,sBAAsB4C,CAAiB,eACtEE,EAAW,MAAM,MAAMD,EAAYN,GAAkB,CAAC,EAE5D,GAAI,CAACO,EAAS,GACZ,OAAO,IAAI,SAAS,oBAAqB,CAAE,OAAQ,GAAI,CAAC,EAG1D,IAAMC,EAAa,MAAMD,EAAS,KAAK,EAEvC,OAAO,IAAI,SAASC,EAAY,CAC9B,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,qBAAsB,CAAC,CAChE,CAAC,CACH,OAASjB,EAAK,CACZ,eAAQ,MAAM,6CAA8CA,CAAG,EACxD,IAAI,SAAS,cAAe,CAAE,OAAQ,GAAI,CAAC,CACpD,CACF,CAaA,eAAsBkB,EACpBC,EACyC,CACzC,IAAMC,EAAe,MAAMrD,EAAmB,CAC5C,aAAcoD,EAAO,MACrB,WAAYA,EAAO,IACnB,gBAAiBA,EAAO,gBACxB,MAAOA,EAAO,KAChB,CAAC,EAEKE,EAAeC,EAAY,CAC/B,OAAQH,EAAO,OACf,QAASA,EAAO,gBAChB,UAAWC,EAAa,MAAQ,eAAiBA,EAAa,OAC9D,WAAY,CACV,SAAUD,EAAO,IACjB,WAAYA,EAAO,UACnB,eAAgBR,EAChB,oBAAqBS,EAAa,MAAQ,QAAU,UACpD,oBAAqBA,EAAa,MAAQ,UAAYA,EAAa,OACnE,GAAGG,EAAkBJ,EAAO,gBAAkB,CAAC,CAAC,CAClD,EACA,UAAWC,EAAa,UACxB,MAAOD,EAAO,KAChB,CAAC,EACD,OAAIA,EAAO,KAAK,UACdA,EAAO,IAAI,UAAUE,CAAY,EAEjC,MAAMA,EAGDD,CACT,CC3XA,eAAsBI,GACpBC,EACAC,EACAC,EACmB,CACnB,IAAMC,EAAS,MAAMH,EAAQ,cAAcC,EAASC,CAAG,EAEvD,GAAIC,EAAO,SAAW,QACpB,OAAO,IAAI,SAASA,EAAO,KAAM,CAC/B,OAAQA,EAAO,OACf,QAAS,IAAI,QAAQA,EAAO,OAAO,CACrC,CAAC,EAIH,IAAMC,EAAiB,MAAM,MAAMH,CAAO,EAE1C,GAAIE,EAAO,QAAS,CAClB,IAAME,EAAW,IAAI,SAASD,EAAe,KAAMA,CAAc,EACjE,OAAW,CAACE,EAAKC,CAAK,IAAK,OAAO,QAAQJ,EAAO,OAAO,EACtDE,EAAS,QAAQ,IAAIC,EAAKC,CAAK,EAEjC,OAAOF,CACT,CAEA,OAAOD,CACT,CAWA,eAAsBI,GACpBR,EACAC,EACAQ,EACAC,EAImB,CACnB,IAAMC,EAAcV,EAAQ,QAAQ,IAAI,wBAAwB,GAAKA,EAAQ,IAE7E,GAAIS,GAAc,IAAI,IAAIC,CAAW,EAAE,WAAa,eAClD,OAAO,MAAMC,GACXF,EAAW,QACXA,EAAW,iBACb,EAGF,IAAMG,EAAa,IAAI,QAAQF,EAAa,CAC1C,OAAQV,EAAQ,OAChB,QAASA,EAAQ,OACnB,CAAC,EAEKE,EAAS,MAAMH,EAAQ,cAAca,CAAU,EAErD,GAAIV,EAAO,SAAW,QACpB,OAAO,IAAI,SAASA,EAAO,KAAM,CAC/B,OAAQA,EAAO,OACf,QAAS,IAAI,QAAQA,EAAO,OAAO,CACrC,CAAC,EAIH,IAAMC,EAAiB,MAAM,MAAMH,EAAS,CAC1C,QAASQ,CACX,CAAgB,EAEhB,GAAIN,EAAO,QAAS,CAClB,IAAME,EAAW,IAAI,SAASD,EAAe,KAAMA,CAAc,EACjE,OAAW,CAACE,EAAKC,CAAK,IAAK,OAAO,QAAQJ,EAAO,OAAO,EACtDE,EAAS,QAAQ,IAAIC,EAAKC,CAAK,EAEjC,OAAOF,CACT,CAEA,OAAOD,CACT,CAEA,SAASU,GAAkBC,EAAsC,CAC/D,OAAQA,EAAQ,CACd,IAAK,KAAK,qBACV,IAAK,KAAK,yBACV,IAAK,KAAK,kBACV,IAAK,KAAK,4BACV,QAAS,aACX,CACF,CAOA,eAAsBC,GACpBhB,EACAiB,EAC4C,CAC5C,IAAMC,EAAYD,EAAM,QAAQ,CAAC,EAAE,GAAG,QAGhCE,EAAmBD,EAAU,UAAU,wBAAwB,IAAI,CAAC,GAAG,MACvEE,EAAmB,GAAGF,EAAU,QAAQ,KAAK,CAAC,EAAE,KAAK,GAAGA,EAAU,GAAG,GACrEG,EAAM,WAAWF,GAAsCC,CAAgB,GAAGF,EAAU,YAAc,IAAMA,EAAU,YAAc,EAAE,GAElII,EAAU,IAAI,QACpB,OAAO,QAAQJ,EAAU,OAAO,EAAE,QAAQ,CAAC,CAACZ,EAAKiB,CAAM,IAAM,CAC3DA,EAAO,QAAQ,CAAC,CAAE,MAAAhB,CAAM,IAAMe,EAAQ,OAAOhB,EAAKC,CAAK,CAAC,CAC1D,CAAC,EAED,IAAMM,EAAa,IAAI,QAAQQ,EAAK,CAClC,OAAQH,EAAU,OAClB,QAASI,CACX,CAAC,EAEKnB,EAAS,MAAMH,EAAQ,cAAca,CAAU,EAErD,GAAIV,EAAO,SAAW,QAAqB,CACzC,IAAMqB,EAAqC,CAAC,EAC5C,cAAO,QAAQrB,EAAO,OAAO,EAAE,QAAQ,CAAC,CAACG,EAAKC,CAAK,IAAM,CACvDiB,EAAgBlB,EAAI,YAAY,CAAC,EAAI,CAAC,CAAE,IAAAA,EAAK,MAAAC,CAAM,CAAC,CACtD,CAAC,EAEM,CACL,OAAQJ,EAAO,OAAO,SAAS,EAC/B,kBAAmBW,GAAkBX,EAAO,MAAM,EAClD,QAASqB,EACT,KAAMrB,EAAO,IACf,CACF,CAGA,OAAOe,CACT,CCrJO,SAASO,GAAmBC,EAA2B,CAC5D,IAAMC,EAAYD,EAAQ,QAAQ,IAAI,YAAY,GAAK,GACjDE,EAASF,EAAQ,QAAQ,IAAI,QAAQ,GAAK,GAC1CG,EAAUH,EAAQ,QAAQ,IAAI,WAAW,EACzCI,EAAiBJ,EAAQ,QAAQ,IAAI,iBAAiB,EACtDK,EAAYL,EAAgB,IAAI,eAAe,MAE/CM,EAAU,CACd,eACA,gBACA,SACA,eACA,QACA,aACA,YACA,YACA,SACA,UACA,gBACA,qBACA,WACA,YACA,aACA,kBACA,YACA,MACA,OACA,MACF,EAEMC,EAAqBN,EAAU,YAAY,EAC3CO,EAAaF,EAAQ,KAAMG,GAAQF,EAAmB,SAASE,CAAG,CAAC,EAGnEC,EACJH,EAAmB,SAAS,UAAU,GACtCA,EAAmB,SAAS,WAAW,GACvC,CAACJ,EAEGQ,EACJ,CAACJ,EAAmB,SAAS,UAAU,GACvC,CAACA,EAAmB,SAAS,WAAW,GACxC,CAACJ,EAGGS,EAAiB,CAACV,GAAU,CAACE,EAG7BS,EAAc,OAAOR,GAAa,UAAYA,EAAW,GAG/D,OACEE,EAAmB,SAAS,QAAQ,GACpCA,EAAmB,SAAS,SAAS,IAGjCG,GAAsBC,EACjB,GAKJH,GAAcE,GAAsBE,GAAkBC,CAC/D,CXTO,IAAMC,EAAN,MAAMA,CAAgB,CAgBpB,YAAYC,EAA+BC,EAAiB,GAAO,CACxE,GAAI,CAACA,GAASF,EAAgB,UAAW,CAEvC,GAAIC,EAAO,SAAWD,EAAgB,UAAU,OAC9C,MAAM,IAAI,MACR,8GACF,EAIF,OAAOA,EAAgB,SACzB,CAMA,GALIE,GAASF,EAAgB,WAE3BA,EAAgB,cAAc,EAG5B,CAACC,EAAO,OACV,MAAM,IAAI,MACR,oDACF,EAEF,KAAK,OAASA,EAAO,OACrB,KAAK,YAAcA,EAAO,aAAe,OACzC,KAAK,YAAcA,EAAO,YAC1B,KAAK,MAAQA,EAAO,OAAS,GAG7BD,EAAgB,UAAY,IAC9B,CAKA,OAAc,eAAsB,CAClCA,EAAgB,UAAY,IAC9B,CAKA,OAAc,WAAWG,EAAmB,CAC1CH,EAAgB,QAAUG,CAC5B,CAKA,OAAc,YAAqB,CACjC,OAAOH,EAAgB,OACzB,CAUA,aAAa,OAAOI,EAKe,CACjC,IAAMC,EAAUD,EAAQ,SAAWJ,EAAgB,QAE7CM,EAAS,MAAMC,EAAyB,CAC5C,aAAcH,EAAQ,MACtB,WAAYA,EAAQ,YACpB,gBAAiBC,EACjB,MAAOD,EAAQ,OAAS,EAC1B,CAAC,EAED,OAAIE,EAAO,MACF,CAAE,MAAO,EAAK,EAGhB,CAAE,MAAO,GAAO,MAAOA,EAAO,KAAM,CAC7C,CAaA,MAAM,gBAAgBF,EAOa,CACjC,IAAME,EAAS,MAAME,EAAqB,CACxC,MAAOJ,EAAQ,MACf,IAAKA,EAAQ,YACb,UAAWA,EAAQ,WAAa,UAChC,gBAAiBJ,EAAgB,QACjC,MAAOI,EAAQ,OAAS,KAAK,MAC7B,OAAQ,KAAK,OACb,IAAKA,EAAQ,IACb,eAAgBA,EAAQ,cAC1B,CAAC,EAED,OAAIE,EAAO,MACF,CAAE,MAAO,EAAK,EAGhB,CAAE,MAAO,GAAO,MAAOA,EAAO,KAAM,CAC7C,CAUA,MAAM,cAAcG,EAAkBC,EAAgD,CACpF,IAAMC,EAAOF,EAAQ,QAAQ,IAAI,eAAe,GAAK,GAC/CG,EAAQD,EAAK,WAAW,UAAU,EAAIA,EAAK,MAAM,CAAC,EAAI,KACtDR,EAAMM,EAAQ,IACdI,EAAYJ,EAAQ,QAAQ,IAAI,YAAY,GAAK,UAGvD,GAAIG,EAAO,CACT,GAAI,KAAK,cAAgB,WACvB,MAAO,CAAE,cAA4B,EAEvC,IAAME,EAAe,MAAMN,EAAqB,CAC9C,MAAAI,EACA,IAAAT,EACA,UAAAU,EACA,gBAAiBb,EAAgB,QACjC,MAAO,KAAK,MACZ,OAAQ,KAAK,OACb,IAAAU,EACA,eAAgB,OAAO,YAAYD,EAAQ,QAAQ,QAAQ,CAAC,CAC9D,CAAC,EACD,OAAKK,EAAa,MAOX,CAAE,cAA4B,EAN5BC,EAAiB,CACtB,OAAQD,EAAa,OACrB,MAAOA,EAAa,MACpB,WAAYX,CACd,CAAC,CAGL,CAKA,GAAI,EAFU,KAAK,cAAcM,EAASC,CAAG,GAAK,IAGhD,MAAO,CAAE,cAA4B,EAIvC,OAAQ,KAAK,YAAa,CACxB,aACE,OAAOK,EAAiB,CACtB,+BACA,MAAO,4CACP,WAAYZ,CACd,CAAC,EACH,WACE,OAAOa,GAAkBb,CAAG,EAC9B,QACE,MAAO,CAAE,cAA4B,CACzC,CACF,CAaA,aAAa,mBAAmBC,EAMA,CAC9B,OAAOa,EAAyB,CAC9B,SAAUb,EAAQ,SAClB,aAAcA,EAAQ,aACtB,YAAaA,EAAQ,YACrB,MAAOA,EAAQ,MACf,MAAOA,EAAQ,KACjB,CAAC,CACH,CAYA,aAAa,yBACXK,EACAS,EACAR,EACAN,EAImB,CACnB,GAAI,CACF,IAAMe,EAAW,IAAInB,EAAgB,CACnC,OAAQkB,EAAI,iBACZ,YAAad,GAAS,YACtB,YAAaA,GAAS,WACxB,CAAC,EACD,OAAO,MAAMgB,GAAwBD,EAAUV,EAASC,CAAG,CAC7D,OAASW,EAAK,CACZ,eAAQ,MAAM,qDAAsDA,CAAG,EAChE,MAAM,MAAMZ,CAAO,CAC5B,CACF,CAaA,aAAa,qBACXA,EACAa,EACAC,EACAnB,EACmB,CACnB,GAAI,CACF,GAAM,CAAE,YAAAoB,EAAa,YAAAC,CAAY,EAAIrB,GAAW,CAAC,EAE3Ce,EAAW,IAAInB,EAAgB,CACnC,OAAQsB,EACR,YAAAE,EACA,YAAAC,CACF,CAAC,EAEGC,EACJ,OAAItB,GAAS,YACXsB,EAAa,CACX,QAAS1B,EAAgB,QACzB,kBAAmBI,EAAQ,iBAC7B,GAGK,MAAMuB,GACXR,EACAV,EACAc,EACAG,CACF,CACF,OAASL,EAAK,CACZ,eAAQ,MAAM,iDAAkDA,CAAG,EAC5D,MAAM,MAAMZ,EAAS,CAAE,QAASc,CAAc,CAAgB,CACvE,CACF,CAQA,aAAa,yBACXK,EACAxB,EAC4C,CAC5C,IAAMK,EAAUmB,GAAO,UAAU,CAAC,GAAG,IAAI,SAAuB,CAAC,EACjE,GAAI,CAEF,GAAI,CADwBnB,EAAQ,UAAU,gBAAgB,EAI5D,OAAOA,EAET,IAAMU,EAAW,IAAInB,EAAgB,CACnC,OAAQI,EAAQ,OAChB,YAAaA,EAAQ,WACvB,CAAC,EACD,OAAO,MAAMyB,GAAwBV,EAAUS,CAAK,CACtD,OAASP,EAAK,CACZ,eAAQ,MAAM,qDAAsDA,CAAG,EAChEZ,CACT,CACF,CACF,EAzUaT,EAEI,QAAkB,kCAFtBA,EAOI,UAAoC,KAP9C,IAAM8B,EAAN9B","names":["index_exports","__export","CDNStatusDescription","EnforcementMode","HandlerAction","LicenseTokenInvalidReason","SupertabConnect","UsageType","defaultBotDetector","__toCommonJS","EnforcementMode","LicenseTokenInvalidReason","FASTLY_BACKEND","HandlerAction","CDNStatusDescription","lazyImport","load","cached","loadJwtVerify","loadDecodeJwt","loadDecodeProtectedHeader","loadKeyImport","loadJwtSign","scorePathPattern","pattern","path","anchored","pat","hasWildcard","regexBody","regexStr","SDK_USER_AGENT","licenseTokenCache","LICENSE_XML_TTL_SECONDS","licenseXmlCache","evictExpiredLicenseXml","now","origin","entry","getCachedToken","cacheKey","debug","cached","UsageType","retrieveLicenseToken","tokenEndpoint","requestOptions","response","errorBody","errorMessage","data","parseError","error","fetchLicenseXml","resourceUrl","debug","origin","cached","licenseXmlCache","now","LICENSE_XML_TTL_SECONDS","licenseXmlUrl","response","SDK_USER_AGENT","xml","evictExpiredLicenseXml","parseContentElements","contentBlocks","contentRegex","urlRegex","serverRegex","licenseRegex","elementCount","match","attrs","body","urlMatch","serverMatch","licenseMatch","missing","licensePermitsUsage","licenseXml","usage","permitsRegex","prohibitsRegex","prohibitedUsages","permittedUsages","findBestMatchingContent","parsed","host","path","bestMatch","bestSpecificity","block","patternPath","patternUrl","specificity","scorePathPattern","findServerlessUsageContent","contentBlocks","resourceUrl","usage","debug","matchingUsageBlocks","block","licensePermitsUsage","findBestMatchingContent","obtainLicenseToken","clientId","clientSecret","xml","fetchLicenseXml","parseContentElements","tokenContentBlocks","matchedContent","patterns","b","cacheKey","cached","getCachedToken","tokenEndpoint","payload","requestOptions","SDK_USER_AGENT","token","retrieveLicenseToken","decodeJwt","loadDecodeJwt","claims","licenseTokenCache","jwksCache","JWKS_CACHE_TTL_MS","JwksKeyNotFoundError","kid","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","cached","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","clearJwksCache","recordEvent","apiKey","baseUrl","eventName","properties","licenseId","debug","payload","options","SDK_USER_AGENT","FASTLY_BACKEND","response","error","DENIED_HEADERS","toEventProperties","headers","result","key","value","lowerKey","stripTrailingSlash","value","reasonToErrorDescription","reason","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","decodeProtectedHeader","loadDecodeProtectedHeader","decodeJwt","loadDecodeJwt","jwtVerify","loadJwtVerify","header","error","payload","licenseId","issuer","normalizedIssuer","normalizedBaseUrl","audienceValues","entry","requestUrlNormalized","normalizedAudience","verify","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","JwksKeyNotFoundError","clearJwksCache","generateLicenseLink","baseURL","err","buildSignalResult","licenseLink","reasonToRslError","sanitizeHeaderValue","buildBlockResult","rslError","status","errorDescription","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","hostRSLicenseXML","merchantSystemUrn","licenseUrl","response","licenseXml","verifyAndRecordEvent","params","verification","eventPromise","recordEvent","toEventProperties","handleCloudflareRequest","handler","request","ctx","result","originResponse","response","key","value","handleFastlyRequest","originBackend","rslOptions","originalUrl","hostRSLicenseXML","webRequest","statusDescription","status","handleCloudfrontRequest","event","cfRequest","viewerRequestUrl","originRequestUrl","url","headers","values","responseHeaders","defaultBotDetector","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","isBrowserMissingSecChUa","missingHeaders","lowBotScore","_SupertabConnect","config","reset","url","options","baseUrl","result","verifyLicenseToken","verifyAndRecordEvent","request","ctx","auth","token","userAgent","verification","buildBlockResult","buildSignalResult","obtainLicenseToken","env","instance","handleCloudflareRequest","err","merchantApiKey","originBackend","botDetector","enforcement","rslOptions","handleFastlyRequest","event","handleCloudfrontRequest","SupertabConnect"]}
package/dist/index.d.cts CHANGED
@@ -111,6 +111,15 @@ interface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {
111
111
  }
112
112
  type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;
113
113
 
114
+ declare enum UsageType {
115
+ ALL = "all",
116
+ SEARCH = "search",
117
+ AI_ALL = "ai-all",
118
+ AI_TRAIN = "ai-train",
119
+ AI_INDEX = "ai-index",
120
+ AI_INPUT = "ai-input"
121
+ }
122
+
114
123
  /**
115
124
  * Default bot detection logic using multiple signals.
116
125
  * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.
@@ -195,18 +204,22 @@ declare class SupertabConnect {
195
204
  handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;
196
205
  /**
197
206
  * Request a license token from the Supertab Connect token endpoint.
207
+ * If usage type is specified and matching serverless content permits it, skips token request and returns undefined.
198
208
  * @param options.clientId OAuth client identifier.
199
209
  * @param options.clientSecret OAuth client secret for client_credentials flow.
200
210
  * @param options.resourceUrl Resource URL attempting to access with a License.
211
+ * @param options.usage Optional usage type.
212
+ * If specified and a matching serverless content exists in license, no token is issued
201
213
  * @param options.debug Enable debug logging (default: false).
202
- * @returns Promise resolving to the issued license access token string.
214
+ * @returns Promise resolving to the issued license access token string, or `undefined` when no token is needed.
203
215
  */
204
216
  static obtainLicenseToken(options: {
205
217
  clientId: string;
206
218
  clientSecret: string;
207
219
  resourceUrl: string;
220
+ usage?: UsageType;
208
221
  debug?: boolean;
209
- }): Promise<string>;
222
+ }): Promise<string | undefined>;
210
223
  /**
211
224
  * Handle incoming requests for Cloudflare Workers.
212
225
  * Pass this directly as your Worker's fetch handler.
@@ -242,4 +255,4 @@ declare class SupertabConnect {
242
255
  static cloudfrontHandleRequests<TRequest extends Record<string, any>>(event: CloudFrontRequestEvent<TRequest>, options: CloudfrontHandlerOptions): Promise<CloudFrontRequestResult<TRequest>>;
243
256
  }
244
257
 
245
- export { type BotDetector, CDNStatusDescription, type CloudFrontRequestEvent, type CloudFrontRequestResult, type CloudfrontHandlerOptions, EnforcementMode, type Env, type ExecutionContext, type FastlyHandlerOptions, HandlerAction, type HandlerResult, LicenseTokenInvalidReason, type RSLVerificationResult, SupertabConnect, type SupertabConnectConfig, defaultBotDetector };
258
+ export { type BotDetector, CDNStatusDescription, type CloudFrontRequestEvent, type CloudFrontRequestResult, type CloudfrontHandlerOptions, EnforcementMode, type Env, type ExecutionContext, type FastlyHandlerOptions, HandlerAction, type HandlerResult, LicenseTokenInvalidReason, type RSLVerificationResult, SupertabConnect, type SupertabConnectConfig, UsageType, defaultBotDetector };
package/dist/index.d.ts CHANGED
@@ -111,6 +111,15 @@ interface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {
111
111
  }
112
112
  type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;
113
113
 
114
+ declare enum UsageType {
115
+ ALL = "all",
116
+ SEARCH = "search",
117
+ AI_ALL = "ai-all",
118
+ AI_TRAIN = "ai-train",
119
+ AI_INDEX = "ai-index",
120
+ AI_INPUT = "ai-input"
121
+ }
122
+
114
123
  /**
115
124
  * Default bot detection logic using multiple signals.
116
125
  * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.
@@ -195,18 +204,22 @@ declare class SupertabConnect {
195
204
  handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;
196
205
  /**
197
206
  * Request a license token from the Supertab Connect token endpoint.
207
+ * If usage type is specified and matching serverless content permits it, skips token request and returns undefined.
198
208
  * @param options.clientId OAuth client identifier.
199
209
  * @param options.clientSecret OAuth client secret for client_credentials flow.
200
210
  * @param options.resourceUrl Resource URL attempting to access with a License.
211
+ * @param options.usage Optional usage type.
212
+ * If specified and a matching serverless content exists in license, no token is issued
201
213
  * @param options.debug Enable debug logging (default: false).
202
- * @returns Promise resolving to the issued license access token string.
214
+ * @returns Promise resolving to the issued license access token string, or `undefined` when no token is needed.
203
215
  */
204
216
  static obtainLicenseToken(options: {
205
217
  clientId: string;
206
218
  clientSecret: string;
207
219
  resourceUrl: string;
220
+ usage?: UsageType;
208
221
  debug?: boolean;
209
- }): Promise<string>;
222
+ }): Promise<string | undefined>;
210
223
  /**
211
224
  * Handle incoming requests for Cloudflare Workers.
212
225
  * Pass this directly as your Worker's fetch handler.
@@ -242,4 +255,4 @@ declare class SupertabConnect {
242
255
  static cloudfrontHandleRequests<TRequest extends Record<string, any>>(event: CloudFrontRequestEvent<TRequest>, options: CloudfrontHandlerOptions): Promise<CloudFrontRequestResult<TRequest>>;
243
256
  }
244
257
 
245
- export { type BotDetector, CDNStatusDescription, type CloudFrontRequestEvent, type CloudFrontRequestResult, type CloudfrontHandlerOptions, EnforcementMode, type Env, type ExecutionContext, type FastlyHandlerOptions, HandlerAction, type HandlerResult, LicenseTokenInvalidReason, type RSLVerificationResult, SupertabConnect, type SupertabConnectConfig, defaultBotDetector };
258
+ export { type BotDetector, CDNStatusDescription, type CloudFrontRequestEvent, type CloudFrontRequestResult, type CloudfrontHandlerOptions, EnforcementMode, type Env, type ExecutionContext, type FastlyHandlerOptions, HandlerAction, type HandlerResult, LicenseTokenInvalidReason, type RSLVerificationResult, SupertabConnect, type SupertabConnectConfig, UsageType, defaultBotDetector };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var F=(t=>(t.DISABLED="disabled",t.SOFT="soft",t.STRICT="strict",t))(F||{}),I=(l=>(l.MISSING_TOKEN="missing_license_token",l.INVALID_HEADER="invalid_license_header",l.INVALID_ALG="invalid_license_algorithm",l.INVALID_PAYLOAD="invalid_license_payload",l.INVALID_ISSUER="invalid_license_issuer",l.SIGNATURE_VERIFICATION_FAILED="license_signature_verification_failed",l.EXPIRED="license_token_expired",l.INVALID_AUDIENCE="invalid_license_audience",l.SERVER_ERROR="server_error",l))(I||{}),E="stc-backend",L=(r=>(r.ALLOW="allow",r.BLOCK="block",r))(L||{}),_=(s=>(s.Unauthorized="Unauthorized",s.PaymentRequired="Payment Required",s.Forbidden="Forbidden",s.ServiceUnavailable="Service Unavailable",s.Error="Error",s))(_||{});function x(n){let e=null;return()=>(e||(e=n()),e)}var H=x(()=>import("jose/jwt/verify")),A=x(()=>import("jose/jwt/decode")),$=x(()=>import("jose/decode/protected_header")),re=x(()=>import("jose/key/import")),ne=x(()=>import("jose/jwt/sign"));function K(n,e){let r=!1,t=n;t.endsWith("$")&&(r=!0,t=t.slice(0,-1));let o=t.includes("*"),i=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*"),a;return r?a=`^${i}$`:o?a=`^${i}`:t==="/"?a="^/":a=`^${i}(/|$)`,new RegExp(a).test(e)?t.replace(/\*/g,"").length:-1}var R="supertab-connect-sdk-typescript/1.4.1";var v=new Map,T=15*60,S=new Map;function se(){let n=Math.floor(Date.now()/1e3);for(let[e,r]of S)n-r.fetchedAt>=T&&S.delete(e)}function oe(n,e){let r=v.get(n);if(!r)return null;let t=Math.floor(Date.now()/1e3);return r.exp>t+30?(e&&console.debug(`Using cached license token (expires in ${r.exp-t}s)`),r.token):(e&&console.debug("Cached license token expired or expiring soon, refreshing"),v.delete(n),null)}async function ie(n,e,r){try{let t=await fetch(n,e);if(!t.ok){let s=await t.text().catch(()=>""),i=`Failed to obtain license token: ${t.status} ${t.statusText}${s?` - ${s}`:""}`;throw new Error(i)}let o;try{o=await t.json()}catch(s){throw r&&console.error("Failed to parse license token response as JSON:",s),new Error("Failed to parse license token response as JSON")}if(!o?.access_token)throw new Error("License token response missing access_token");return o.access_token}catch(t){throw r&&console.error("Error generating license token:",t),t}}async function ae(n,e){let r=new URL(n).origin,t=S.get(r);if(t){let a=Math.floor(Date.now()/1e3);if(a-t.fetchedAt<T)return e&&console.debug(`Using cached license.xml for origin ${r} (expires in ${T-(a-t.fetchedAt)}s)`),t.xml;e&&console.debug(`Cached license.xml for origin ${r} expired, re-fetching`),S.delete(r)}let o=`${r}/license.xml`,s=await fetch(o,{headers:{"User-Agent":R}});if(!s.ok)throw e&&console.error(`Failed to fetch license.xml from ${o}: ${s.status}`),new Error(`Failed to fetch license.xml from ${o}: ${s.status}`);let i=await s.text();return e&&console.debug("Fetched license.xml from",o),se(),S.set(r,{xml:i,fetchedAt:Math.floor(Date.now()/1e3)}),i}function ce(n,e){let r=[],t=/<content\s([^>]*)>([\s\S]*?)<\/content>/gi,o=/url\s*=\s*"([^"]*)"/i,s=/server\s*=\s*"([^"]*)"/i,i=/<license[^>]*>[\s\S]*?<\/license>/i,a=0,c;for(;(c=t.exec(n))!==null;){a++;let l=c[1],d=c[2],f=l.match(o),p=l.match(s),g=d.match(i);if(f&&p&&g)r.push({urlPattern:f[1],server:p[1],licenseXml:g[0]});else if(e){let b=[!f&&"url",!p&&"server",!g&&"<license>"].filter(Boolean).join(", ");console.debug(`Skipping <content> element #${a}: missing ${b}`)}}return e&&console.debug(`Found ${a} <content> element(s), ${r.length} valid`),r}function le(n,e,r){let t=new URL(e),o=t.host,s=t.pathname;r&&console.debug(`Matching resource URL: ${e} (host=${o}, path=${s})`);let i=null,a=-1;for(let c of n){let l;if(c.urlPattern.startsWith("/"))l=c.urlPattern;else{let p;try{p=new URL(c.urlPattern)}catch{r&&console.debug(`Skipping block with invalid URL pattern: ${c.urlPattern}`);continue}if(p.host!==o){r&&console.debug(`Skipping block: host mismatch (pattern=${p.host}, resource=${o})`);continue}l=p.pathname}if(l===s)return r&&console.debug(`Exact match found: ${c.urlPattern}`),c;let f=K(l,s);f>a&&(a=f,i=c)}return r&&console.debug(i?`Wildcard match found: ${i.urlPattern} (specificity=${a})`:`No matching content block found for ${e}`),i}async function V({clientId:n,clientSecret:e,resourceUrl:r,debug:t}){let o=await ae(r,t);t&&console.debug(`Fetched license.xml (${o.length} chars)`);let s=ce(o,t);if(s.length===0)throw t&&console.error("No valid <content> elements with <license> found in license.xml"),new Error("No valid <content> elements with <license> found in license.xml");let i=le(s,r,t);if(!i){if(t){let g=s.map(b=>b.urlPattern).join(", ");console.error(`No <content> element matches resource URL: ${r}. Available patterns: ${g}`)}throw new Error(`No <content> element in license.xml matches resource URL: ${r}`)}t&&(console.debug("Matched content block for resource URL:",r),console.debug("Using license XML:",i.licenseXml));let a=`${n}:${i.server}:${i.urlPattern}`,c=oe(a,t);if(c)return c;let l=i.server+"/token";t&&console.debug(`Requesting license token from ${l}`);let d=new URLSearchParams({grant_type:"client_credentials",license:i.licenseXml,resource:i.urlPattern}),f={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${n}:${e}`),"User-Agent":R},body:d.toString()},p=await ie(l,f,t);try{let{decodeJwt:g}=await A(),b=g(p);b.exp&&v.set(a,{token:p,exp:b.exp})}catch{t&&console.debug("Failed to decode token for caching, skipping cache")}return p}var C=new Map,ue=48*60*60*1e3,w=class extends Error{constructor(e){super(`No matching platform key found: ${e}`),this.name="JwksKeyNotFoundError"}};function de(){let n={method:"GET",headers:{"User-Agent":R}};return globalThis.fastly&&(n={...n,backend:E}),n}async function fe({cacheKey:n,url:e,debug:r,failureMessage:t,logLabel:o}){let s=C.get(n);if(s&&Date.now()-s.cachedAt<ue)return s.data;try{let i=await fetch(e,de());if(!i.ok)throw new Error(`${t}: ${i.status}`);let a=await i.json();return C.set(n,{data:a,cachedAt:Date.now()}),a}catch(i){throw r&&console.error(o,i),i}}async function B(n,e){let r=`${n}/.well-known/jwks.json/platform`;return e&&console.debug(`Fetching platform JWKS from URL: ${r}`),fe({cacheKey:"platform_jwks",url:r,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}function J(){C.clear()}async function W({apiKey:n,baseUrl:e,eventName:r,properties:t,licenseId:o,debug:s=!1}){let i={event_name:r,license_id:o,properties:t};try{let a={method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":R},body:JSON.stringify(i)};globalThis.fastly&&(a={...a,backend:E});let c=await fetch(`${e}/events`,a);!c.ok&&s&&console.error(`Failed to record event: ${c.status}`)}catch(a){s&&console.error("Error recording event:",a)}}var pe=new Set(["authorization","cookie","set-cookie","proxy-authorization","x-api-key","x-amz-security-token","user-agent","x-license-auth"]);function M(n){let e={};for(let[r,t]of Object.entries(n)){let o=r.toLowerCase();pe.has(o)||(e[`h_${o}`]=t)}return e}var k=n=>n.trim().replace(/\/+$/,"");function y(n){switch(n){case"missing_license_token":return"Authorization header missing or malformed";case"invalid_license_algorithm":return"Unsupported token algorithm";case"license_token_expired":return"The license token has expired";case"license_signature_verification_failed":return"The license token signature is invalid";case"invalid_license_header":return"The license token header is malformed";case"invalid_license_payload":return"The license token payload is malformed";case"invalid_license_issuer":return"The license token issuer is not recognized";case"invalid_license_audience":return"The license does not grant access to this resource";case"server_error":return"The server encountered an error validating the license";default:return"License token missing, expired, revoked, or malformed"}}async function U({licenseToken:n,requestUrl:e,supertabBaseUrl:r,debug:t}){let{decodeProtectedHeader:o}=await $(),{decodeJwt:s}=await A(),{jwtVerify:i}=await H();if(!n)return{valid:!1,reason:"missing_license_token",error:y("missing_license_token")};let a;try{a=o(n)}catch(h){return t&&console.error("Invalid license JWT header:",h),{valid:!1,reason:"invalid_license_header",error:y("invalid_license_header")}}if(a.alg!=="ES256")return t&&console.error("Unsupported license JWT alg:",a.alg),{valid:!1,reason:"invalid_license_algorithm",error:y("invalid_license_algorithm")};let c;try{c=s(n)}catch(h){return t&&console.error("Invalid license JWT payload:",h),{valid:!1,reason:"invalid_license_payload",error:y("invalid_license_payload")}}let l=c.license_id,d=c.iss,f=d?k(d):void 0,p=k(r);if(!f||!f.startsWith(p))return t&&console.error("License JWT issuer is missing or malformed:",d),{valid:!1,reason:"invalid_license_issuer",error:y("invalid_license_issuer"),licenseId:l};let g=Array.isArray(c.aud)?c.aud.filter(h=>typeof h=="string"):typeof c.aud=="string"?[c.aud]:[],b=k(e);if(!g.some(h=>{let m=k(h);return m?b.startsWith(m):!1}))return t&&console.error("License JWT audience does not match request URL:",c.aud),{valid:!1,reason:"invalid_license_audience",error:y("invalid_license_audience"),licenseId:l};let q=async()=>{let h;try{h=await B(r,t)}catch(m){return t&&console.error("Failed to fetch platform JWKS:",m),{valid:!1,reason:"server_error",error:y("server_error"),licenseId:l}}try{let ee=await i(n,async N=>{let O=h.keys.find(te=>te.kid===N.kid);if(!O)throw new w(N.kid);return O},{issuer:d,algorithms:[a.alg],clockTolerance:"1m"});return{valid:!0,licenseId:l,payload:ee.payload}}catch(m){if(t&&console.error("License JWT verification failed:",m),m instanceof w)throw m;return m instanceof Error&&m.message?.includes("exp")?{valid:!1,reason:"license_token_expired",error:y("license_token_expired"),licenseId:l}:{valid:!1,reason:"license_signature_verification_failed",error:y("license_signature_verification_failed"),licenseId:l}}};try{return await q()}catch(h){if(h instanceof w)return t&&console.debug("Key not found in cached JWKS, clearing cache and retrying..."),J(),await q();throw h}}function j({requestUrl:n}){try{let e=new URL(n);return`${e.protocol}//${e.host}/license.xml`}catch(e){return console.error("[SupertabConnect] generateLicenseLink failed to parse URL:",e),"/license.xml"}}function G(n){let e=j({requestUrl:n});return{action:"allow",headers:{Link:`<${e}>; rel="license"; type="application/rsl+xml"`,"X-RSL-Status":"token_required","X-RSL-Reason":"missing"}}}function he(n){switch(n){case"missing_license_token":case"invalid_license_algorithm":return{rslError:"invalid_request",status:401};case"license_token_expired":case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":case"invalid_license_issuer":return{rslError:"invalid_token",status:401};case"invalid_license_audience":return{rslError:"insufficient_scope",status:403};case"server_error":return{rslError:"server_error",status:503};default:return{rslError:"invalid_token",status:401}}}function ge(n){return n.replace(/[\r\n]/g,"").replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function D({reason:n,error:e,requestUrl:r}){let{rslError:t,status:o}=he(n),s=ge(e),i=j({requestUrl:r});return{action:"block",status:o,body:`Access to this resource requires a valid license token. Error: ${t} - ${e}`,headers:{"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${t}", error_description="${s}"`,Link:`<${i}>; rel="license"; type="application/rsl+xml"`}}}function me(){let n={method:"GET",headers:{"User-Agent":R}};return globalThis.fastly&&(n={...n,backend:E}),n}async function X(n,e){try{let r=`${n}/merchants/systems/${e}/license.xml`,t=await fetch(r,me());if(!t.ok)return new Response("License not found",{status:404});let o=await t.text();return new Response(o,{status:200,headers:new Headers({"Content-Type":"application/rsl+xml"})})}catch(r){return console.error("[SupertabConnect] hostRSLicenseXML failed:",r),new Response("Bad Gateway",{status:502})}}async function P(n){let e=await U({licenseToken:n.token,requestUrl:n.url,supertabBaseUrl:n.supertabBaseUrl,debug:n.debug}),r=W({apiKey:n.apiKey,baseUrl:n.supertabBaseUrl,eventName:e.valid?"license_used":e.reason,properties:{page_url:n.url,user_agent:n.userAgent,sdk_user_agent:R,verification_status:e.valid?"valid":"invalid",verification_reason:e.valid?"success":e.reason,...M(n.requestHeaders??{})},licenseId:e.licenseId,debug:n.debug});return n.ctx?.waitUntil?n.ctx.waitUntil(r):await r,e}async function z(n,e,r){let t=await n.handleRequest(e,r);if(t.action==="block")return new Response(t.body,{status:t.status,headers:new Headers(t.headers)});let o=await fetch(e);if(t.headers){let s=new Response(o.body,o);for(let[i,a]of Object.entries(t.headers))s.headers.set(i,a);return s}return o}async function Y(n,e,r,t){let o=e.headers.get("x-original-request-url")||e.url;if(t&&new URL(o).pathname==="/license.xml")return await X(t.baseUrl,t.merchantSystemUrn);let s=new Request(o,{method:e.method,headers:e.headers}),i=await n.handleRequest(s);if(i.action==="block")return new Response(i.body,{status:i.status,headers:new Headers(i.headers)});let a=await fetch(e,{backend:r});if(i.headers){let c=new Response(a.body,a);for(let[l,d]of Object.entries(i.headers))c.headers.set(l,d);return c}return a}function Re(n){switch(n){case 401:return"Unauthorized";case 402:return"Payment Required";case 403:return"Forbidden";case 503:return"Service Unavailable";default:return"Error"}}async function Q(n,e){let r=e.Records[0].cf.request,t=r.headers?.["x-original-request-url"]?.[0]?.value,o=`${r.headers.host[0].value}${r.uri}`,s=`https://${t||o}${r.querystring?"?"+r.querystring:""}`,i=new Headers;Object.entries(r.headers).forEach(([l,d])=>{d.forEach(({value:f})=>i.append(l,f))});let a=new Request(s,{method:r.method,headers:i}),c=await n.handleRequest(a);if(c.action==="block"){let l={};return Object.entries(c.headers).forEach(([d,f])=>{l[d.toLowerCase()]=[{key:d,value:f}]}),{status:c.status.toString(),statusDescription:Re(c.status),headers:l,body:c.body}}return r}function ye(n){let e=n.headers.get("User-Agent")||"",r=n.headers.get("accept")||"",t=n.headers.get("sec-ch-ua"),o=n.headers.get("accept-language"),s=n.cf?.botManagement?.score,i=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],a=e.toLowerCase(),c=i.some(g=>a.includes(g)),l=a.includes("headless")||a.includes("puppeteer")||!t,d=!a.includes("headless")&&!a.includes("puppeteer")&&!t,f=!r||!o,p=typeof s=="number"&&s<30;return(a.includes("safari")||a.includes("mozilla"))&&l&&d?!1:c||l||f||p}var u=class u{constructor(e,r=!1){if(!r&&u._instance){if(e.apiKey!==u._instance.apiKey)throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(r&&u._instance&&u.resetInstance(),!e.apiKey)throw new Error("Missing required configuration: apiKey is required");this.apiKey=e.apiKey,this.enforcement=e.enforcement??"soft",this.botDetector=e.botDetector,this.debug=e.debug??!1,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}static getBaseUrl(){return u.baseUrl}static async verify(e){let r=e.baseUrl??u.baseUrl,t=await U({licenseToken:e.token,requestUrl:e.resourceUrl,supertabBaseUrl:r,debug:e.debug??!1});return t.valid?{valid:!0}:{valid:!1,error:t.error}}async verifyAndRecord(e){let r=await P({token:e.token,url:e.resourceUrl,userAgent:e.userAgent??"unknown",supertabBaseUrl:u.baseUrl,debug:e.debug??this.debug,apiKey:this.apiKey,ctx:e.ctx,requestHeaders:e.requestHeaders});return r.valid?{valid:!0}:{valid:!1,error:r.error}}async handleRequest(e,r){let t=e.headers.get("Authorization")||"",o=t.startsWith("License ")?t.slice(8):null,s=e.url,i=e.headers.get("User-Agent")||"unknown";if(o){if(this.enforcement==="disabled")return{action:"allow"};let c=await P({token:o,url:s,userAgent:i,supertabBaseUrl:u.baseUrl,debug:this.debug,apiKey:this.apiKey,ctx:r,requestHeaders:Object.fromEntries(e.headers.entries())});return c.valid?{action:"allow"}:D({reason:c.reason,error:c.error,requestUrl:s})}if(!(this.botDetector?.(e,r)??!1))return{action:"allow"};switch(this.enforcement){case"strict":return D({reason:"missing_license_token",error:"Authorization header missing or malformed",requestUrl:s});case"soft":return G(s);default:return{action:"allow"}}}static async obtainLicenseToken(e){return V({clientId:e.clientId,clientSecret:e.clientSecret,resourceUrl:e.resourceUrl,debug:e.debug})}static async cloudflareHandleRequests(e,r,t,o){try{let s=new u({apiKey:r.MERCHANT_API_KEY,botDetector:o?.botDetector,enforcement:o?.enforcement});return await z(s,e,t)}catch(s){return console.error("[SupertabConnect] cloudflareHandleRequests failed:",s),await fetch(e)}}static async fastlyHandleRequests(e,r,t,o){try{let{botDetector:s,enforcement:i}=o??{},a=new u({apiKey:r,botDetector:s,enforcement:i}),c;return o?.enableRSL&&(c={baseUrl:u.baseUrl,merchantSystemUrn:o.merchantSystemUrn}),await Y(a,e,t,c)}catch(s){return console.error("[SupertabConnect] fastlyHandleRequests failed:",s),await fetch(e,{backend:t})}}static async cloudfrontHandleRequests(e,r){let t=e?.Records?.[0]?.cf?.request??{};try{if(!t.headers?.["x-license-auth"])return t;let s=new u({apiKey:r.apiKey,enforcement:r.enforcement});return await Q(s,e)}catch(o){return console.error("[SupertabConnect] cloudfrontHandleRequests failed:",o),t}}};u.baseUrl="https://api-connect.supertab.co",u._instance=null;var Z=u;export{_ as CDNStatusDescription,F as EnforcementMode,L as HandlerAction,I as LicenseTokenInvalidReason,Z as SupertabConnect,ye as defaultBotDetector};
1
+ var H=(r=>(r.DISABLED="disabled",r.SOFT="soft",r.STRICT="strict",r))(H||{}),_=(l=>(l.MISSING_TOKEN="missing_license_token",l.INVALID_HEADER="invalid_license_header",l.INVALID_ALG="invalid_license_algorithm",l.INVALID_PAYLOAD="invalid_license_payload",l.INVALID_ISSUER="invalid_license_issuer",l.SIGNATURE_VERIFICATION_FAILED="license_signature_verification_failed",l.EXPIRED="license_token_expired",l.INVALID_AUDIENCE="invalid_license_audience",l.SERVER_ERROR="server_error",l))(_||{}),L="stc-backend",k=(t=>(t.ALLOW="allow",t.BLOCK="block",t))(k||{}),U=(o=>(o.Unauthorized="Unauthorized",o.PaymentRequired="Payment Required",o.Forbidden="Forbidden",o.ServiceUnavailable="Service Unavailable",o.Error="Error",o))(U||{});function A(n){let e=null;return()=>(e||(e=n()),e)}var $=A(()=>import("jose/jwt/verify")),I=A(()=>import("jose/jwt/decode")),K=A(()=>import("jose/decode/protected_header")),oe=A(()=>import("jose/key/import")),ie=A(()=>import("jose/jwt/sign"));function B(n,e){let t=!1,r=n;r.endsWith("$")&&(t=!0,r=r.slice(0,-1));let s=r.includes("*"),i=r.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*"),a;return t?a=`^${i}$`:s?a=`^${i}`:r==="/"?a="^/":a=`^${i}(/|$)`,new RegExp(a).test(e)?r.replace(/\*/g,"").length:-1}var m="supertab-connect-sdk-typescript/1.5.0";var C=new Map,T=15*60,S=new Map;function ae(){let n=Math.floor(Date.now()/1e3);for(let[e,t]of S)n-t.fetchedAt>=T&&S.delete(e)}function ce(n,e){let t=C.get(n);if(!t)return null;let r=Math.floor(Date.now()/1e3);return t.exp>r+30?(e&&console.debug(`Using cached license token (expires in ${t.exp-r}s)`),t.token):(e&&console.debug("Cached license token expired or expiring soon, refreshing"),C.delete(n),null)}var V=(i=>(i.ALL="all",i.SEARCH="search",i.AI_ALL="ai-all",i.AI_TRAIN="ai-train",i.AI_INDEX="ai-index",i.AI_INPUT="ai-input",i))(V||{});async function le(n,e,t){try{let r=await fetch(n,e);if(!r.ok){let o=await r.text().catch(()=>""),i=`Failed to obtain license token: ${r.status} ${r.statusText}${o?` - ${o}`:""}`;throw new Error(i)}let s;try{s=await r.json()}catch(o){throw t&&console.error("Failed to parse license token response as JSON:",o),new Error("Failed to parse license token response as JSON")}if(!s?.access_token)throw new Error("License token response missing access_token");return s.access_token}catch(r){throw t&&console.error("Error generating license token:",r),r}}async function ue(n,e){let t=new URL(n).origin,r=S.get(t);if(r){let a=Math.floor(Date.now()/1e3);if(a-r.fetchedAt<T)return e&&console.debug(`Using cached license.xml for origin ${t} (expires in ${T-(a-r.fetchedAt)}s)`),r.xml;e&&console.debug(`Cached license.xml for origin ${t} expired, re-fetching`),S.delete(t)}let s=`${t}/license.xml`,o=await fetch(s,{headers:{"User-Agent":m}});if(!o.ok)throw e&&console.error(`Failed to fetch license.xml from ${s}: ${o.status}`),new Error(`Failed to fetch license.xml from ${s}: ${o.status}`);let i=await o.text();return e&&console.debug("Fetched license.xml from",s),ae(),S.set(t,{xml:i,fetchedAt:Math.floor(Date.now()/1e3)}),i}function de(n,e){let t=[],r=/<content\s([^>]*)>([\s\S]*?)<\/content>/gi,s=/url\s*=\s*"([^"]*)"/i,o=/server\s*=\s*"([^"]*)"/i,i=/<license[^>]*>[\s\S]*?<\/license>/i,a=0,c;for(;(c=r.exec(n))!==null;){a++;let l=c[1],d=c[2],f=l.match(s),g=l.match(o),R=d.match(i);if(f&&R)t.push({urlPattern:f[1],server:g?.[1],licenseXml:R[0]});else if(e){let b=[!f&&"url",!R&&"<license>"].filter(Boolean).join(", ");console.debug(`Skipping <content> element #${a}: missing ${b}`)}}return e&&console.debug(`Found ${a} <content> element(s), ${t.length} valid`),t}function fe(n,e){let t=/<permits\b[^>]*type\s*=\s*"usage"[^>]*>([\s\S]*?)<\/permits>/gi,r=/<prohibits\b[^>]*type\s*=\s*"usage"[^>]*>([\s\S]*?)<\/prohibits>/gi,s;for(;(s=r.exec(n))!==null;){let o=s[1].trim().split(/\s+/).filter(Boolean);if(o.includes("all")||o.includes(e))return!1}for(;(s=t.exec(n))!==null;){let o=s[1].trim().split(/\s+/).filter(Boolean);if(o.includes("all")||o.includes(e))return!0}return!1}function J(n,e,t){let r=new URL(e),s=r.host,o=r.pathname;t&&console.debug(`Matching resource URL: ${e} (host=${s}, path=${o})`);let i=null,a=-1;for(let c of n){let l;if(c.urlPattern.startsWith("/"))l=c.urlPattern;else{let g;try{g=new URL(c.urlPattern)}catch{t&&console.debug(`Skipping block with invalid URL pattern: ${c.urlPattern}`);continue}if(g.host!==s){t&&console.debug(`Skipping block: host mismatch (pattern=${g.host}, resource=${s})`);continue}l=g.pathname}if(l===o)return t&&console.debug(`Exact match found: ${c.urlPattern}`),c;let f=B(l,o);f>a&&(a=f,i=c)}return t&&console.debug(i?`Wildcard match found: ${i.urlPattern} (specificity=${a})`:`No matching content block found for ${e}`),i}function pe(n,e,t,r){let s=n.filter(o=>!o.server&&fe(o.licenseXml,t));return J(s,e,r)}async function W({clientId:n,clientSecret:e,resourceUrl:t,usage:r,debug:s}){let o=await ue(t,s);s&&console.debug(`Fetched license.xml (${o.length} chars)`);let i=de(o,s);if(i.length===0)throw s&&console.error("No valid <content> elements with <license> found in license.xml"),new Error("No valid <content> elements with <license> found in license.xml");if(r&&pe(i,t,r,s)){s&&(console.debug("Matched serverless content to usage and resource URL combination, skipping license token request. "),console.debug("URL: "+t+", Usage: "+r));return}let a=i.filter(E=>!!E.server),c=J(a,t,s);if(!c){if(s){let E=a.map(w=>w.urlPattern).join(", ");console.error(`No <content> element matches resource URL: ${t}. Available patterns: ${E}`)}throw new Error(`No <content> element in license.xml matches resource URL: ${t}`)}s&&(console.debug("Matched content block for resource URL:",t),console.debug("Using license XML:",c.licenseXml));let l=`${n}:${c.server}:${c.urlPattern}`,d=ce(l,s);if(d)return d;let f=c.server+"/token";s&&console.debug(`Requesting license token from ${f}`);let g=new URLSearchParams({grant_type:"client_credentials",license:c.licenseXml,resource:c.urlPattern}),R={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${n}:${e}`),"User-Agent":m},body:g.toString()},b=await le(f,R,s);try{let{decodeJwt:E}=await I(),w=E(b);w.exp&&C.set(l,{token:b,exp:w.exp})}catch{s&&console.debug("Failed to decode token for caching, skipping cache")}return b}var D=new Map,ge=48*60*60*1e3,x=class extends Error{constructor(e){super(`No matching platform key found: ${e}`),this.name="JwksKeyNotFoundError"}};function he(){let n={method:"GET",headers:{"User-Agent":m}};return globalThis.fastly&&(n={...n,backend:L}),n}async function me({cacheKey:n,url:e,debug:t,failureMessage:r,logLabel:s}){let o=D.get(n);if(o&&Date.now()-o.cachedAt<ge)return o.data;try{let i=await fetch(e,he());if(!i.ok)throw new Error(`${r}: ${i.status}`);let a=await i.json();return D.set(n,{data:a,cachedAt:Date.now()}),a}catch(i){throw t&&console.error(s,i),i}}async function M(n,e){let t=`${n}/.well-known/jwks.json/platform`;return e&&console.debug(`Fetching platform JWKS from URL: ${t}`),me({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}function j(){D.clear()}async function G({apiKey:n,baseUrl:e,eventName:t,properties:r,licenseId:s,debug:o=!1}){let i={event_name:t,license_id:s,properties:r};try{let a={method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":m},body:JSON.stringify(i)};globalThis.fastly&&(a={...a,backend:L});let c=await fetch(`${e}/events`,a);!c.ok&&o&&console.error(`Failed to record event: ${c.status}`)}catch(a){o&&console.error("Error recording event:",a)}}var Re=new Set(["authorization","cookie","set-cookie","proxy-authorization","x-api-key","x-amz-security-token","user-agent","x-license-auth"]);function X(n){let e={};for(let[t,r]of Object.entries(n)){let s=t.toLowerCase();Re.has(s)||(e[`h_${s}`]=r)}return e}var v=n=>n.trim().replace(/\/+$/,"");function y(n){switch(n){case"missing_license_token":return"Authorization header missing or malformed";case"invalid_license_algorithm":return"Unsupported token algorithm";case"license_token_expired":return"The license token has expired";case"license_signature_verification_failed":return"The license token signature is invalid";case"invalid_license_header":return"The license token header is malformed";case"invalid_license_payload":return"The license token payload is malformed";case"invalid_license_issuer":return"The license token issuer is not recognized";case"invalid_license_audience":return"The license does not grant access to this resource";case"server_error":return"The server encountered an error validating the license";default:return"License token missing, expired, revoked, or malformed"}}async function P({licenseToken:n,requestUrl:e,supertabBaseUrl:t,debug:r}){let{decodeProtectedHeader:s}=await K(),{decodeJwt:o}=await I(),{jwtVerify:i}=await $();if(!n)return{valid:!1,reason:"missing_license_token",error:y("missing_license_token")};let a;try{a=s(n)}catch(p){return r&&console.error("Invalid license JWT header:",p),{valid:!1,reason:"invalid_license_header",error:y("invalid_license_header")}}if(a.alg!=="ES256")return r&&console.error("Unsupported license JWT alg:",a.alg),{valid:!1,reason:"invalid_license_algorithm",error:y("invalid_license_algorithm")};let c;try{c=o(n)}catch(p){return r&&console.error("Invalid license JWT payload:",p),{valid:!1,reason:"invalid_license_payload",error:y("invalid_license_payload")}}let l=c.license_id,d=c.iss,f=d?v(d):void 0,g=v(t);if(!f||!f.startsWith(g))return r&&console.error("License JWT issuer is missing or malformed:",d),{valid:!1,reason:"invalid_license_issuer",error:y("invalid_license_issuer"),licenseId:l};let R=Array.isArray(c.aud)?c.aud.filter(p=>typeof p=="string"):typeof c.aud=="string"?[c.aud]:[],b=v(e);if(!R.some(p=>{let h=v(p);return h?b.startsWith(h):!1}))return r&&console.error("License JWT audience does not match request URL:",c.aud),{valid:!1,reason:"invalid_license_audience",error:y("invalid_license_audience"),licenseId:l};let w=async()=>{let p;try{p=await M(t,r)}catch(h){return r&&console.error("Failed to fetch platform JWKS:",h),{valid:!1,reason:"server_error",error:y("server_error"),licenseId:l}}try{let ne=await i(n,async O=>{let F=p.keys.find(se=>se.kid===O.kid);if(!F)throw new x(O.kid);return F},{issuer:d,algorithms:[a.alg],clockTolerance:"1m"});return{valid:!0,licenseId:l,payload:ne.payload}}catch(h){if(r&&console.error("License JWT verification failed:",h),h instanceof x)throw h;return h instanceof Error&&h.message?.includes("exp")?{valid:!1,reason:"license_token_expired",error:y("license_token_expired"),licenseId:l}:{valid:!1,reason:"license_signature_verification_failed",error:y("license_signature_verification_failed"),licenseId:l}}};try{return await w()}catch(p){if(p instanceof x)return r&&console.debug("Key not found in cached JWKS, clearing cache and retrying..."),j(),await w();throw p}}function z({requestUrl:n}){try{let e=new URL(n);return`${e.protocol}//${e.host}/license.xml`}catch(e){return console.error("[SupertabConnect] generateLicenseLink failed to parse URL:",e),"/license.xml"}}function Y(n){let e=z({requestUrl:n});return{action:"allow",headers:{Link:`<${e}>; rel="license"; type="application/rsl+xml"`,"X-RSL-Status":"token_required","X-RSL-Reason":"missing"}}}function ye(n){switch(n){case"missing_license_token":case"invalid_license_algorithm":return{rslError:"invalid_request",status:401};case"license_token_expired":case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":case"invalid_license_issuer":return{rslError:"invalid_token",status:401};case"invalid_license_audience":return{rslError:"insufficient_scope",status:403};case"server_error":return{rslError:"server_error",status:503};default:return{rslError:"invalid_token",status:401}}}function be(n){return n.replace(/[\r\n]/g,"").replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function q({reason:n,error:e,requestUrl:t}){let{rslError:r,status:s}=ye(n),o=be(e),i=z({requestUrl:t});return{action:"block",status:s,body:`Access to this resource requires a valid license token. Error: ${r} - ${e}`,headers:{"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${r}", error_description="${o}"`,Link:`<${i}>; rel="license"; type="application/rsl+xml"`}}}function Ee(){let n={method:"GET",headers:{"User-Agent":m}};return globalThis.fastly&&(n={...n,backend:L}),n}async function Q(n,e){try{let t=`${n}/merchants/systems/${e}/license.xml`,r=await fetch(t,Ee());if(!r.ok)return new Response("License not found",{status:404});let s=await r.text();return new Response(s,{status:200,headers:new Headers({"Content-Type":"application/rsl+xml"})})}catch(t){return console.error("[SupertabConnect] hostRSLicenseXML failed:",t),new Response("Bad Gateway",{status:502})}}async function N(n){let e=await P({licenseToken:n.token,requestUrl:n.url,supertabBaseUrl:n.supertabBaseUrl,debug:n.debug}),t=G({apiKey:n.apiKey,baseUrl:n.supertabBaseUrl,eventName:e.valid?"license_used":e.reason,properties:{page_url:n.url,user_agent:n.userAgent,sdk_user_agent:m,verification_status:e.valid?"valid":"invalid",verification_reason:e.valid?"success":e.reason,...X(n.requestHeaders??{})},licenseId:e.licenseId,debug:n.debug});return n.ctx?.waitUntil?n.ctx.waitUntil(t):await t,e}async function Z(n,e,t){let r=await n.handleRequest(e,t);if(r.action==="block")return new Response(r.body,{status:r.status,headers:new Headers(r.headers)});let s=await fetch(e);if(r.headers){let o=new Response(s.body,s);for(let[i,a]of Object.entries(r.headers))o.headers.set(i,a);return o}return s}async function ee(n,e,t,r){let s=e.headers.get("x-original-request-url")||e.url;if(r&&new URL(s).pathname==="/license.xml")return await Q(r.baseUrl,r.merchantSystemUrn);let o=new Request(s,{method:e.method,headers:e.headers}),i=await n.handleRequest(o);if(i.action==="block")return new Response(i.body,{status:i.status,headers:new Headers(i.headers)});let a=await fetch(e,{backend:t});if(i.headers){let c=new Response(a.body,a);for(let[l,d]of Object.entries(i.headers))c.headers.set(l,d);return c}return a}function we(n){switch(n){case 401:return"Unauthorized";case 402:return"Payment Required";case 403:return"Forbidden";case 503:return"Service Unavailable";default:return"Error"}}async function te(n,e){let t=e.Records[0].cf.request,r=t.headers?.["x-original-request-url"]?.[0]?.value,s=`${t.headers.host[0].value}${t.uri}`,o=`https://${r||s}${t.querystring?"?"+t.querystring:""}`,i=new Headers;Object.entries(t.headers).forEach(([l,d])=>{d.forEach(({value:f})=>i.append(l,f))});let a=new Request(o,{method:t.method,headers:i}),c=await n.handleRequest(a);if(c.action==="block"){let l={};return Object.entries(c.headers).forEach(([d,f])=>{l[d.toLowerCase()]=[{key:d,value:f}]}),{status:c.status.toString(),statusDescription:we(c.status),headers:l,body:c.body}}return t}function Le(n){let e=n.headers.get("User-Agent")||"",t=n.headers.get("accept")||"",r=n.headers.get("sec-ch-ua"),s=n.headers.get("accept-language"),o=n.cf?.botManagement?.score,i=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],a=e.toLowerCase(),c=i.some(R=>a.includes(R)),l=a.includes("headless")||a.includes("puppeteer")||!r,d=!a.includes("headless")&&!a.includes("puppeteer")&&!r,f=!t||!s,g=typeof o=="number"&&o<30;return(a.includes("safari")||a.includes("mozilla"))&&l&&d?!1:c||l||f||g}var u=class u{constructor(e,t=!1){if(!t&&u._instance){if(e.apiKey!==u._instance.apiKey)throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(t&&u._instance&&u.resetInstance(),!e.apiKey)throw new Error("Missing required configuration: apiKey is required");this.apiKey=e.apiKey,this.enforcement=e.enforcement??"soft",this.botDetector=e.botDetector,this.debug=e.debug??!1,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}static getBaseUrl(){return u.baseUrl}static async verify(e){let t=e.baseUrl??u.baseUrl,r=await P({licenseToken:e.token,requestUrl:e.resourceUrl,supertabBaseUrl:t,debug:e.debug??!1});return r.valid?{valid:!0}:{valid:!1,error:r.error}}async verifyAndRecord(e){let t=await N({token:e.token,url:e.resourceUrl,userAgent:e.userAgent??"unknown",supertabBaseUrl:u.baseUrl,debug:e.debug??this.debug,apiKey:this.apiKey,ctx:e.ctx,requestHeaders:e.requestHeaders});return t.valid?{valid:!0}:{valid:!1,error:t.error}}async handleRequest(e,t){let r=e.headers.get("Authorization")||"",s=r.startsWith("License ")?r.slice(8):null,o=e.url,i=e.headers.get("User-Agent")||"unknown";if(s){if(this.enforcement==="disabled")return{action:"allow"};let c=await N({token:s,url:o,userAgent:i,supertabBaseUrl:u.baseUrl,debug:this.debug,apiKey:this.apiKey,ctx:t,requestHeaders:Object.fromEntries(e.headers.entries())});return c.valid?{action:"allow"}:q({reason:c.reason,error:c.error,requestUrl:o})}if(!(this.botDetector?.(e,t)??!1))return{action:"allow"};switch(this.enforcement){case"strict":return q({reason:"missing_license_token",error:"Authorization header missing or malformed",requestUrl:o});case"soft":return Y(o);default:return{action:"allow"}}}static async obtainLicenseToken(e){return W({clientId:e.clientId,clientSecret:e.clientSecret,resourceUrl:e.resourceUrl,usage:e.usage,debug:e.debug})}static async cloudflareHandleRequests(e,t,r,s){try{let o=new u({apiKey:t.MERCHANT_API_KEY,botDetector:s?.botDetector,enforcement:s?.enforcement});return await Z(o,e,r)}catch(o){return console.error("[SupertabConnect] cloudflareHandleRequests failed:",o),await fetch(e)}}static async fastlyHandleRequests(e,t,r,s){try{let{botDetector:o,enforcement:i}=s??{},a=new u({apiKey:t,botDetector:o,enforcement:i}),c;return s?.enableRSL&&(c={baseUrl:u.baseUrl,merchantSystemUrn:s.merchantSystemUrn}),await ee(a,e,r,c)}catch(o){return console.error("[SupertabConnect] fastlyHandleRequests failed:",o),await fetch(e,{backend:r})}}static async cloudfrontHandleRequests(e,t){let r=e?.Records?.[0]?.cf?.request??{};try{if(!r.headers?.["x-license-auth"])return r;let o=new u({apiKey:t.apiKey,enforcement:t.enforcement});return await te(o,e)}catch(s){return console.error("[SupertabConnect] cloudfrontHandleRequests failed:",s),r}}};u.baseUrl="https://api-connect.supertab.co",u._instance=null;var re=u;export{U as CDNStatusDescription,H as EnforcementMode,k as HandlerAction,_ as LicenseTokenInvalidReason,re as SupertabConnect,V as UsageType,Le as defaultBotDetector};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/jose.ts","../src/url-pattern.ts","../src/version.ts","../src/customer.ts","../src/jwks.ts","../src/events.ts","../src/headers.ts","../src/license.ts","../src/cdn.ts","../src/bots.ts","../src/index.ts"],"sourcesContent":["import type { JWTPayload } from \"jose\";\n\nexport enum EnforcementMode {\n DISABLED = \"disabled\",\n SOFT = \"soft\",\n STRICT = \"strict\",\n}\n\nexport interface ExecutionContext {\n waitUntil(promise: Promise<void>): void;\n}\n\nexport type BotDetector = (request: Request, ctx?: ExecutionContext) => boolean;\n\nexport interface SupertabConnectConfig {\n apiKey: string;\n enforcement?: EnforcementMode;\n botDetector?: BotDetector;\n debug?: boolean;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n properties: Record<string, string>;\n}\n\nexport type LicenseTokenVerificationResult =\n | { valid: true; licenseId?: string; payload: JWTPayload }\n | { valid: false; reason: LicenseTokenInvalidReason; error: string; licenseId?: string };\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n SERVER_ERROR = \"server_error\",\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var fastly: object | undefined;\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}\n\nexport enum HandlerAction {\n ALLOW = \"allow\",\n BLOCK = \"block\",\n}\n\nexport type HandlerResult =\n | { action: HandlerAction.ALLOW; headers?: Record<string, string> }\n | { action: HandlerAction.BLOCK; status: number; body: string; headers: Record<string, string> };\n\nexport enum CDNStatusDescription {\n Unauthorized = \"Unauthorized\",\n PaymentRequired = \"Payment Required\",\n Forbidden = \"Forbidden\",\n ServiceUnavailable = \"Service Unavailable\",\n Error = \"Error\",\n}\n\n// CloudFront Lambda@Edge types\n// Uses permissive types to be compatible with aws-lambda package types\nexport interface CloudFrontHeaders {\n [key: string]: Array<{ key?: string; value: string }>;\n}\n\nexport interface CloudFrontResultResponse {\n status: string;\n statusDescription?: CDNStatusDescription;\n headers?: CloudFrontHeaders;\n bodyEncoding?: \"text\" | \"base64\";\n body?: string;\n}\n\n// CloudFrontRequestEvent uses a generic request type to accept aws-lambda's CloudFrontRequest\nexport interface CloudFrontRequestEvent<TRequest = Record<string, any>> {\n Records: Array<{\n cf: {\n config?: {\n distributionDomainName?: string;\n distributionId?: string;\n eventType?: string;\n requestId?: string;\n };\n request: TRequest & {\n uri: string;\n method: string;\n querystring: string;\n headers: CloudFrontHeaders;\n };\n };\n }>;\n}\n\n// Result can be either the original request (pass-through) or a response\n// Using generic to preserve the original request type for pass-through\nexport type CloudFrontRequestResult<TRequest = Record<string, any>> = TRequest | CloudFrontResultResponse;\n\nexport interface CloudfrontHandlerOptions {\n apiKey: string;\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\nexport type RSLVerificationResult = {\n valid: boolean;\n error?: string;\n};\n\ninterface FastlyHandlerBaseOptions {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\ninterface FastlyHandlerWithRSL extends FastlyHandlerBaseOptions {\n enableRSL: true;\n merchantSystemUrn: string;\n}\n\ninterface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {\n enableRSL?: false;\n merchantSystemUrn?: never;\n}\n\nexport type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;\n","// Per-subpath lazy loaders for jose — each caches its own promise to avoid redundant imports.\n\nfunction lazyImport<T>(load: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n return () => {\n if (!cached) {\n cached = load();\n }\n return cached;\n };\n}\n\nexport const loadJwtVerify = lazyImport(() => import(\"jose/jwt/verify\"));\nexport const loadDecodeJwt = lazyImport(() => import(\"jose/jwt/decode\"));\nexport const loadDecodeProtectedHeader = lazyImport(() => import(\"jose/decode/protected_header\"));\nexport const loadKeyImport = lazyImport(() => import(\"jose/key/import\"));\nexport const loadJwtSign = lazyImport(() => import(\"jose/jwt/sign\"));\n","/**\n * Match a URL path against a robots.txt-style pattern.\n *\n * - `*` matches zero or more characters (including `/`)\n * - Trailing `$` anchors the match to the end of the path\n * - Without `$`, patterns without `*` match as prefix at segment boundaries\n * (e.g. `/content` matches `/content/article` but not `/content-other`)\n * - Without `$`, patterns with `*` are prefix-matched from the start\n *\n * Returns specificity (number of literal characters) on match, or -1 on no match.\n */\nexport function scorePathPattern(pattern: string, path: string): number {\n let anchored = false;\n let pat = pattern;\n\n if (pat.endsWith(\"$\")) {\n anchored = true;\n pat = pat.slice(0, -1);\n }\n\n const hasWildcard = pat.includes(\"*\");\n\n // Escape regex special chars (except *) and treat them as literals\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Converts wildcard * to regex equivalent .*\n const regexBody = escaped.replace(/\\*/g, \".*\");\n\n let regexStr: string;\n if (anchored) {\n regexStr = `^${regexBody}$`;\n } else if (hasWildcard) {\n regexStr = `^${regexBody}`;\n } else {\n // No wildcards, no anchor: prefix match at segment boundary\n // Special case: \"/\" matches all paths\n if (pat === \"/\") {\n regexStr = `^/`;\n } else {\n regexStr = `^${regexBody}(/|$)`;\n }\n }\n\n if (new RegExp(regexStr).test(path)) {\n return pat.replace(/\\*/g, \"\").length;\n }\n\n return -1;\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_USER_AGENT = `supertab-connect-sdk-typescript/${typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"unknown\"}`;\n","import { loadKeyImport, loadJwtSign, loadDecodeJwt } from \"./jose\";\nimport { scorePathPattern } from \"./url-pattern\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype CachedToken = { token: string; exp: number };\n\n// In-memory cache for license tokens, keyed by \"clientId:server:urlPattern\"\nconst licenseTokenCache = new Map<string, CachedToken>();\n\ntype CachedLicenseXml = { xml: string; fetchedAt: number };\nconst LICENSE_XML_TTL_SECONDS = 15 * 60; // 15 minutes\n\n// In-memory cache for license.xml content, keyed by origin (e.g. \"https://example.com\")\nconst licenseXmlCache = new Map<string, CachedLicenseXml>();\n\nfunction evictExpiredLicenseXml(): void {\n const now = Math.floor(Date.now() / 1000);\n for (const [origin, entry] of licenseXmlCache) {\n if (now - entry.fetchedAt >= LICENSE_XML_TTL_SECONDS) {\n licenseXmlCache.delete(origin);\n }\n }\n}\n\nfunction getCachedToken(\n cacheKey: string,\n debug?: boolean\n): string | null {\n const cached = licenseTokenCache.get(cacheKey);\n if (!cached) return null;\n\n const now = Math.floor(Date.now() / 1000);\n if (cached.exp > now + 30) {\n if (debug) {\n console.debug(\n `Using cached license token (expires in ${cached.exp - now}s)`\n );\n }\n return cached.token;\n }\n\n if (debug) {\n console.debug(\"Cached license token expired or expiring soon, refreshing\");\n }\n licenseTokenCache.delete(cacheKey);\n return null;\n}\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n debug?: boolean;\n};\n\ntype ContentBlock = {\n urlPattern: string;\n licenseXml: string;\n server: string;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\n) {\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: { access_token?: string };\n try {\n data = await response.json() as { access_token?: string };\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const { importPKCS8 } = await loadKeyImport();\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { SignJWT } = await loadJwtSign();\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nasync function fetchLicenseXml(\n resourceUrl: string,\n debug: boolean | undefined\n): Promise<string> {\n const origin = new URL(resourceUrl).origin;\n\n const cached = licenseXmlCache.get(origin);\n if (cached) {\n const now = Math.floor(Date.now() / 1000);\n if (now - cached.fetchedAt < LICENSE_XML_TTL_SECONDS) {\n if (debug) {\n console.debug(`Using cached license.xml for origin ${origin} (expires in ${LICENSE_XML_TTL_SECONDS - (now - cached.fetchedAt)}s)`);\n }\n return cached.xml;\n }\n if (debug) {\n console.debug(`Cached license.xml for origin ${origin} expired, re-fetching`);\n }\n licenseXmlCache.delete(origin);\n }\n\n const licenseXmlUrl = `${origin}/license.xml`;\n const response = await fetch(licenseXmlUrl, {\n headers: { \"User-Agent\": SDK_USER_AGENT },\n });\n if (!response.ok) {\n if (debug) {\n console.error(`Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`);\n }\n throw new Error(\n `Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`\n );\n }\n\n const xml = await response.text();\n if (debug) {\n console.debug(\"Fetched license.xml from\", licenseXmlUrl);\n }\n evictExpiredLicenseXml();\n licenseXmlCache.set(origin, { xml, fetchedAt: Math.floor(Date.now() / 1000) });\n return xml;\n}\n\nfunction parseContentElements(xml: string, debug?: boolean): ContentBlock[] {\n const contentBlocks: ContentBlock[] = [];\n const contentRegex = /<content\\s([^>]*)>([\\s\\S]*?)<\\/content>/gi;\n const urlRegex = /url\\s*=\\s*\"([^\"]*)\"/i;\n const serverRegex = /server\\s*=\\s*\"([^\"]*)\"/i;\n const licenseRegex = /<license[^>]*>[\\s\\S]*?<\\/license>/i;\n\n let elementCount = 0;\n let match;\n while ((match = contentRegex.exec(xml)) !== null) {\n elementCount++;\n const attrs = match[1];\n const body = match[2];\n const urlMatch = attrs.match(urlRegex);\n const serverMatch = attrs.match(serverRegex);\n const licenseMatch = body.match(licenseRegex);\n\n if (urlMatch && serverMatch && licenseMatch) {\n contentBlocks.push({\n urlPattern: urlMatch[1],\n server: serverMatch[1],\n licenseXml: licenseMatch[0],\n });\n } else if (debug) {\n const missing = [\n !urlMatch && \"url\",\n !serverMatch && \"server\",\n !licenseMatch && \"<license>\",\n ].filter(Boolean).join(\", \");\n console.debug(`Skipping <content> element #${elementCount}: missing ${missing}`);\n }\n }\n\n if (debug) {\n console.debug(`Found ${elementCount} <content> element(s), ${contentBlocks.length} valid`);\n }\n\n return contentBlocks;\n}\n\nfunction findBestMatchingContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n debug?: boolean\n): ContentBlock | null {\n const parsed = new URL(resourceUrl);\n const host = parsed.host;\n const path = parsed.pathname;\n\n if (debug) {\n console.debug(`Matching resource URL: ${resourceUrl} (host=${host}, path=${path})`);\n }\n\n let bestMatch: ContentBlock | null = null;\n let bestSpecificity = -1;\n\n for (const block of contentBlocks) {\n let patternPath: string;\n const isPathOnly = block.urlPattern.startsWith(\"/\");\n\n if (isPathOnly) {\n patternPath = block.urlPattern;\n } else {\n let patternUrl: URL;\n try {\n patternUrl = new URL(block.urlPattern);\n } catch {\n if (debug) {\n console.debug(`Skipping block with invalid URL pattern: ${block.urlPattern}`);\n }\n continue;\n }\n\n if (patternUrl.host !== host) {\n if (debug) {\n console.debug(`Skipping block: host mismatch (pattern=${patternUrl.host}, resource=${host})`);\n }\n continue;\n }\n\n patternPath = patternUrl.pathname;\n }\n\n // Exact match — highest priority, return immediately\n if (patternPath === path) {\n if (debug) {\n console.debug(`Exact match found: ${block.urlPattern}`);\n }\n return block;\n }\n\n // Pattern match (wildcards, prefix, anchored)\n const specificity = scorePathPattern(patternPath, path);\n if (specificity > bestSpecificity) {\n bestSpecificity = specificity;\n bestMatch = block;\n }\n }\n\n if (debug) {\n if (bestMatch) {\n console.debug(`Wildcard match found: ${bestMatch.urlPattern} (specificity=${bestSpecificity})`);\n } else {\n console.debug(`No matching content block found for ${resourceUrl}`);\n }\n }\n\n return bestMatch;\n}\n\nexport { parseContentElements, findBestMatchingContent };\nexport type { ContentBlock };\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n resourceUrl,\n debug,\n}: ObtainLicenseTokenParams): Promise<string> {\n const xml = await fetchLicenseXml(resourceUrl, debug);\n if (debug) {\n console.debug(`Fetched license.xml (${xml.length} chars)`);\n }\n const contentBlocks = parseContentElements(xml, debug);\n\n if (contentBlocks.length === 0) {\n if (debug) {\n console.error(\"No valid <content> elements with <license> found in license.xml\");\n }\n throw new Error(\n \"No valid <content> elements with <license> found in license.xml\"\n );\n }\n\n const matchedContent = findBestMatchingContent(contentBlocks, resourceUrl, debug);\n if (!matchedContent) {\n if (debug) {\n const patterns = contentBlocks.map(b => b.urlPattern).join(\", \");\n console.error(`No <content> element matches resource URL: ${resourceUrl}. Available patterns: ${patterns}`);\n }\n throw new Error(\n `No <content> element in license.xml matches resource URL: ${resourceUrl}`\n );\n }\n\n if (debug) {\n console.debug(\"Matched content block for resource URL:\", resourceUrl);\n console.debug(\"Using license XML:\", matchedContent.licenseXml);\n }\n\n // Cache tokens by server + urlPattern so path-only patterns (e.g. \"/articles/*\")\n // on different origins/servers don't collide with each other.\n const cacheKey = `${clientId}:${matchedContent.server}:${matchedContent.urlPattern}`;\n const cached = getCachedToken(cacheKey, debug);\n if (cached) return cached;\n\n const tokenEndpoint = matchedContent.server + '/token';\n if (debug) {\n console.debug(`Requesting license token from ${tokenEndpoint}`);\n }\n\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\n license: matchedContent.licenseXml,\n resource: matchedContent.urlPattern,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n const token = await retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n\n try {\n const { decodeJwt } = await loadDecodeJwt();\n const claims = decodeJwt(token);\n if (claims.exp) {\n licenseTokenCache.set(cacheKey, { token, exp: claims.exp });\n }\n } catch {\n if (debug) {\n console.debug(\"Failed to decode token for caching, skipping cache\");\n }\n }\n\n return token;\n}\n\nexport type { ObtainLicenseTokenParams };\n","import type { JSONWebKeySet } from \"jose\";\nimport { FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype JwksCacheEntry = { data: JSONWebKeySet; cachedAt: number };\nconst jwksCache = new Map<string, JwksCacheEntry>();\nconst JWKS_CACHE_TTL_MS = 48 * 60 * 60 * 1000; // 48 hours\n\nexport class JwksKeyNotFoundError extends Error {\n constructor(kid: string | undefined) {\n super(`No matching platform key found: ${kid}`);\n this.name = \"JwksKeyNotFoundError\";\n }\n}\n\ntype FetchJwksParams = {\n cacheKey: string;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<JSONWebKeySet> {\n const cached = jwksCache.get(cacheKey);\n if (cached && (Date.now() - cached.cachedAt) < JWKS_CACHE_TTL_MS) {\n return cached.data;\n }\n\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json() as JSONWebKeySet;\n jwksCache.set(cacheKey, { data: jwksData, cachedAt: Date.now() });\n return jwksData;\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<JSONWebKeySet> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n if (debug) {\n console.debug(`Fetching platform JWKS from URL: ${jwksUrl}`);\n }\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n","import { EventPayload, FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\nexport async function recordEvent({\n apiKey,\n baseUrl,\n eventName,\n properties,\n licenseId,\n debug = false,\n}: {\n apiKey: string;\n baseUrl: string;\n eventName: string;\n properties: Record<string, string>;\n licenseId?: string;\n debug?: boolean;\n}): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: FetchOptions = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: JSON.stringify(payload),\n };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(`${baseUrl}/events`, options);\n\n if (!response.ok && debug) {\n console.error(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n if (debug) {\n console.error(\"Error recording event:\", error);\n }\n }\n}\n","const DENIED_HEADERS = new Set([\n // Credentials\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n \"x-api-key\",\n \"x-amz-security-token\",\n // Already captured as properties.user_agent — avoid duplication\n \"user-agent\",\n // SDK-internal plumbing (not useful as analytics signal)\n \"x-license-auth\",\n]);\n\n/**\n * Transform a raw headers record into event properties: lowercase keys,\n * drop sensitive headers, and apply an `h_` prefix. Called from\n * verifyAndRecordEvent so both automatic and manual paths enforce the\n * same rules.\n */\nexport function toEventProperties(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase();\n if (!DENIED_HEADERS.has(lowerKey)) {\n result[`h_${lowerKey}`] = value;\n }\n }\n return result;\n}\n","import type { JWTPayload, JWTHeaderParameters } from \"jose\";\nimport { loadJwtVerify, loadDecodeJwt, loadDecodeProtectedHeader } from \"./jose\";\n\ninterface LicenseJWTPayload extends JWTPayload {\n license_id?: string;\n}\nimport {\n ExecutionContext,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks, clearJwksCache, JwksKeyNotFoundError } from \"./jwks\";\nimport { recordEvent } from \"./events\";\nimport { SDK_USER_AGENT } from \"./version\";\nimport { toEventProperties } from \"./headers\";\n\nconst stripTrailingSlash = (value: string) => value.trim().replace(/\\/+$/, \"\");\n\nfunction reasonToErrorDescription(reason: LicenseTokenInvalidReason): string {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n return \"Authorization header missing or malformed\";\n case LicenseTokenInvalidReason.INVALID_ALG:\n return \"Unsupported token algorithm\";\n case LicenseTokenInvalidReason.EXPIRED:\n return \"The license token has expired\";\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n return \"The license token signature is invalid\";\n case LicenseTokenInvalidReason.INVALID_HEADER:\n return \"The license token header is malformed\";\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n return \"The license token payload is malformed\";\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return \"The license token issuer is not recognized\";\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return \"The license does not grant access to this resource\";\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return \"The server encountered an error validating the license\";\n default:\n return \"License token missing, expired, revoked, or malformed\";\n }\n}\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n const { decodeProtectedHeader } = await loadDecodeProtectedHeader();\n const { decodeJwt } = await loadDecodeJwt();\n const { jwtVerify } = await loadJwtVerify();\n\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.MISSING_TOKEN),\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_HEADER),\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ALG),\n };\n }\n\n let payload: LicenseJWTPayload;\n try {\n payload = decodeJwt(licenseToken) as LicenseJWTPayload;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_PAYLOAD),\n };\n }\n\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n const normalizedIssuer = issuer ? stripTrailingSlash(issuer) : undefined;\n const normalizedBaseUrl = stripTrailingSlash(supertabBaseUrl);\n\n if (!normalizedIssuer || !normalizedIssuer.startsWith(normalizedBaseUrl)) {\n if (debug) {\n console.error(\"License JWT issuer is missing or malformed:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ISSUER),\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_AUDIENCE),\n licenseId,\n };\n }\n\n const verify = async (): Promise<LicenseTokenVerificationResult> => {\n let jwks;\n try {\n jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n } catch (error) {\n if (debug) {\n console.error(\"Failed to fetch platform JWKS:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SERVER_ERROR,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SERVER_ERROR),\n licenseId,\n };\n }\n\n try {\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new JwksKeyNotFoundError(jwtHeader.kid);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof JwksKeyNotFoundError) {\n throw error;\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.EXPIRED),\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED),\n licenseId,\n };\n }\n };\n\n try {\n return await verify();\n } catch (error) {\n if (error instanceof JwksKeyNotFoundError) {\n if (debug) {\n console.debug(\"Key not found in cached JWKS, clearing cache and retrying...\");\n }\n clearJwksCache();\n return await verify();\n }\n throw error;\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n try {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n } catch (err) {\n console.error(\"[SupertabConnect] generateLicenseLink failed to parse URL:\", err);\n return \"/license.xml\";\n }\n}\n\n/**\n * Build a HandlerResult that signals a missing token in soft enforcement mode.\n * Returns headers indicating a license is required without blocking the request.\n */\nexport function buildSignalResult(requestUrl: string): HandlerResult {\n const licenseLink = generateLicenseLink({ requestUrl });\n return {\n action: HandlerAction.ALLOW,\n headers: {\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n \"X-RSL-Status\": \"token_required\",\n \"X-RSL-Reason\": \"missing\",\n },\n };\n}\n\nfunction reasonToRslError(reason: LicenseTokenInvalidReason | string): { rslError: string; status: number } {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n case LicenseTokenInvalidReason.INVALID_ALG:\n return { rslError: \"invalid_request\", status: 401 };\n case LicenseTokenInvalidReason.EXPIRED:\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return { rslError: \"invalid_token\", status: 401 };\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return { rslError: \"insufficient_scope\", status: 403 };\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return { rslError: \"server_error\", status: 503 };\n default:\n return { rslError: \"invalid_token\", status: 401 };\n }\n}\n\n/**\n * Sanitize a string for safe use in an HTTP header quoted-string (RFC 7230).\n * Strips CR/LF to prevent header injection and escapes backslashes and quotes.\n */\nfunction sanitizeHeaderValue(value: string): string {\n return value\n .replace(/[\\r\\n]/g, \"\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"');\n}\n\nexport function buildBlockResult({\n reason,\n error,\n requestUrl,\n}: {\n reason: LicenseTokenInvalidReason | string;\n error: string;\n requestUrl: string;\n}): HandlerResult {\n const { rslError, status } = reasonToRslError(reason);\n const errorDescription = sanitizeHeaderValue(error);\n const licenseLink = generateLicenseLink({ requestUrl });\n\n return {\n action: HandlerAction.BLOCK,\n status,\n body: `Access to this resource requires a valid license token. Error: ${rslError} - ${error}`,\n headers: {\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\"`,\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n },\n };\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n try {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/rsl+xml\" }),\n });\n } catch (err) {\n console.error(\"[SupertabConnect] hostRSLicenseXML failed:\", err);\n return new Response(\"Bad Gateway\", { status: 502 });\n }\n}\n\nexport type VerifyAndRecordEventParams = {\n token: string;\n url: string;\n userAgent: string;\n supertabBaseUrl: string;\n debug: boolean;\n apiKey: string;\n ctx?: ExecutionContext;\n requestHeaders?: Record<string, string>;\n};\n\nexport async function verifyAndRecordEvent(\n params: VerifyAndRecordEventParams\n): Promise<LicenseTokenVerificationResult> {\n const verification = await verifyLicenseToken({\n licenseToken: params.token,\n requestUrl: params.url,\n supertabBaseUrl: params.supertabBaseUrl,\n debug: params.debug,\n });\n\n const eventPromise = recordEvent({\n apiKey: params.apiKey,\n baseUrl: params.supertabBaseUrl,\n eventName: verification.valid ? \"license_used\" : verification.reason,\n properties: {\n page_url: params.url,\n user_agent: params.userAgent,\n sdk_user_agent: SDK_USER_AGENT,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.valid ? \"success\" : verification.reason,\n ...toEventProperties(params.requestHeaders ?? {}),\n },\n licenseId: verification.licenseId,\n debug: params.debug,\n });\n if (params.ctx?.waitUntil) {\n params.ctx.waitUntil(eventPromise);\n } else {\n await eventPromise;\n }\n\n return verification;\n}\n","import {\n HandlerAction,\n HandlerResult,\n ExecutionContext,\n CDNStatusDescription,\n CloudFrontHeaders,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n} from \"./types\";\nimport { hostRSLicenseXML } from \"./license\";\n\n// Interface for what the CDN handlers need - avoids circular dependency\ninterface RequestHandler {\n handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;\n}\n\nexport async function handleCloudflareRequest(\n handler: RequestHandler,\n request: Request,\n ctx: ExecutionContext\n): Promise<Response> {\n const result = await handler.handleRequest(request, ctx);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\n/**\n * Handles an Origin request in Fastly. Expects `X-Original-Request-URL` header to contain the original viewer request URL.\n * @param handler Request handler instance that inspects the request and decides whether to allow or block it.\n * @param request Fastly request to process.\n * @param originBackend Fastly backend name used when forwarding allowed requests to origin.\n * @param rslOptions Optional configuration for serving `/license.xml` directly from the edge.\n * @param rslOptions.baseUrl Base URL used when generating the hosted license XML response.\n * @param rslOptions.merchantSystemUrn Merchant system URN for fetching License from Supertab Connect\n */\nexport async function handleFastlyRequest(\n handler: RequestHandler,\n request: Request,\n originBackend: string,\n rslOptions?: {\n baseUrl: string;\n merchantSystemUrn: string;\n }\n): Promise<Response> {\n const originalUrl = request.headers.get(\"x-original-request-url\") || request.url;\n\n if (rslOptions && new URL(originalUrl).pathname === \"/license.xml\") {\n return await hostRSLicenseXML(\n rslOptions.baseUrl,\n rslOptions.merchantSystemUrn\n );\n }\n\n const webRequest = new Request(originalUrl, {\n method: request.method,\n headers: request.headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request, {\n backend: originBackend,\n } as RequestInit);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\nfunction statusDescription(status: number): CDNStatusDescription {\n switch (status) {\n case 401: return CDNStatusDescription.Unauthorized;\n case 402: return CDNStatusDescription.PaymentRequired;\n case 403: return CDNStatusDescription.Forbidden;\n case 503: return CDNStatusDescription.ServiceUnavailable;\n default: return CDNStatusDescription.Error;\n }\n}\n\n/**\n * Handles an Origin request in CloudFront. Expects X-Original-Request-URL header to contain the original viewer request URL.\n * @param handler\n * @param event\n */\nexport async function handleCloudfrontRequest<TRequest extends Record<string, any>>(\n handler: RequestHandler,\n event: CloudFrontRequestEvent<TRequest>\n): Promise<CloudFrontRequestResult<TRequest>> {\n const cfRequest = event.Records[0].cf.request;\n\n // Convert CloudFront request to Web API Request\n const viewerRequestUrl = cfRequest.headers?.[\"x-original-request-url\"]?.[0]?.value;\n const originRequestUrl = `${cfRequest.headers.host[0].value}${cfRequest.uri}`;\n const url = `https://${viewerRequestUrl ? viewerRequestUrl : originRequestUrl}${cfRequest.querystring ? \"?\" + cfRequest.querystring : \"\"}`;\n\n const headers = new Headers();\n Object.entries(cfRequest.headers).forEach(([key, values]) => {\n values.forEach(({ value }) => headers.append(key, value));\n });\n\n const webRequest = new Request(url, {\n method: cfRequest.method,\n headers: headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n const responseHeaders: CloudFrontHeaders = {};\n Object.entries(result.headers).forEach(([key, value]) => {\n responseHeaders[key.toLowerCase()] = [{ key, value }];\n });\n\n return {\n status: result.status.toString(),\n statusDescription: statusDescription(result.status),\n headers: responseHeaders,\n body: result.body,\n };\n }\n\n // Allow request to continue to origin\n return cfRequest;\n}\n","/**\n * Default bot detection logic using multiple signals.\n * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.\n * @param request The incoming request to analyze\n * @returns true if the request appears to be from a bot, false otherwise\n */\nexport function defaultBotDetector(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n lowerCaseUserAgent.includes(\"headless\") ||\n lowerCaseUserAgent.includes(\"puppeteer\") ||\n !secChUa;\n\n const isBrowserMissingSecChUa =\n !lowerCaseUserAgent.includes(\"headless\") &&\n !lowerCaseUserAgent.includes(\"puppeteer\") &&\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && isBrowserMissingSecChUa) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n}\n","import {\n SupertabConnectConfig,\n EnforcementMode,\n BotDetector,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n FastlyHandlerOptions,\n} from \"./types\";\nimport { obtainLicenseToken as obtainLicenseTokenHelper } from \"./customer\";\nimport {\n buildBlockResult,\n buildSignalResult,\n verifyLicenseToken as verifyLicenseTokenHelper,\n verifyAndRecordEvent,\n} from \"./license\";\nimport {\n handleCloudflareRequest,\n handleFastlyRequest,\n handleCloudfrontRequest,\n} from \"./cdn\";\nimport {\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n} from \"./types\";\n\nexport { EnforcementMode, HandlerAction, LicenseTokenInvalidReason, CDNStatusDescription };\nexport type {\n SupertabConnectConfig,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n BotDetector,\n HandlerResult,\n FastlyHandlerOptions,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n};\nexport { defaultBotDetector } from \"./bots\";\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private enforcement!: EnforcementMode;\n private botDetector?: BotDetector;\n private debug!: boolean;\n\n private static _instance: SupertabConnect | null = null;\n\n /**\n * Create a new SupertabConnect instance (singleton).\n * Returns the existing instance if one exists with the same config.\n * @param config SDK configuration including apiKey\n * @param reset Pass true to replace an existing instance with different config\n * @throws If an instance with different config already exists and reset is false\n */\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (config.apiKey !== SupertabConnect._instance.apiKey) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey) {\n throw new Error(\n \"Missing required configuration: apiKey is required\"\n );\n }\n this.apiKey = config.apiKey;\n this.enforcement = config.enforcement ?? EnforcementMode.SOFT;\n this.botDetector = config.botDetector;\n this.debug = config.debug ?? false;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n /**\n * Clear the singleton instance, allowing a new one to be created with different config.\n */\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Get the current base URL for API requests.\n */\n public static getBaseUrl(): string {\n return SupertabConnect.baseUrl;\n }\n\n /**\n * Pure token verification — verifies a license token without recording any events.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.baseUrl Optional override for the Supertab Connect API base URL\n * @param options.debug Enable debug logging (default: false)\n * @returns A promise that resolves with the verification result\n */\n static async verify(options: {\n token: string;\n resourceUrl: string;\n baseUrl?: string;\n debug?: boolean;\n }): Promise<RSLVerificationResult> {\n const baseUrl = options.baseUrl ?? SupertabConnect.baseUrl;\n\n const result = await verifyLicenseTokenHelper({\n licenseToken: options.token,\n requestUrl: options.resourceUrl,\n supertabBaseUrl: baseUrl,\n debug: options.debug ?? false,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Verify a license token and record an analytics event.\n * Uses the instance's apiKey for event recording.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.userAgent Optional user agent string for event recording\n * @param options.requestHeaders Optional request headers to include in the event properties\n * @param options.debug Enable debug logging (default: false)\n * @param options.ctx Optional execution context with waitUntil for non-blocking event recording\n * @returns A promise that resolves with the verification result\n */\n async verifyAndRecord(options: {\n token: string;\n resourceUrl: string;\n userAgent?: string;\n requestHeaders?: Record<string, string>;\n debug?: boolean;\n ctx?: ExecutionContext;\n }): Promise<RSLVerificationResult> {\n const result = await verifyAndRecordEvent({\n token: options.token,\n url: options.resourceUrl,\n userAgent: options.userAgent ?? \"unknown\",\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: options.debug ?? this.debug,\n apiKey: this.apiKey!,\n ctx: options.ctx,\n requestHeaders: options.requestHeaders,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Handle an incoming request by extracting the license token, verifying it, and recording an analytics event.\n * When no token is present, bot detection and enforcement mode determine the response.\n * @param request The incoming HTTP request\n * @param ctx Execution context for non-blocking event recording.\n * Pass this from your platform which has/requires this context (e.g. Cloudflare Workers)\n * @returns A promise that resolves with the handler result indicating ALLOW or BLOCK request\n */\n async handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult> {\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"License \") ? auth.slice(8) : null;\n const url = request.url;\n const userAgent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // Token present → ALWAYS validate, regardless of mode or bot detection\n if (token) {\n if (this.enforcement === EnforcementMode.DISABLED) {\n return { action: HandlerAction.ALLOW };\n }\n const verification = await verifyAndRecordEvent({\n token,\n url,\n userAgent,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: this.debug,\n apiKey: this.apiKey!,\n ctx,\n requestHeaders: Object.fromEntries(request.headers.entries()),\n });\n if (!verification.valid) {\n return buildBlockResult({\n reason: verification.reason,\n error: verification.error,\n requestUrl: url,\n });\n }\n return { action: HandlerAction.ALLOW };\n }\n\n // No token from here on\n const isBot = this.botDetector?.(request, ctx) ?? false;\n\n if (!isBot) {\n return { action: HandlerAction.ALLOW };\n }\n\n // Bot detected, no token — enforcement mode decides\n switch (this.enforcement) {\n case EnforcementMode.STRICT:\n return buildBlockResult({\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: \"Authorization header missing or malformed\",\n requestUrl: url,\n });\n case EnforcementMode.SOFT:\n return buildSignalResult(url);\n default: // DISABLED\n return { action: HandlerAction.ALLOW };\n }\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param options.clientId OAuth client identifier.\n * @param options.clientSecret OAuth client secret for client_credentials flow.\n * @param options.resourceUrl Resource URL attempting to access with a License.\n * @param options.debug Enable debug logging (default: false).\n * @returns Promise resolving to the issued license access token string.\n */\n static async obtainLicenseToken(options: {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n debug?: boolean;\n }): Promise<string> {\n return obtainLicenseTokenHelper({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n resourceUrl: options.resourceUrl,\n debug: options.debug,\n });\n }\n\n /**\n * Handle incoming requests for Cloudflare Workers.\n * Pass this directly as your Worker's fetch handler.\n * @param request The incoming Worker request\n * @param env Worker environment bindings containing MERCHANT_API_KEY\n * @param ctx Worker execution context for non-blocking event recording\n * @param options Optional configuration items\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: ExecutionContext,\n options?: {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n }\n ): Promise<Response> {\n try {\n const instance = new SupertabConnect({\n apiKey: env.MERCHANT_API_KEY,\n botDetector: options?.botDetector,\n enforcement: options?.enforcement,\n });\n return await handleCloudflareRequest(instance, request, ctx);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudflareHandleRequests failed:\", err);\n return await fetch(request);\n }\n }\n\n /**\n * Handle incoming requests for Fastly Compute.\n * @param request The incoming Fastly request\n * @param merchantApiKey The merchant API key for authentication\n * @param originBackend The Fastly backend name to forward allowed requests to\n * @param options Optional configuration items\n * @param options.enableRSL Serve license.xml at /license.xml for RSL-compliant clients (default: false)\n * @param options.merchantSystemUrn Required when enableRSL is true; the merchant system URN used to fetch license.xml\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async fastlyHandleRequests(\n request: Request,\n merchantApiKey: string,\n originBackend: string,\n options?: FastlyHandlerOptions\n ): Promise<Response> {\n try {\n const { botDetector, enforcement } = options ?? {};\n\n const instance = new SupertabConnect({\n apiKey: merchantApiKey,\n botDetector,\n enforcement,\n });\n\n let rslOptions: { baseUrl: string; merchantSystemUrn: string } | undefined;\n if (options?.enableRSL) {\n rslOptions = {\n baseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: options.merchantSystemUrn,\n };\n }\n\n return await handleFastlyRequest(\n instance,\n request,\n originBackend,\n rslOptions\n );\n } catch (err) {\n console.error(\"[SupertabConnect] fastlyHandleRequests failed:\", err);\n return await fetch(request, { backend: originBackend } as RequestInit);\n }\n }\n\n /**\n * Handle incoming requests for AWS CloudFront Lambda@Edge.\n * Use as the handler for an origin-request LambdaEdge function.\n * @param event The CloudFront origin-request event\n * @param options Configuration including apiKey and optional botDetector/enforcement\n */\n static async cloudfrontHandleRequests<TRequest extends Record<string, any>>(\n event: CloudFrontRequestEvent<TRequest>,\n options: CloudfrontHandlerOptions\n ): Promise<CloudFrontRequestResult<TRequest>> {\n const request = event?.Records?.[0]?.cf?.request as TRequest ?? {} as CloudFrontRequestResult<TRequest>;\n try {\n const license_auth_header = request.headers?.[\"x-license-auth\"];\n if (!license_auth_header) {\n // No license auth header means the request is either from a human or from an unidentifiable bot.\n // No reasons to waste compute resources on the rest of the checks.\n return request;\n }\n const instance = new SupertabConnect({\n apiKey: options.apiKey,\n enforcement: options.enforcement\n });\n return await handleCloudfrontRequest(instance, event);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudfrontHandleRequests failed:\", err);\n return request;\n }\n }\n}\n"],"mappings":"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","names":["EnforcementMode","LicenseTokenInvalidReason","FASTLY_BACKEND","HandlerAction","CDNStatusDescription","lazyImport","load","cached","loadJwtVerify","loadDecodeJwt","loadDecodeProtectedHeader","loadKeyImport","loadJwtSign","scorePathPattern","pattern","path","anchored","pat","hasWildcard","regexBody","regexStr","SDK_USER_AGENT","licenseTokenCache","LICENSE_XML_TTL_SECONDS","licenseXmlCache","evictExpiredLicenseXml","now","origin","entry","getCachedToken","cacheKey","debug","cached","retrieveLicenseToken","tokenEndpoint","requestOptions","response","errorBody","errorMessage","data","parseError","error","fetchLicenseXml","resourceUrl","debug","origin","cached","licenseXmlCache","now","LICENSE_XML_TTL_SECONDS","licenseXmlUrl","response","SDK_USER_AGENT","xml","evictExpiredLicenseXml","parseContentElements","contentBlocks","contentRegex","urlRegex","serverRegex","licenseRegex","elementCount","match","attrs","body","urlMatch","serverMatch","licenseMatch","missing","findBestMatchingContent","parsed","host","path","bestMatch","bestSpecificity","block","patternPath","patternUrl","specificity","scorePathPattern","obtainLicenseToken","clientId","clientSecret","resourceUrl","debug","xml","fetchLicenseXml","contentBlocks","parseContentElements","matchedContent","findBestMatchingContent","patterns","cacheKey","cached","getCachedToken","tokenEndpoint","payload","requestOptions","SDK_USER_AGENT","token","retrieveLicenseToken","decodeJwt","loadDecodeJwt","claims","licenseTokenCache","jwksCache","JWKS_CACHE_TTL_MS","JwksKeyNotFoundError","kid","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","cached","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","clearJwksCache","recordEvent","apiKey","baseUrl","eventName","properties","licenseId","debug","payload","options","SDK_USER_AGENT","FASTLY_BACKEND","response","error","DENIED_HEADERS","toEventProperties","headers","result","key","value","lowerKey","stripTrailingSlash","value","reasonToErrorDescription","reason","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","decodeProtectedHeader","loadDecodeProtectedHeader","decodeJwt","loadDecodeJwt","jwtVerify","loadJwtVerify","header","error","payload","licenseId","issuer","normalizedIssuer","normalizedBaseUrl","audienceValues","entry","requestUrlNormalized","normalizedAudience","verify","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","JwksKeyNotFoundError","clearJwksCache","generateLicenseLink","baseURL","err","buildSignalResult","licenseLink","reasonToRslError","sanitizeHeaderValue","buildBlockResult","rslError","status","errorDescription","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","hostRSLicenseXML","merchantSystemUrn","licenseUrl","response","licenseXml","verifyAndRecordEvent","params","verification","eventPromise","recordEvent","toEventProperties","handleCloudflareRequest","handler","request","ctx","result","originResponse","response","key","value","handleFastlyRequest","originBackend","rslOptions","originalUrl","hostRSLicenseXML","webRequest","statusDescription","status","handleCloudfrontRequest","event","cfRequest","viewerRequestUrl","originRequestUrl","url","headers","values","responseHeaders","defaultBotDetector","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","isBrowserMissingSecChUa","missingHeaders","lowBotScore","_SupertabConnect","config","reset","url","options","baseUrl","result","verifyLicenseToken","verifyAndRecordEvent","request","ctx","auth","token","userAgent","verification","buildBlockResult","buildSignalResult","obtainLicenseToken","env","instance","handleCloudflareRequest","err","merchantApiKey","originBackend","botDetector","enforcement","rslOptions","handleFastlyRequest","event","handleCloudfrontRequest","SupertabConnect"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/jose.ts","../src/url-pattern.ts","../src/version.ts","../src/customer.ts","../src/jwks.ts","../src/events.ts","../src/headers.ts","../src/license.ts","../src/cdn.ts","../src/bots.ts","../src/index.ts"],"sourcesContent":["import type { JWTPayload } from \"jose\";\n\nexport enum EnforcementMode {\n DISABLED = \"disabled\",\n SOFT = \"soft\",\n STRICT = \"strict\",\n}\n\nexport interface ExecutionContext {\n waitUntil(promise: Promise<void>): void;\n}\n\nexport type BotDetector = (request: Request, ctx?: ExecutionContext) => boolean;\n\nexport interface SupertabConnectConfig {\n apiKey: string;\n enforcement?: EnforcementMode;\n botDetector?: BotDetector;\n debug?: boolean;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n properties: Record<string, string>;\n}\n\nexport type LicenseTokenVerificationResult =\n | { valid: true; licenseId?: string; payload: JWTPayload }\n | { valid: false; reason: LicenseTokenInvalidReason; error: string; licenseId?: string };\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n SERVER_ERROR = \"server_error\",\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var fastly: object | undefined;\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}\n\nexport enum HandlerAction {\n ALLOW = \"allow\",\n BLOCK = \"block\",\n}\n\nexport type HandlerResult =\n | { action: HandlerAction.ALLOW; headers?: Record<string, string> }\n | { action: HandlerAction.BLOCK; status: number; body: string; headers: Record<string, string> };\n\nexport enum CDNStatusDescription {\n Unauthorized = \"Unauthorized\",\n PaymentRequired = \"Payment Required\",\n Forbidden = \"Forbidden\",\n ServiceUnavailable = \"Service Unavailable\",\n Error = \"Error\",\n}\n\n// CloudFront Lambda@Edge types\n// Uses permissive types to be compatible with aws-lambda package types\nexport interface CloudFrontHeaders {\n [key: string]: Array<{ key?: string; value: string }>;\n}\n\nexport interface CloudFrontResultResponse {\n status: string;\n statusDescription?: CDNStatusDescription;\n headers?: CloudFrontHeaders;\n bodyEncoding?: \"text\" | \"base64\";\n body?: string;\n}\n\n// CloudFrontRequestEvent uses a generic request type to accept aws-lambda's CloudFrontRequest\nexport interface CloudFrontRequestEvent<TRequest = Record<string, any>> {\n Records: Array<{\n cf: {\n config?: {\n distributionDomainName?: string;\n distributionId?: string;\n eventType?: string;\n requestId?: string;\n };\n request: TRequest & {\n uri: string;\n method: string;\n querystring: string;\n headers: CloudFrontHeaders;\n };\n };\n }>;\n}\n\n// Result can be either the original request (pass-through) or a response\n// Using generic to preserve the original request type for pass-through\nexport type CloudFrontRequestResult<TRequest = Record<string, any>> = TRequest | CloudFrontResultResponse;\n\nexport interface CloudfrontHandlerOptions {\n apiKey: string;\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\nexport type RSLVerificationResult = {\n valid: boolean;\n error?: string;\n};\n\ninterface FastlyHandlerBaseOptions {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n}\n\ninterface FastlyHandlerWithRSL extends FastlyHandlerBaseOptions {\n enableRSL: true;\n merchantSystemUrn: string;\n}\n\ninterface FastlyHandlerWithoutRSL extends FastlyHandlerBaseOptions {\n enableRSL?: false;\n merchantSystemUrn?: never;\n}\n\nexport type FastlyHandlerOptions = FastlyHandlerWithRSL | FastlyHandlerWithoutRSL;\n","// Per-subpath lazy loaders for jose — each caches its own promise to avoid redundant imports.\n\nfunction lazyImport<T>(load: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n return () => {\n if (!cached) {\n cached = load();\n }\n return cached;\n };\n}\n\nexport const loadJwtVerify = lazyImport(() => import(\"jose/jwt/verify\"));\nexport const loadDecodeJwt = lazyImport(() => import(\"jose/jwt/decode\"));\nexport const loadDecodeProtectedHeader = lazyImport(() => import(\"jose/decode/protected_header\"));\nexport const loadKeyImport = lazyImport(() => import(\"jose/key/import\"));\nexport const loadJwtSign = lazyImport(() => import(\"jose/jwt/sign\"));\n","/**\n * Match a URL path against a robots.txt-style pattern.\n *\n * - `*` matches zero or more characters (including `/`)\n * - Trailing `$` anchors the match to the end of the path\n * - Without `$`, patterns without `*` match as prefix at segment boundaries\n * (e.g. `/content` matches `/content/article` but not `/content-other`)\n * - Without `$`, patterns with `*` are prefix-matched from the start\n *\n * Returns specificity (number of literal characters) on match, or -1 on no match.\n */\nexport function scorePathPattern(pattern: string, path: string): number {\n let anchored = false;\n let pat = pattern;\n\n if (pat.endsWith(\"$\")) {\n anchored = true;\n pat = pat.slice(0, -1);\n }\n\n const hasWildcard = pat.includes(\"*\");\n\n // Escape regex special chars (except *) and treat them as literals\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Converts wildcard * to regex equivalent .*\n const regexBody = escaped.replace(/\\*/g, \".*\");\n\n let regexStr: string;\n if (anchored) {\n regexStr = `^${regexBody}$`;\n } else if (hasWildcard) {\n regexStr = `^${regexBody}`;\n } else {\n // No wildcards, no anchor: prefix match at segment boundary\n // Special case: \"/\" matches all paths\n if (pat === \"/\") {\n regexStr = `^/`;\n } else {\n regexStr = `^${regexBody}(/|$)`;\n }\n }\n\n if (new RegExp(regexStr).test(path)) {\n return pat.replace(/\\*/g, \"\").length;\n }\n\n return -1;\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_USER_AGENT = `supertab-connect-sdk-typescript/${typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"unknown\"}`;\n","import { loadKeyImport, loadJwtSign, loadDecodeJwt } from \"./jose\";\nimport { scorePathPattern } from \"./url-pattern\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype CachedToken = { token: string; exp: number };\n\n// In-memory cache for license tokens, keyed by \"clientId:server:urlPattern\"\nconst licenseTokenCache = new Map<string, CachedToken>();\n\ntype CachedLicenseXml = { xml: string; fetchedAt: number };\nconst LICENSE_XML_TTL_SECONDS = 15 * 60; // 15 minutes\n\n// In-memory cache for license.xml content, keyed by origin (e.g. \"https://example.com\")\nconst licenseXmlCache = new Map<string, CachedLicenseXml>();\n\nfunction evictExpiredLicenseXml(): void {\n const now = Math.floor(Date.now() / 1000);\n for (const [origin, entry] of licenseXmlCache) {\n if (now - entry.fetchedAt >= LICENSE_XML_TTL_SECONDS) {\n licenseXmlCache.delete(origin);\n }\n }\n}\n\nfunction getCachedToken(\n cacheKey: string,\n debug?: boolean\n): string | null {\n const cached = licenseTokenCache.get(cacheKey);\n if (!cached) return null;\n\n const now = Math.floor(Date.now() / 1000);\n if (cached.exp > now + 30) {\n if (debug) {\n console.debug(\n `Using cached license token (expires in ${cached.exp - now}s)`\n );\n }\n return cached.token;\n }\n\n if (debug) {\n console.debug(\"Cached license token expired or expiring soon, refreshing\");\n }\n licenseTokenCache.delete(cacheKey);\n return null;\n}\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nexport enum UsageType {\n ALL = \"all\",\n SEARCH = \"search\",\n AI_ALL = \"ai-all\",\n AI_TRAIN = \"ai-train\",\n AI_INDEX = \"ai-index\",\n AI_INPUT = \"ai-input\",\n}\n\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n usage?: UsageType;\n debug?: boolean;\n};\n\ntype ContentBlock = {\n urlPattern: string;\n licenseXml: string;\n server?: string;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\n) {\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: { access_token?: string };\n try {\n data = await response.json() as { access_token?: string };\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const { importPKCS8 } = await loadKeyImport();\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { SignJWT } = await loadJwtSign();\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nasync function fetchLicenseXml(\n resourceUrl: string,\n debug: boolean | undefined\n): Promise<string> {\n const origin = new URL(resourceUrl).origin;\n\n const cached = licenseXmlCache.get(origin);\n if (cached) {\n const now = Math.floor(Date.now() / 1000);\n if (now - cached.fetchedAt < LICENSE_XML_TTL_SECONDS) {\n if (debug) {\n console.debug(`Using cached license.xml for origin ${origin} (expires in ${LICENSE_XML_TTL_SECONDS - (now - cached.fetchedAt)}s)`);\n }\n return cached.xml;\n }\n if (debug) {\n console.debug(`Cached license.xml for origin ${origin} expired, re-fetching`);\n }\n licenseXmlCache.delete(origin);\n }\n\n const licenseXmlUrl = `${origin}/license.xml`;\n const response = await fetch(licenseXmlUrl, {\n headers: { \"User-Agent\": SDK_USER_AGENT },\n });\n if (!response.ok) {\n if (debug) {\n console.error(`Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`);\n }\n throw new Error(\n `Failed to fetch license.xml from ${licenseXmlUrl}: ${response.status}`\n );\n }\n\n const xml = await response.text();\n if (debug) {\n console.debug(\"Fetched license.xml from\", licenseXmlUrl);\n }\n evictExpiredLicenseXml();\n licenseXmlCache.set(origin, { xml, fetchedAt: Math.floor(Date.now() / 1000) });\n return xml;\n}\n\nfunction parseContentElements(xml: string, debug?: boolean): ContentBlock[] {\n const contentBlocks: ContentBlock[] = [];\n const contentRegex = /<content\\s([^>]*)>([\\s\\S]*?)<\\/content>/gi;\n const urlRegex = /url\\s*=\\s*\"([^\"]*)\"/i;\n const serverRegex = /server\\s*=\\s*\"([^\"]*)\"/i;\n const licenseRegex = /<license[^>]*>[\\s\\S]*?<\\/license>/i;\n\n let elementCount = 0;\n let match;\n while ((match = contentRegex.exec(xml)) !== null) {\n elementCount++;\n const attrs = match[1];\n const body = match[2];\n const urlMatch = attrs.match(urlRegex);\n const serverMatch = attrs.match(serverRegex);\n const licenseMatch = body.match(licenseRegex);\n\n if (urlMatch && licenseMatch) {\n contentBlocks.push({\n urlPattern: urlMatch[1],\n server: serverMatch?.[1],\n licenseXml: licenseMatch[0],\n });\n } else if (debug) {\n const missing = [\n !urlMatch && \"url\",\n !licenseMatch && \"<license>\",\n ].filter(Boolean).join(\", \");\n console.debug(`Skipping <content> element #${elementCount}: missing ${missing}`);\n }\n }\n\n if (debug) {\n console.debug(`Found ${elementCount} <content> element(s), ${contentBlocks.length} valid`);\n }\n\n return contentBlocks;\n}\n\n/**\n * Check if <license> section permits the chosen usage type without prohibiting it\n * @param licenseXml\n * @param usage\n */\nfunction licensePermitsUsage(\n licenseXml: string,\n usage: UsageType\n): boolean {\n const permitsRegex = /<permits\\b[^>]*type\\s*=\\s*\"usage\"[^>]*>([\\s\\S]*?)<\\/permits>/gi;\n const prohibitsRegex = /<prohibits\\b[^>]*type\\s*=\\s*\"usage\"[^>]*>([\\s\\S]*?)<\\/prohibits>/gi;\n\n let match: RegExpExecArray | null;\n\n // Check for <prohibits> first - it takes precedence\n while ((match = prohibitsRegex.exec(licenseXml)) !== null) {\n const prohibitedUsages = match[1]\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (\n prohibitedUsages.includes(UsageType.ALL) ||\n prohibitedUsages.includes(usage)\n ) {\n return false;\n }\n }\n\n // Now we can safely look for <permits>\n while ((match = permitsRegex.exec(licenseXml)) !== null) {\n const permittedUsages = match[1]\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (\n permittedUsages.includes(UsageType.ALL) ||\n permittedUsages.includes(usage)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction findBestMatchingContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n debug?: boolean\n): ContentBlock | null {\n const parsed = new URL(resourceUrl);\n const host = parsed.host;\n const path = parsed.pathname;\n\n if (debug) {\n console.debug(`Matching resource URL: ${resourceUrl} (host=${host}, path=${path})`);\n }\n\n let bestMatch: ContentBlock | null = null;\n let bestSpecificity = -1;\n\n for (const block of contentBlocks) {\n let patternPath: string;\n const isPathOnly = block.urlPattern.startsWith(\"/\");\n\n if (isPathOnly) {\n patternPath = block.urlPattern;\n } else {\n let patternUrl: URL;\n try {\n patternUrl = new URL(block.urlPattern);\n } catch {\n if (debug) {\n console.debug(`Skipping block with invalid URL pattern: ${block.urlPattern}`);\n }\n continue;\n }\n\n if (patternUrl.host !== host) {\n if (debug) {\n console.debug(`Skipping block: host mismatch (pattern=${patternUrl.host}, resource=${host})`);\n }\n continue;\n }\n\n patternPath = patternUrl.pathname;\n }\n\n // Exact match — highest priority, return immediately\n if (patternPath === path) {\n if (debug) {\n console.debug(`Exact match found: ${block.urlPattern}`);\n }\n return block;\n }\n\n // Pattern match (wildcards, prefix, anchored)\n const specificity = scorePathPattern(patternPath, path);\n if (specificity > bestSpecificity) {\n bestSpecificity = specificity;\n bestMatch = block;\n }\n }\n\n if (debug) {\n if (bestMatch) {\n console.debug(`Wildcard match found: ${bestMatch.urlPattern} (specificity=${bestSpecificity})`);\n } else {\n console.debug(`No matching content block found for ${resourceUrl}`);\n }\n }\n\n return bestMatch;\n}\n\nexport { parseContentElements, findBestMatchingContent };\nexport type { ContentBlock };\n\n/**\n * Find serverless content with <permits> section for the selected usage type that matches with the requested resource.\n * @param contentBlocks Parsed content blocks of the processed License XML\n * @param resourceUrl Requested resource\n * @param usage One of usage types as defined in RSL Specification\n * @param debug Enables debug printouts if true\n */\nfunction findServerlessUsageContent(\n contentBlocks: ContentBlock[],\n resourceUrl: string,\n usage: UsageType,\n debug?: boolean\n): ContentBlock | null {\n const matchingUsageBlocks = contentBlocks.filter(\n (block) => !block.server && licensePermitsUsage(block.licenseXml, usage)\n );\n\n return findBestMatchingContent(matchingUsageBlocks, resourceUrl, debug);\n}\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n resourceUrl,\n usage,\n debug,\n}: ObtainLicenseTokenParams): Promise<string | undefined> {\n const xml = await fetchLicenseXml(resourceUrl, debug);\n if (debug) {\n console.debug(`Fetched license.xml (${xml.length} chars)`);\n }\n const contentBlocks = parseContentElements(xml, debug);\n\n if (contentBlocks.length === 0) {\n if (debug) {\n console.error(\"No valid <content> elements with <license> found in license.xml\");\n }\n throw new Error(\n \"No valid <content> elements with <license> found in license.xml\"\n );\n }\n\n if (usage) {\n const serverlessUsageContent = findServerlessUsageContent(\n contentBlocks,\n resourceUrl,\n usage,\n debug\n );\n\n if (serverlessUsageContent) {\n if (debug) {\n console.debug(\"Matched serverless content to usage and resource URL combination, skipping license token request. \");\n console.debug(\"URL: \" + resourceUrl + \", Usage: \" + usage);\n }\n return undefined;\n }\n }\n\n const tokenContentBlocks = contentBlocks.filter((block) => !!block.server);\n const matchedContent = findBestMatchingContent(tokenContentBlocks, resourceUrl, debug);\n if (!matchedContent) {\n if (debug) {\n const patterns = tokenContentBlocks.map(b => b.urlPattern).join(\", \");\n console.error(`No <content> element matches resource URL: ${resourceUrl}. Available patterns: ${patterns}`);\n }\n throw new Error(\n `No <content> element in license.xml matches resource URL: ${resourceUrl}`\n );\n }\n\n if (debug) {\n console.debug(\"Matched content block for resource URL:\", resourceUrl);\n console.debug(\"Using license XML:\", matchedContent.licenseXml);\n }\n\n // Cache tokens by server + urlPattern so path-only patterns (e.g. \"/articles/*\")\n // on different origins/servers don't collide with each other.\n const cacheKey = `${clientId}:${matchedContent.server}:${matchedContent.urlPattern}`;\n const cached = getCachedToken(cacheKey, debug);\n if (cached) return cached;\n\n const tokenEndpoint = matchedContent.server + '/token';\n if (debug) {\n console.debug(`Requesting license token from ${tokenEndpoint}`);\n }\n\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\n license: matchedContent.licenseXml,\n resource: matchedContent.urlPattern,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: payload.toString(),\n };\n\n const token = await retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n\n try {\n const { decodeJwt } = await loadDecodeJwt();\n const claims = decodeJwt(token);\n if (claims.exp) {\n licenseTokenCache.set(cacheKey, { token, exp: claims.exp });\n }\n } catch {\n if (debug) {\n console.debug(\"Failed to decode token for caching, skipping cache\");\n }\n }\n\n return token;\n}\n\nexport type { ObtainLicenseTokenParams };\n","import type { JSONWebKeySet } from \"jose\";\nimport { FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\ntype JwksCacheEntry = { data: JSONWebKeySet; cachedAt: number };\nconst jwksCache = new Map<string, JwksCacheEntry>();\nconst JWKS_CACHE_TTL_MS = 48 * 60 * 60 * 1000; // 48 hours\n\nexport class JwksKeyNotFoundError extends Error {\n constructor(kid: string | undefined) {\n super(`No matching platform key found: ${kid}`);\n this.name = \"JwksKeyNotFoundError\";\n }\n}\n\ntype FetchJwksParams = {\n cacheKey: string;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<JSONWebKeySet> {\n const cached = jwksCache.get(cacheKey);\n if (cached && (Date.now() - cached.cachedAt) < JWKS_CACHE_TTL_MS) {\n return cached.data;\n }\n\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json() as JSONWebKeySet;\n jwksCache.set(cacheKey, { data: jwksData, cachedAt: Date.now() });\n return jwksData;\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<JSONWebKeySet> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n if (debug) {\n console.debug(`Fetching platform JWKS from URL: ${jwksUrl}`);\n }\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n","import { EventPayload, FASTLY_BACKEND, FetchOptions } from \"./types\";\nimport { SDK_USER_AGENT } from \"./version\";\n\nexport async function recordEvent({\n apiKey,\n baseUrl,\n eventName,\n properties,\n licenseId,\n debug = false,\n}: {\n apiKey: string;\n baseUrl: string;\n eventName: string;\n properties: Record<string, string>;\n licenseId?: string;\n debug?: boolean;\n}): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: FetchOptions = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": SDK_USER_AGENT,\n },\n body: JSON.stringify(payload),\n };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(`${baseUrl}/events`, options);\n\n if (!response.ok && debug) {\n console.error(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n if (debug) {\n console.error(\"Error recording event:\", error);\n }\n }\n}\n","const DENIED_HEADERS = new Set([\n // Credentials\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"proxy-authorization\",\n \"x-api-key\",\n \"x-amz-security-token\",\n // Already captured as properties.user_agent — avoid duplication\n \"user-agent\",\n // SDK-internal plumbing (not useful as analytics signal)\n \"x-license-auth\",\n]);\n\n/**\n * Transform a raw headers record into event properties: lowercase keys,\n * drop sensitive headers, and apply an `h_` prefix. Called from\n * verifyAndRecordEvent so both automatic and manual paths enforce the\n * same rules.\n */\nexport function toEventProperties(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase();\n if (!DENIED_HEADERS.has(lowerKey)) {\n result[`h_${lowerKey}`] = value;\n }\n }\n return result;\n}\n","import type { JWTPayload, JWTHeaderParameters } from \"jose\";\nimport { loadJwtVerify, loadDecodeJwt, loadDecodeProtectedHeader } from \"./jose\";\n\ninterface LicenseJWTPayload extends JWTPayload {\n license_id?: string;\n}\nimport {\n ExecutionContext,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks, clearJwksCache, JwksKeyNotFoundError } from \"./jwks\";\nimport { recordEvent } from \"./events\";\nimport { SDK_USER_AGENT } from \"./version\";\nimport { toEventProperties } from \"./headers\";\n\nconst stripTrailingSlash = (value: string) => value.trim().replace(/\\/+$/, \"\");\n\nfunction reasonToErrorDescription(reason: LicenseTokenInvalidReason): string {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n return \"Authorization header missing or malformed\";\n case LicenseTokenInvalidReason.INVALID_ALG:\n return \"Unsupported token algorithm\";\n case LicenseTokenInvalidReason.EXPIRED:\n return \"The license token has expired\";\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n return \"The license token signature is invalid\";\n case LicenseTokenInvalidReason.INVALID_HEADER:\n return \"The license token header is malformed\";\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n return \"The license token payload is malformed\";\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return \"The license token issuer is not recognized\";\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return \"The license does not grant access to this resource\";\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return \"The server encountered an error validating the license\";\n default:\n return \"License token missing, expired, revoked, or malformed\";\n }\n}\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n const { decodeProtectedHeader } = await loadDecodeProtectedHeader();\n const { decodeJwt } = await loadDecodeJwt();\n const { jwtVerify } = await loadJwtVerify();\n\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.MISSING_TOKEN),\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_HEADER),\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ALG),\n };\n }\n\n let payload: LicenseJWTPayload;\n try {\n payload = decodeJwt(licenseToken) as LicenseJWTPayload;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_PAYLOAD),\n };\n }\n\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n const normalizedIssuer = issuer ? stripTrailingSlash(issuer) : undefined;\n const normalizedBaseUrl = stripTrailingSlash(supertabBaseUrl);\n\n if (!normalizedIssuer || !normalizedIssuer.startsWith(normalizedBaseUrl)) {\n if (debug) {\n console.error(\"License JWT issuer is missing or malformed:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_ISSUER),\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.INVALID_AUDIENCE),\n licenseId,\n };\n }\n\n const verify = async (): Promise<LicenseTokenVerificationResult> => {\n let jwks;\n try {\n jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n } catch (error) {\n if (debug) {\n console.error(\"Failed to fetch platform JWKS:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SERVER_ERROR,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SERVER_ERROR),\n licenseId,\n };\n }\n\n try {\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new JwksKeyNotFoundError(jwtHeader.kid);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof JwksKeyNotFoundError) {\n throw error;\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.EXPIRED),\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n error: reasonToErrorDescription(LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED),\n licenseId,\n };\n }\n };\n\n try {\n return await verify();\n } catch (error) {\n if (error instanceof JwksKeyNotFoundError) {\n if (debug) {\n console.debug(\"Key not found in cached JWKS, clearing cache and retrying...\");\n }\n clearJwksCache();\n return await verify();\n }\n throw error;\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n try {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n } catch (err) {\n console.error(\"[SupertabConnect] generateLicenseLink failed to parse URL:\", err);\n return \"/license.xml\";\n }\n}\n\n/**\n * Build a HandlerResult that signals a missing token in soft enforcement mode.\n * Returns headers indicating a license is required without blocking the request.\n */\nexport function buildSignalResult(requestUrl: string): HandlerResult {\n const licenseLink = generateLicenseLink({ requestUrl });\n return {\n action: HandlerAction.ALLOW,\n headers: {\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n \"X-RSL-Status\": \"token_required\",\n \"X-RSL-Reason\": \"missing\",\n },\n };\n}\n\nfunction reasonToRslError(reason: LicenseTokenInvalidReason | string): { rslError: string; status: number } {\n switch (reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n case LicenseTokenInvalidReason.INVALID_ALG:\n return { rslError: \"invalid_request\", status: 401 };\n case LicenseTokenInvalidReason.EXPIRED:\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n return { rslError: \"invalid_token\", status: 401 };\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n return { rslError: \"insufficient_scope\", status: 403 };\n case LicenseTokenInvalidReason.SERVER_ERROR:\n return { rslError: \"server_error\", status: 503 };\n default:\n return { rslError: \"invalid_token\", status: 401 };\n }\n}\n\n/**\n * Sanitize a string for safe use in an HTTP header quoted-string (RFC 7230).\n * Strips CR/LF to prevent header injection and escapes backslashes and quotes.\n */\nfunction sanitizeHeaderValue(value: string): string {\n return value\n .replace(/[\\r\\n]/g, \"\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"');\n}\n\nexport function buildBlockResult({\n reason,\n error,\n requestUrl,\n}: {\n reason: LicenseTokenInvalidReason | string;\n error: string;\n requestUrl: string;\n}): HandlerResult {\n const { rslError, status } = reasonToRslError(reason);\n const errorDescription = sanitizeHeaderValue(error);\n const licenseLink = generateLicenseLink({ requestUrl });\n\n return {\n action: HandlerAction.BLOCK,\n status,\n body: `Access to this resource requires a valid license token. Error: ${rslError} - ${error}`,\n headers: {\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\"`,\n Link: `<${licenseLink}>; rel=\"license\"; type=\"application/rsl+xml\"`,\n },\n };\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\", headers: { \"User-Agent\": SDK_USER_AGENT } };\n if (globalThis.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n try {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/rsl+xml\" }),\n });\n } catch (err) {\n console.error(\"[SupertabConnect] hostRSLicenseXML failed:\", err);\n return new Response(\"Bad Gateway\", { status: 502 });\n }\n}\n\nexport type VerifyAndRecordEventParams = {\n token: string;\n url: string;\n userAgent: string;\n supertabBaseUrl: string;\n debug: boolean;\n apiKey: string;\n ctx?: ExecutionContext;\n requestHeaders?: Record<string, string>;\n};\n\nexport async function verifyAndRecordEvent(\n params: VerifyAndRecordEventParams\n): Promise<LicenseTokenVerificationResult> {\n const verification = await verifyLicenseToken({\n licenseToken: params.token,\n requestUrl: params.url,\n supertabBaseUrl: params.supertabBaseUrl,\n debug: params.debug,\n });\n\n const eventPromise = recordEvent({\n apiKey: params.apiKey,\n baseUrl: params.supertabBaseUrl,\n eventName: verification.valid ? \"license_used\" : verification.reason,\n properties: {\n page_url: params.url,\n user_agent: params.userAgent,\n sdk_user_agent: SDK_USER_AGENT,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.valid ? \"success\" : verification.reason,\n ...toEventProperties(params.requestHeaders ?? {}),\n },\n licenseId: verification.licenseId,\n debug: params.debug,\n });\n if (params.ctx?.waitUntil) {\n params.ctx.waitUntil(eventPromise);\n } else {\n await eventPromise;\n }\n\n return verification;\n}\n","import {\n HandlerAction,\n HandlerResult,\n ExecutionContext,\n CDNStatusDescription,\n CloudFrontHeaders,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n} from \"./types\";\nimport { hostRSLicenseXML } from \"./license\";\n\n// Interface for what the CDN handlers need - avoids circular dependency\ninterface RequestHandler {\n handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult>;\n}\n\nexport async function handleCloudflareRequest(\n handler: RequestHandler,\n request: Request,\n ctx: ExecutionContext\n): Promise<Response> {\n const result = await handler.handleRequest(request, ctx);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\n/**\n * Handles an Origin request in Fastly. Expects `X-Original-Request-URL` header to contain the original viewer request URL.\n * @param handler Request handler instance that inspects the request and decides whether to allow or block it.\n * @param request Fastly request to process.\n * @param originBackend Fastly backend name used when forwarding allowed requests to origin.\n * @param rslOptions Optional configuration for serving `/license.xml` directly from the edge.\n * @param rslOptions.baseUrl Base URL used when generating the hosted license XML response.\n * @param rslOptions.merchantSystemUrn Merchant system URN for fetching License from Supertab Connect\n */\nexport async function handleFastlyRequest(\n handler: RequestHandler,\n request: Request,\n originBackend: string,\n rslOptions?: {\n baseUrl: string;\n merchantSystemUrn: string;\n }\n): Promise<Response> {\n const originalUrl = request.headers.get(\"x-original-request-url\") || request.url;\n\n if (rslOptions && new URL(originalUrl).pathname === \"/license.xml\") {\n return await hostRSLicenseXML(\n rslOptions.baseUrl,\n rslOptions.merchantSystemUrn\n );\n }\n\n const webRequest = new Request(originalUrl, {\n method: request.method,\n headers: request.headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n return new Response(result.body, {\n status: result.status,\n headers: new Headers(result.headers),\n });\n }\n\n // action === HandlerAction.ALLOW\n const originResponse = await fetch(request, {\n backend: originBackend,\n } as RequestInit);\n\n if (result.headers) {\n const response = new Response(originResponse.body, originResponse);\n for (const [key, value] of Object.entries(result.headers)) {\n response.headers.set(key, value);\n }\n return response;\n }\n\n return originResponse;\n}\n\nfunction statusDescription(status: number): CDNStatusDescription {\n switch (status) {\n case 401: return CDNStatusDescription.Unauthorized;\n case 402: return CDNStatusDescription.PaymentRequired;\n case 403: return CDNStatusDescription.Forbidden;\n case 503: return CDNStatusDescription.ServiceUnavailable;\n default: return CDNStatusDescription.Error;\n }\n}\n\n/**\n * Handles an Origin request in CloudFront. Expects X-Original-Request-URL header to contain the original viewer request URL.\n * @param handler\n * @param event\n */\nexport async function handleCloudfrontRequest<TRequest extends Record<string, any>>(\n handler: RequestHandler,\n event: CloudFrontRequestEvent<TRequest>\n): Promise<CloudFrontRequestResult<TRequest>> {\n const cfRequest = event.Records[0].cf.request;\n\n // Convert CloudFront request to Web API Request\n const viewerRequestUrl = cfRequest.headers?.[\"x-original-request-url\"]?.[0]?.value;\n const originRequestUrl = `${cfRequest.headers.host[0].value}${cfRequest.uri}`;\n const url = `https://${viewerRequestUrl ? viewerRequestUrl : originRequestUrl}${cfRequest.querystring ? \"?\" + cfRequest.querystring : \"\"}`;\n\n const headers = new Headers();\n Object.entries(cfRequest.headers).forEach(([key, values]) => {\n values.forEach(({ value }) => headers.append(key, value));\n });\n\n const webRequest = new Request(url, {\n method: cfRequest.method,\n headers: headers,\n });\n\n const result = await handler.handleRequest(webRequest);\n\n if (result.action === HandlerAction.BLOCK) {\n const responseHeaders: CloudFrontHeaders = {};\n Object.entries(result.headers).forEach(([key, value]) => {\n responseHeaders[key.toLowerCase()] = [{ key, value }];\n });\n\n return {\n status: result.status.toString(),\n statusDescription: statusDescription(result.status),\n headers: responseHeaders,\n body: result.body,\n };\n }\n\n // Allow request to continue to origin\n return cfRequest;\n}\n","/**\n * Default bot detection logic using multiple signals.\n * Checks User-Agent patterns, headless browser indicators, missing headers, and Cloudflare bot scores.\n * @param request The incoming request to analyze\n * @returns true if the request appears to be from a bot, false otherwise\n */\nexport function defaultBotDetector(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n lowerCaseUserAgent.includes(\"headless\") ||\n lowerCaseUserAgent.includes(\"puppeteer\") ||\n !secChUa;\n\n const isBrowserMissingSecChUa =\n !lowerCaseUserAgent.includes(\"headless\") &&\n !lowerCaseUserAgent.includes(\"puppeteer\") &&\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && isBrowserMissingSecChUa) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n}\n","import {\n SupertabConnectConfig,\n EnforcementMode,\n BotDetector,\n HandlerAction,\n HandlerResult,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n FastlyHandlerOptions,\n} from \"./types\";\nimport {\n obtainLicenseToken as obtainLicenseTokenHelper,\n UsageType,\n} from \"./customer\";\nimport {\n buildBlockResult,\n buildSignalResult,\n verifyLicenseToken as verifyLicenseTokenHelper,\n verifyAndRecordEvent,\n} from \"./license\";\nimport {\n handleCloudflareRequest,\n handleFastlyRequest,\n handleCloudfrontRequest,\n} from \"./cdn\";\nimport {\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n} from \"./types\";\n\nexport {\n EnforcementMode,\n HandlerAction,\n LicenseTokenInvalidReason,\n CDNStatusDescription,\n UsageType,\n};\nexport type {\n SupertabConnectConfig,\n RSLVerificationResult,\n ExecutionContext,\n Env,\n BotDetector,\n HandlerResult,\n FastlyHandlerOptions,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudfrontHandlerOptions,\n};\nexport { defaultBotDetector } from \"./bots\";\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private enforcement!: EnforcementMode;\n private botDetector?: BotDetector;\n private debug!: boolean;\n\n private static _instance: SupertabConnect | null = null;\n\n /**\n * Create a new SupertabConnect instance (singleton).\n * Returns the existing instance if one exists with the same config.\n * @param config SDK configuration including apiKey\n * @param reset Pass true to replace an existing instance with different config\n * @throws If an instance with different config already exists and reset is false\n */\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (config.apiKey !== SupertabConnect._instance.apiKey) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey) {\n throw new Error(\n \"Missing required configuration: apiKey is required\"\n );\n }\n this.apiKey = config.apiKey;\n this.enforcement = config.enforcement ?? EnforcementMode.SOFT;\n this.botDetector = config.botDetector;\n this.debug = config.debug ?? false;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n /**\n * Clear the singleton instance, allowing a new one to be created with different config.\n */\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Get the current base URL for API requests.\n */\n public static getBaseUrl(): string {\n return SupertabConnect.baseUrl;\n }\n\n /**\n * Pure token verification — verifies a license token without recording any events.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.baseUrl Optional override for the Supertab Connect API base URL\n * @param options.debug Enable debug logging (default: false)\n * @returns A promise that resolves with the verification result\n */\n static async verify(options: {\n token: string;\n resourceUrl: string;\n baseUrl?: string;\n debug?: boolean;\n }): Promise<RSLVerificationResult> {\n const baseUrl = options.baseUrl ?? SupertabConnect.baseUrl;\n\n const result = await verifyLicenseTokenHelper({\n licenseToken: options.token,\n requestUrl: options.resourceUrl,\n supertabBaseUrl: baseUrl,\n debug: options.debug ?? false,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Verify a license token and record an analytics event.\n * Uses the instance's apiKey for event recording.\n * @param options.token The license token to verify\n * @param options.resourceUrl The URL of the resource being accessed\n * @param options.userAgent Optional user agent string for event recording\n * @param options.requestHeaders Optional request headers to include in the event properties\n * @param options.debug Enable debug logging (default: false)\n * @param options.ctx Optional execution context with waitUntil for non-blocking event recording\n * @returns A promise that resolves with the verification result\n */\n async verifyAndRecord(options: {\n token: string;\n resourceUrl: string;\n userAgent?: string;\n requestHeaders?: Record<string, string>;\n debug?: boolean;\n ctx?: ExecutionContext;\n }): Promise<RSLVerificationResult> {\n const result = await verifyAndRecordEvent({\n token: options.token,\n url: options.resourceUrl,\n userAgent: options.userAgent ?? \"unknown\",\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: options.debug ?? this.debug,\n apiKey: this.apiKey!,\n ctx: options.ctx,\n requestHeaders: options.requestHeaders,\n });\n\n if (result.valid) {\n return { valid: true };\n }\n\n return { valid: false, error: result.error };\n }\n\n /**\n * Handle an incoming request by extracting the license token, verifying it, and recording an analytics event.\n * When no token is present, bot detection and enforcement mode determine the response.\n * @param request The incoming HTTP request\n * @param ctx Execution context for non-blocking event recording.\n * Pass this from your platform which has/requires this context (e.g. Cloudflare Workers)\n * @returns A promise that resolves with the handler result indicating ALLOW or BLOCK request\n */\n async handleRequest(request: Request, ctx?: ExecutionContext): Promise<HandlerResult> {\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"License \") ? auth.slice(8) : null;\n const url = request.url;\n const userAgent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // Token present → ALWAYS validate, regardless of mode or bot detection\n if (token) {\n if (this.enforcement === EnforcementMode.DISABLED) {\n return { action: HandlerAction.ALLOW };\n }\n const verification = await verifyAndRecordEvent({\n token,\n url,\n userAgent,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug: this.debug,\n apiKey: this.apiKey!,\n ctx,\n requestHeaders: Object.fromEntries(request.headers.entries()),\n });\n if (!verification.valid) {\n return buildBlockResult({\n reason: verification.reason,\n error: verification.error,\n requestUrl: url,\n });\n }\n return { action: HandlerAction.ALLOW };\n }\n\n // No token from here on\n const isBot = this.botDetector?.(request, ctx) ?? false;\n\n if (!isBot) {\n return { action: HandlerAction.ALLOW };\n }\n\n // Bot detected, no token — enforcement mode decides\n switch (this.enforcement) {\n case EnforcementMode.STRICT:\n return buildBlockResult({\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n error: \"Authorization header missing or malformed\",\n requestUrl: url,\n });\n case EnforcementMode.SOFT:\n return buildSignalResult(url);\n default: // DISABLED\n return { action: HandlerAction.ALLOW };\n }\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * If usage type is specified and matching serverless content permits it, skips token request and returns undefined.\n * @param options.clientId OAuth client identifier.\n * @param options.clientSecret OAuth client secret for client_credentials flow.\n * @param options.resourceUrl Resource URL attempting to access with a License.\n * @param options.usage Optional usage type.\n * If specified and a matching serverless content exists in license, no token is issued\n * @param options.debug Enable debug logging (default: false).\n * @returns Promise resolving to the issued license access token string, or `undefined` when no token is needed.\n */\n static async obtainLicenseToken(options: {\n clientId: string;\n clientSecret: string;\n resourceUrl: string;\n usage?: UsageType;\n debug?: boolean;\n }): Promise<string | undefined> {\n return obtainLicenseTokenHelper({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n resourceUrl: options.resourceUrl,\n usage: options.usage,\n debug: options.debug,\n });\n }\n\n /**\n * Handle incoming requests for Cloudflare Workers.\n * Pass this directly as your Worker's fetch handler.\n * @param request The incoming Worker request\n * @param env Worker environment bindings containing MERCHANT_API_KEY\n * @param ctx Worker execution context for non-blocking event recording\n * @param options Optional configuration items\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: ExecutionContext,\n options?: {\n botDetector?: BotDetector;\n enforcement?: EnforcementMode;\n }\n ): Promise<Response> {\n try {\n const instance = new SupertabConnect({\n apiKey: env.MERCHANT_API_KEY,\n botDetector: options?.botDetector,\n enforcement: options?.enforcement,\n });\n return await handleCloudflareRequest(instance, request, ctx);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudflareHandleRequests failed:\", err);\n return await fetch(request);\n }\n }\n\n /**\n * Handle incoming requests for Fastly Compute.\n * @param request The incoming Fastly request\n * @param merchantApiKey The merchant API key for authentication\n * @param originBackend The Fastly backend name to forward allowed requests to\n * @param options Optional configuration items\n * @param options.enableRSL Serve license.xml at /license.xml for RSL-compliant clients (default: false)\n * @param options.merchantSystemUrn Required when enableRSL is true; the merchant system URN used to fetch license.xml\n * @param options.botDetector Custom bot detection function\n * @param options.enforcement Enforcement mode (default: SOFT)\n */\n static async fastlyHandleRequests(\n request: Request,\n merchantApiKey: string,\n originBackend: string,\n options?: FastlyHandlerOptions\n ): Promise<Response> {\n try {\n const { botDetector, enforcement } = options ?? {};\n\n const instance = new SupertabConnect({\n apiKey: merchantApiKey,\n botDetector,\n enforcement,\n });\n\n let rslOptions: { baseUrl: string; merchantSystemUrn: string } | undefined;\n if (options?.enableRSL) {\n rslOptions = {\n baseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: options.merchantSystemUrn,\n };\n }\n\n return await handleFastlyRequest(\n instance,\n request,\n originBackend,\n rslOptions\n );\n } catch (err) {\n console.error(\"[SupertabConnect] fastlyHandleRequests failed:\", err);\n return await fetch(request, { backend: originBackend } as RequestInit);\n }\n }\n\n /**\n * Handle incoming requests for AWS CloudFront Lambda@Edge.\n * Use as the handler for an origin-request LambdaEdge function.\n * @param event The CloudFront origin-request event\n * @param options Configuration including apiKey and optional botDetector/enforcement\n */\n static async cloudfrontHandleRequests<TRequest extends Record<string, any>>(\n event: CloudFrontRequestEvent<TRequest>,\n options: CloudfrontHandlerOptions\n ): Promise<CloudFrontRequestResult<TRequest>> {\n const request = event?.Records?.[0]?.cf?.request as TRequest ?? {} as CloudFrontRequestResult<TRequest>;\n try {\n const license_auth_header = request.headers?.[\"x-license-auth\"];\n if (!license_auth_header) {\n // No license auth header means the request is either from a human or from an unidentifiable bot.\n // No reasons to waste compute resources on the rest of the checks.\n return request;\n }\n const instance = new SupertabConnect({\n apiKey: options.apiKey,\n enforcement: options.enforcement\n });\n return await handleCloudfrontRequest(instance, event);\n } catch (err) {\n console.error(\"[SupertabConnect] cloudfrontHandleRequests failed:\", err);\n return request;\n }\n }\n}\n"],"mappings":"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","names":["EnforcementMode","LicenseTokenInvalidReason","FASTLY_BACKEND","HandlerAction","CDNStatusDescription","lazyImport","load","cached","loadJwtVerify","loadDecodeJwt","loadDecodeProtectedHeader","loadKeyImport","loadJwtSign","scorePathPattern","pattern","path","anchored","pat","hasWildcard","regexBody","regexStr","SDK_USER_AGENT","licenseTokenCache","LICENSE_XML_TTL_SECONDS","licenseXmlCache","evictExpiredLicenseXml","now","origin","entry","getCachedToken","cacheKey","debug","cached","UsageType","retrieveLicenseToken","tokenEndpoint","requestOptions","response","errorBody","errorMessage","data","parseError","error","fetchLicenseXml","resourceUrl","debug","origin","cached","licenseXmlCache","now","LICENSE_XML_TTL_SECONDS","licenseXmlUrl","response","SDK_USER_AGENT","xml","evictExpiredLicenseXml","parseContentElements","contentBlocks","contentRegex","urlRegex","serverRegex","licenseRegex","elementCount","match","attrs","body","urlMatch","serverMatch","licenseMatch","missing","licensePermitsUsage","licenseXml","usage","permitsRegex","prohibitsRegex","prohibitedUsages","permittedUsages","findBestMatchingContent","parsed","host","path","bestMatch","bestSpecificity","block","patternPath","patternUrl","specificity","scorePathPattern","findServerlessUsageContent","contentBlocks","resourceUrl","usage","debug","matchingUsageBlocks","block","licensePermitsUsage","findBestMatchingContent","obtainLicenseToken","clientId","clientSecret","xml","fetchLicenseXml","parseContentElements","tokenContentBlocks","matchedContent","patterns","b","cacheKey","cached","getCachedToken","tokenEndpoint","payload","requestOptions","SDK_USER_AGENT","token","retrieveLicenseToken","decodeJwt","loadDecodeJwt","claims","licenseTokenCache","jwksCache","JWKS_CACHE_TTL_MS","JwksKeyNotFoundError","kid","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","cached","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","clearJwksCache","recordEvent","apiKey","baseUrl","eventName","properties","licenseId","debug","payload","options","SDK_USER_AGENT","FASTLY_BACKEND","response","error","DENIED_HEADERS","toEventProperties","headers","result","key","value","lowerKey","stripTrailingSlash","value","reasonToErrorDescription","reason","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","decodeProtectedHeader","loadDecodeProtectedHeader","decodeJwt","loadDecodeJwt","jwtVerify","loadJwtVerify","header","error","payload","licenseId","issuer","normalizedIssuer","normalizedBaseUrl","audienceValues","entry","requestUrlNormalized","normalizedAudience","verify","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","JwksKeyNotFoundError","clearJwksCache","generateLicenseLink","baseURL","err","buildSignalResult","licenseLink","reasonToRslError","sanitizeHeaderValue","buildBlockResult","rslError","status","errorDescription","buildFetchOptions","options","SDK_USER_AGENT","FASTLY_BACKEND","hostRSLicenseXML","merchantSystemUrn","licenseUrl","response","licenseXml","verifyAndRecordEvent","params","verification","eventPromise","recordEvent","toEventProperties","handleCloudflareRequest","handler","request","ctx","result","originResponse","response","key","value","handleFastlyRequest","originBackend","rslOptions","originalUrl","hostRSLicenseXML","webRequest","statusDescription","status","handleCloudfrontRequest","event","cfRequest","viewerRequestUrl","originRequestUrl","url","headers","values","responseHeaders","defaultBotDetector","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","isBrowserMissingSecChUa","missingHeaders","lowBotScore","_SupertabConnect","config","reset","url","options","baseUrl","result","verifyLicenseToken","verifyAndRecordEvent","request","ctx","auth","token","userAgent","verification","buildBlockResult","buildSignalResult","obtainLicenseToken","env","instance","handleCloudflareRequest","err","merchantApiKey","originBackend","botDetector","enforcement","rslOptions","handleFastlyRequest","event","handleCloudfrontRequest","SupertabConnect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getsupertab/supertab-connect-sdk",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Supertab Connect SDK (beta)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",