@getsupertab/supertab-connect-sdk 1.5.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Check our [documentation](https://supertab-connect.mintlify.app/introduction/about-supertab-connect) for more information on Supertab Connect.
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/%40getsupertab%2Fsupertab-connect-sdk.svg)](https://www.npmjs.com/package/%40getsupertab%2Fsupertab-connect-sdk)
6
+ [![License](https://img.shields.io/npm/l/%40getsupertab%2Fsupertab-connect-sdk.svg)](https://github.com/getsupertab/connect-sdk-typescript/blob/main/LICENSE)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/getsupertab/connect-sdk-typescript/ci.yml?branch=main)](https://github.com/getsupertab/connect-sdk-typescript/actions/workflows/ci.yml)
8
+ [![TypeScript Types](https://img.shields.io/npm/types/%40getsupertab%2Fsupertab-connect-sdk.svg)](https://www.npmjs.com/package/%40getsupertab%2Fsupertab-connect-sdk)
9
+ [![Node.js Version](https://img.shields.io/node/v/%40getsupertab%2Fsupertab-connect-sdk.svg)](https://www.npmjs.com/package/%40getsupertab%2Fsupertab-connect-sdk)
10
+
5
11
  ## Installation
6
12
 
7
13
  ```
@@ -10,7 +16,30 @@ npm install @getsupertab/supertab-connect-sdk
10
16
  yarn add @getsupertab/supertab-connect-sdk
11
17
  ```
12
18
 
13
- ## Basic Usage
19
+ ## Customer Usage
20
+
21
+ Obtain a license token for a resource URL:
22
+
23
+ ```ts
24
+ import { SupertabConnect, UsageType } from "@getsupertab/supertab-connect-sdk";
25
+
26
+ const token = await SupertabConnect.obtainLicenseToken({
27
+ clientId: "your_client_id",
28
+ clientSecret: "your_client_secret",
29
+ resourceUrl: "https://example.com/premium/article",
30
+ usage: UsageType.SEARCH,
31
+ });
32
+
33
+ if (token) {
34
+ // Send the token as: Authorization: License <token>
35
+ } else {
36
+ // No token is required for this resource and usage.
37
+ }
38
+ ```
39
+
40
+ ## Merchant Usage
41
+
42
+ Supertab Connect SDK offers various CDN-attuned implementations of CAP ([Crawler Authentication Protocol](https://supertab-connect.mintlify.app/licensing/crawler-authentication-protocol)).
14
43
 
15
44
  ### Cloudflare Workers
16
45
 
@@ -69,6 +98,8 @@ export async function handler(
69
98
 
70
99
  ### Manual Setup
71
100
 
101
+ If you want to do a manual integration, the SDK also provides low-level methods for token verification and event recording.
102
+
72
103
  ```ts
73
104
  import { SupertabConnect } from "@getsupertab/supertab-connect-sdk";
74
105
 
@@ -145,6 +176,7 @@ Verifies a license token and records an analytics event. Uses the instance's `ap
145
176
  | `token` | `string` | Yes | The license token to verify |
146
177
  | `resourceUrl` | `string` | Yes | The URL of the resource being accessed |
147
178
  | `userAgent` | `string` | No | User agent string for event recording |
179
+ | `requestHeaders` | `Record<string, string>` | No | Request headers to include in event properties |
148
180
  | `debug` | `boolean` | No | Enable debug logging |
149
181
  | `ctx` | `ExecutionContext` | No | Execution context for non-blocking event recording |
150
182
 
@@ -194,17 +226,36 @@ Convenience handler for AWS CloudFront Lambda@Edge viewer-request functions.
194
226
  - `event` (`CloudFrontRequestEvent`): The CloudFront viewer-request event
195
227
  - `options` (`CloudfrontHandlerOptions`): Configuration object with `apiKey` and optional `botDetector`/`enforcement`
196
228
 
197
- ### `obtainLicenseToken(options): Promise<string>` (static)
229
+ ### `obtainLicenseToken(options): Promise<string | undefined>` (static)
198
230
 
199
231
  Request a license token from the Supertab Connect token endpoint using OAuth2 client credentials.
200
232
 
233
+ If `usage` is provided and a matching `<content>` block permits that usage without requiring a license token,
234
+ the SDK returns `undefined` instead of requesting a token as this is the valid behavior.
235
+
201
236
  **Parameters (options object):**
202
237
 
203
- | Parameter | Type | Required | Description |
204
- | -------------- | --------- | -------- | ----------------------------------------------- |
205
- | `clientId` | `string` | Yes | OAuth client identifier |
206
- | `clientSecret` | `string` | Yes | OAuth client secret for client_credentials flow |
207
- | `resourceUrl` | `string` | Yes | Resource URL to obtain a license for |
208
- | `debug` | `boolean` | No | Enable debug logging |
238
+ | Parameter | Type | Required | Description |
239
+ | -------------- | ----------- | -------- | -------------------------------------------------------- |
240
+ | `clientId` | `string` | Yes | OAuth client identifier |
241
+ | `clientSecret` | `string` | Yes | OAuth client secret for client_credentials flow |
242
+ | `resourceUrl` | `string` | Yes | Resource URL to obtain a license for |
243
+ | `usage` | `UsageType` | No | Usage being requested; enables serverless usage matching |
244
+ | `debug` | `boolean` | No | Enable debug logging |
245
+
246
+ ```ts
247
+ import { SupertabConnect, UsageType } from "@getsupertab/supertab-connect-sdk";
248
+
249
+ const token = await SupertabConnect.obtainLicenseToken({
250
+ clientId: "your_client_id",
251
+ clientSecret: "your_client_secret",
252
+ resourceUrl: "https://example.com/articles/post-1",
253
+ usage: UsageType.SEARCH,
254
+ });
209
255
 
256
+ if (token === undefined) {
257
+ // The matching serverless license already permits this usage.
258
+ } else {
259
+ // Use the token in the Authorization header.
260
+ }
210
261
  ```
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
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});
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/2.0.1";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
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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};
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/2.0.1";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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getsupertab/supertab-connect-sdk",
3
- "version": "1.5.0",
3
+ "version": "2.0.1",
4
4
  "description": "Supertab Connect SDK (beta)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -13,6 +13,9 @@
13
13
  "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
16
19
  "sideEffects": true,
17
20
  "files": [
18
21
  "dist",