@sekyro/node 0.1.0-rc.5 → 0.1.0-rc.7

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.js CHANGED
@@ -1 +1 @@
1
- var WEBHOOK_MESSAGES={invalid_signature:"No v1 signature in the header matches this body and secret",stale:"Delivery timestamp is outside the tolerance window",malformed:"Signature header or body is not in the expected format"},SekyroWebhookError=class extends Error{name="SekyroWebhookError";code;constructor(code,message=WEBHOOK_MESSAGES[code]){super(message),this.code=code}},CONFIG_MESSAGES={missing_api_key:"Sekyro AI: `apiKey` is required",invalid_base_url:"Sekyro AI: `baseUrl` must be an https: URL (http: is allowed only for localhost, 127.0.0.1 or [::1])",invalid_registry_record:"Sekyro AI: the ANRPTS answer can't be used as a registry record"},SekyroConfigError=class extends TypeError{name="SekyroConfigError";code;constructor(code,message=CONFIG_MESSAGES[code]){super(message),this.code=code}},SekyroApiError=class extends Error{name="SekyroApiError";status;code;requestId;details;retryable;constructor(init){super(init.message,init.cause===void 0?void 0:{cause:init.cause}),this.status=init.status,this.code=init.code,this.requestId=init.requestId,this.details=init.details,this.retryable=isRetryable(init.status,init.code)}},FLOW_ERROR_CODES=new Set(["flow_not_found","flow_not_published","flow_archived","override_invalid","override_widens_flow","reuse_disabled","choice_not_found","choice_locked","choice_option_unavailable"]),FLOW_REF_CODES=new Set(["flow_not_published","flow_archived","reuse_disabled"]),isRecord=x=>typeof x=="object"&&x!==null&&!Array.isArray(x),isStringArray=x=>Array.isArray(x)&&x.every(s=>typeof s=="string");function isIssue(x){return isRecord(x)&&typeof x.code=="string"&&typeof x.message=="string"}function hasFlowDetails(code,details){if(!isRecord(details)||!Array.isArray(details.issues)||!details.issues.every(isIssue))return!1;if(code==="override_widens_flow")return isStringArray(details.fields);if(FLOW_REF_CODES.has(code)){let flow=details.flow;return isRecord(flow)&&typeof flow.id=="string"&&typeof flow.slug=="string"}return!0}function isFlowError(err,...codes){return!(err instanceof SekyroApiError)||!FLOW_ERROR_CODES.has(err.code)||codes.length>0&&!codes.includes(err.code)?!1:hasFlowDetails(err.code,err.details)}function isRetryable(status,code){return code==="network"||code==="timeout"||code==="idempotency_in_progress"||status===429?!0:status>=500&&status<=599}var SDK_VERSION="0.1.0-rc.5";var USER_AGENT=`@sekyro/node/${SDK_VERSION}`,DEFAULT_BASE_URL="https://api.sekyro.ai/api/v2",DEFAULT_TIMEOUT_MS=3e4;function isSuccessEnvelope(value){return typeof value=="object"&&value!==null&&value.success===!0&&"data"in value}function isErrorEnvelope(value){if(typeof value!="object"||value===null)return!1;let v=value;if(v.status!=="error"||typeof v.error!="object"||v.error===null)return!1;let e=v.error;return typeof e.code=="string"&&typeof e.message=="string"}function isAbortLike(err){if(typeof err!="object"||err===null)return!1;let name=err.name;return name==="TimeoutError"||name==="AbortError"}var LOOPBACK_HOSTS=new Set(["localhost","127.0.0.1","[::1]"]);function assertSafeBaseUrl(baseUrl){let url;try{url=new URL(baseUrl)}catch{throw new SekyroConfigError("invalid_base_url")}if(url.protocol!=="https:"&&!(url.protocol==="http:"&&LOOPBACK_HOSTS.has(url.hostname)))throw new SekyroConfigError("invalid_base_url")}var Transport=class{apiKey;baseUrl;timeoutMs;fetchImpl;constructor(opts){let baseUrl=(opts.baseUrl??DEFAULT_BASE_URL).replace(/\/+$/,"");assertSafeBaseUrl(baseUrl),this.apiKey=opts.apiKey,this.baseUrl=baseUrl,this.timeoutMs=opts.timeoutMs??DEFAULT_TIMEOUT_MS,this.fetchImpl=opts.fetch??globalThis.fetch}async call(method,path,body,idempotencyKey){let headers={authorization:`Bearer ${this.apiKey}`,accept:"application/json","user-agent":USER_AGENT};body!==void 0&&(headers["content-type"]="application/json"),idempotencyKey!==void 0&&(headers["x-idempotency-key"]=idempotencyKey);let init={method,headers,signal:AbortSignal.timeout(this.timeoutMs)};body!==void 0&&(init.body=JSON.stringify(body));let res;try{res=await this.fetchImpl(`${this.baseUrl}${path}`,init)}catch(err){throw isAbortLike(err)?new SekyroApiError({status:0,code:"timeout",message:`Request timed out after ${this.timeoutMs}ms`,cause:err}):new SekyroApiError({status:0,code:"network",message:`Network error: ${err instanceof Error?err.message:String(err)}`,cause:err})}let text2=await res.text(),parsed,parseError;try{parsed=text2.length>0?JSON.parse(text2):void 0}catch(err){parseError=err}if(!res.ok){if(isErrorEnvelope(parsed)){let init2={status:res.status,code:parsed.error.code,message:parsed.error.message};throw parsed.error.request_id!==void 0&&(init2.requestId=parsed.error.request_id),parsed.error.details!==void 0&&(init2.details=parsed.error.details),new SekyroApiError(init2)}throw new SekyroApiError({status:res.status,code:"http_error",message:`HTTP ${res.status}${res.statusText?` ${res.statusText}`:""}`})}if(!isSuccessEnvelope(parsed))throw new SekyroApiError({status:res.status,code:"malformed_response",message:"Response is not a `{ success: true, data }` envelope",cause:parseError});return parsed.data}},SessionsApi=class{constructor(call){this.call=call}call;async create(input={},opts={}){let wire=await this.call("POST","/sessions",input,opts.idempotencyKey);return{id:wire.id,status:wire.status,clientToken:wire.client_token,hostedUrl:wire.hosted_url,...wire.flow!==void 0?{flow:wire.flow}:{},steps:wire.steps,expiresAt:wire.expiresAt}}async selectMethod(id,method){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.selectMethod: `id` must be a non-empty string");return this.call("POST",`/sessions/${encodeURIComponent(id)}/method`,{method})}async get(id){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.get: `id` must be a non-empty string");return this.call("GET",`/sessions/${encodeURIComponent(id)}`)}async portrait(id){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.portrait: `id` must be a non-empty string");return this.call("GET",`/sessions/${encodeURIComponent(id)}/portrait?format=url`)}};function assertId(method,id){if(typeof id!="string"||id.length===0)throw new TypeError(`subjects.${method}: \`id\` must be a non-empty string`)}var SubjectsApi=class{constructor(call){this.call=call}call;async get(id){return assertId("get",id),this.call("GET",`/subjects/${encodeURIComponent(id)}`)}async list(query){if(typeof query?.externalRef!="string"||query.externalRef.length===0)throw new TypeError("subjects.list: `externalRef` must be a non-empty string");let qs=new URLSearchParams({externalRef:query.externalRef});return this.call("GET",`/subjects?${qs.toString()}`)}async revoke(id,input={},opts={}){return assertId("revoke",id),this.call("POST",`/subjects/${encodeURIComponent(id)}/revoke`,input,opts.idempotencyKey)}async delete(id){return assertId("delete",id),this.call("DELETE",`/subjects/${encodeURIComponent(id)}`)}},Sekyro=class{sessions;subjects;#transport;constructor(opts){if(typeof opts?.apiKey!="string"||opts.apiKey.length===0)throw new SekyroConfigError("missing_api_key");let transport=new Transport(opts);this.#transport=transport,this.sessions=new SessionsApi(transport.call.bind(transport)),this.subjects=new SubjectsApi(transport.call.bind(transport))}async listFlows(){return this.#transport.call("GET","/flows")}};function registryFromAnrpts(response,options){let p=response?.personne??{},required=(field,value)=>{let s=text(value);if(s===void 0)throw new SekyroConfigError("invalid_registry_record",`Sekyro AI: the ANRPTS answer has no usable \`${field}\``);return s},birthDate=isoDay(p.dateNaissance);if(birthDate===void 0)throw new SekyroConfigError("invalid_registry_record","Sekyro AI: the ANRPTS answer has no usable `personne.dateNaissance`");let photo=required("photo",response?.photo).replace(/\s+/g,""),fetchedAt=new Date(options?.fetchedAt??Number.NaN);if(Number.isNaN(fetchedAt.getTime()))throw new SekyroConfigError("invalid_registry_record","Sekyro AI: `fetchedAt` (when your system received the ANRPTS answer) is missing or not a valid date");let record={nni:required("personne.nni",p.nni),firstNameFr:required("personne.prenomFr",p.prenomFr),lastNameFr:required("personne.patronymeFr",p.patronymeFr),firstNameAr:required("personne.prenomAr",p.prenomAr),lastNameAr:required("personne.patronymeAr",p.patronymeAr),birthDate,photo,fetchedAt:fetchedAt.toISOString()},optional=[["fatherNameFr",p.perePrenomFr],["fatherNameAr",p.perePrenomAr],["birthPlaceFr",p.lieuNaissanceFr],["birthPlaceAr",p.lieuNaissanceAr]];for(let[key,value]of optional){let s=text(value);s!==void 0&&(record[key]=s)}let sex=text(p.sexeCode)?.toUpperCase();(sex==="M"||sex==="F")&&(record.sex=sex);let nationalities=Array.isArray(p.nationalities)?p.nationalities.map(text).filter(n=>n!==void 0).slice(0,5):[];nationalities.length>0&&(record.nationalities=nationalities);let photoDate=isoDay(response?.datePhoto);return photoDate!==void 0&&(record.photoDate=photoDate),record}function text(value){if(typeof value!="string")return;let s=value.trim();return s.length>0?s:void 0}function isoDay(value){let s=text(value);if(s===void 0)return;let iso=/^(\d{4})-(\d{2})-(\d{2})(?:$|T)/.exec(s),fr=/^(\d{2})\/(\d{2})\/(\d{4})$/.exec(s),[y,m,d]=iso?[iso[1],iso[2],iso[3]]:fr?[fr[3],fr[2],fr[1]]:[];if(y===void 0||m===void 0||d===void 0)return;let t=new Date(Date.UTC(Number(y),Number(m)-1,Number(d)));return t.getUTCFullYear()===Number(y)&&t.getUTCMonth()===Number(m)-1&&t.getUTCDate()===Number(d)?`${y}-${m}-${d}`:void 0}import{createHmac,timingSafeEqual}from"crypto";var SEKYRO_SIGNATURE_HEADER="x-sekyro-signature",LEGACY_SIGNATURE_HEADER="x-ibtikar-signature";function signatureFromHeaders(headers){return readHeader(headers,SEKYRO_SIGNATURE_HEADER)??readHeader(headers,LEGACY_SIGNATURE_HEADER)}function readHeader(headers,name){if(typeof headers.get=="function"){let v2=headers.get(name);return v2===null||v2===""?void 0:v2}let record=headers,key=Object.keys(record).find(k=>k.toLowerCase()===name);if(key===void 0)return;let raw=record[key],v=Array.isArray(raw)?raw[0]:raw;return v===void 0||v===""?void 0:v}var HEX64=/^[0-9a-f]{64}$/i;function verifyWebhook(rawBody,header,secret,opts={}){if(!header||!secret)throw new SekyroWebhookError("invalid_signature");let parts=header.split(",").map(part=>{let trimmed=part.trim(),eq=trimmed.indexOf("=");return eq===-1?[trimmed,""]:[trimmed.slice(0,eq).trim(),trimmed.slice(eq+1).trim()]}),tRaw=parts.find(([k])=>k==="t")?.[1],t=tRaw!==void 0&&/^\d+$/.test(tRaw)?Number(tRaw):Number.NaN,sigs=parts.filter(([k])=>k==="v1").map(([,v])=>v);if(!Number.isSafeInteger(t)||sigs.length===0)throw new SekyroWebhookError("malformed");let nowSec=Math.floor((opts.now?.()??Date.now())/1e3),tolerance=opts.toleranceSec??300;if(Math.abs(nowSec-t)>tolerance)throw new SekyroWebhookError("stale");let expected=createHmac("sha256",secret).update(`${t}.${rawBody}`).digest();if(!sigs.some(hex=>{if(!HEX64.test(hex))return!1;let got=Buffer.from(hex,"hex");return got.length===expected.length&&timingSafeEqual(got,expected)}))throw new SekyroWebhookError("invalid_signature");let parsed;try{parsed=JSON.parse(rawBody)}catch{throw new SekyroWebhookError("malformed","Signed body is not valid JSON")}if(parsed===null||typeof parsed!="object"||Array.isArray(parsed))throw new SekyroWebhookError("malformed","Signed body is not a JSON object");return parsed}export{DEFAULT_BASE_URL,DEFAULT_TIMEOUT_MS,LEGACY_SIGNATURE_HEADER,SEKYRO_SIGNATURE_HEADER,Sekyro,SekyroApiError,SekyroConfigError,SekyroWebhookError,isFlowError,registryFromAnrpts,signatureFromHeaders,verifyWebhook};
1
+ var WEBHOOK_MESSAGES={invalid_signature:"No v1 signature in the header matches this body and secret",stale:"Delivery timestamp is outside the tolerance window",malformed:"Signature header or body is not in the expected format"},SekyroWebhookError=class extends Error{name="SekyroWebhookError";code;constructor(code,message=WEBHOOK_MESSAGES[code]){super(message),this.code=code}},RESULT_TOKEN_MESSAGES={invalid_token:"The result token is not a valid Sekyro AI token",expired:"The result token has expired",wrong_audience:"The result token was issued for another app",wrong_issuer:"The result token was not issued by the expected issuer",wrong_env:"The result token is for another environment",key_env_mismatch:"The result token environment does not match its signing key",jwks_unavailable:"The Sekyro AI public key set could not be fetched",invalid_options:"Invalid options for verifyResultToken"},SekyroResultTokenError=class extends Error{name="SekyroResultTokenError";code;constructor(code,message=RESULT_TOKEN_MESSAGES[code],cause){super(message,cause===void 0?void 0:{cause}),this.code=code}},CONFIG_MESSAGES={missing_api_key:"Sekyro AI: `apiKey` is required",invalid_base_url:"Sekyro AI: `baseUrl` must be an https: URL (http: is allowed only for localhost, 127.0.0.1 or [::1])",invalid_registry_record:"Sekyro AI: the ANRPTS answer can't be used as a registry record"},SekyroConfigError=class extends TypeError{name="SekyroConfigError";code;constructor(code,message=CONFIG_MESSAGES[code]){super(message),this.code=code}},SekyroApiError=class extends Error{name="SekyroApiError";status;code;requestId;details;retryable;constructor(init){super(init.message,init.cause===void 0?void 0:{cause:init.cause}),this.status=init.status,this.code=init.code,this.requestId=init.requestId,this.details=init.details,this.retryable=isRetryable(init.status,init.code)}},FLOW_ERROR_CODES=new Set(["flow_not_found","flow_not_published","flow_archived","override_invalid","override_widens_flow","reuse_disabled","choice_not_found","choice_locked","choice_option_unavailable"]),FLOW_REF_CODES=new Set(["flow_not_published","flow_archived","reuse_disabled"]),isRecord=x=>typeof x=="object"&&x!==null&&!Array.isArray(x),isStringArray=x=>Array.isArray(x)&&x.every(s=>typeof s=="string");function isIssue(x){return isRecord(x)&&typeof x.code=="string"&&typeof x.message=="string"}function hasFlowDetails(code,details){if(!isRecord(details)||!Array.isArray(details.issues)||!details.issues.every(isIssue))return!1;if(code==="override_widens_flow")return isStringArray(details.fields);if(FLOW_REF_CODES.has(code)){let flow=details.flow;return isRecord(flow)&&typeof flow.id=="string"&&typeof flow.slug=="string"}return!0}function isFlowError(err,...codes){return!(err instanceof SekyroApiError)||!FLOW_ERROR_CODES.has(err.code)||codes.length>0&&!codes.includes(err.code)?!1:hasFlowDetails(err.code,err.details)}function isRetryable(status,code){return code==="network"||code==="timeout"||code==="idempotency_in_progress"||status===429?!0:status>=500&&status<=599}var SDK_VERSION="0.1.0-rc.7";var USER_AGENT=`@sekyro/node/${SDK_VERSION}`,DEFAULT_BASE_URL="https://api.sekyro.ai/api/v2",DEFAULT_TIMEOUT_MS=3e4;function toCreatedSession(wire){return{id:wire.id,status:wire.status,clientToken:wire.client_token,hostedUrl:wire.hosted_url,...wire.flow!==void 0?{flow:wire.flow}:{},steps:wire.steps,expiresAt:wire.expiresAt,...wire.level_target!==void 0?{levelTarget:wire.level_target}:{}}}var SUBJECT_ID_RE=/^[0-9a-f]{24}$/i;function isSuccessEnvelope(value){return typeof value=="object"&&value!==null&&value.success===!0&&"data"in value}function isErrorEnvelope(value){if(typeof value!="object"||value===null)return!1;let v=value;if(v.status!=="error"||typeof v.error!="object"||v.error===null)return!1;let e=v.error;return typeof e.code=="string"&&typeof e.message=="string"}function isAbortLike(err){if(typeof err!="object"||err===null)return!1;let name=err.name;return name==="TimeoutError"||name==="AbortError"}var LOOPBACK_HOSTS=new Set(["localhost","127.0.0.1","[::1]"]);function assertSafeBaseUrl(baseUrl){let url;try{url=new URL(baseUrl)}catch{throw new SekyroConfigError("invalid_base_url")}if(url.protocol!=="https:"&&!(url.protocol==="http:"&&LOOPBACK_HOSTS.has(url.hostname)))throw new SekyroConfigError("invalid_base_url")}var Transport=class{apiKey;baseUrl;timeoutMs;fetchImpl;constructor(opts){let baseUrl=(opts.baseUrl??DEFAULT_BASE_URL).replace(/\/+$/,"");assertSafeBaseUrl(baseUrl),this.apiKey=opts.apiKey,this.baseUrl=baseUrl,this.timeoutMs=opts.timeoutMs??DEFAULT_TIMEOUT_MS,this.fetchImpl=opts.fetch??globalThis.fetch}async call(method,path,body,idempotencyKey){let headers={authorization:`Bearer ${this.apiKey}`,accept:"application/json","user-agent":USER_AGENT};body!==void 0&&(headers["content-type"]="application/json"),idempotencyKey!==void 0&&(headers["x-idempotency-key"]=idempotencyKey);let init={method,headers,signal:AbortSignal.timeout(this.timeoutMs)};body!==void 0&&(init.body=JSON.stringify(body));let res;try{res=await this.fetchImpl(`${this.baseUrl}${path}`,init)}catch(err){throw isAbortLike(err)?new SekyroApiError({status:0,code:"timeout",message:`Request timed out after ${this.timeoutMs}ms`,cause:err}):new SekyroApiError({status:0,code:"network",message:`Network error: ${err instanceof Error?err.message:String(err)}`,cause:err})}let text2=await res.text(),parsed,parseError;try{parsed=text2.length>0?JSON.parse(text2):void 0}catch(err){parseError=err}if(!res.ok){if(isErrorEnvelope(parsed)){let init2={status:res.status,code:parsed.error.code,message:parsed.error.message};throw parsed.error.request_id!==void 0&&(init2.requestId=parsed.error.request_id),parsed.error.details!==void 0&&(init2.details=parsed.error.details),new SekyroApiError(init2)}throw new SekyroApiError({status:res.status,code:"http_error",message:`HTTP ${res.status}${res.statusText?` ${res.statusText}`:""}`})}if(!isSuccessEnvelope(parsed))throw new SekyroApiError({status:res.status,code:"malformed_response",message:"Response is not a `{ success: true, data }` envelope",cause:parseError});return parsed.data}},SessionsApi=class{constructor(call){this.call=call}call;async create(input={},opts={}){let wire=await this.call("POST","/sessions",input,opts.idempotencyKey);return toCreatedSession(wire)}async selectMethod(id,method){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.selectMethod: `id` must be a non-empty string");return this.call("POST",`/sessions/${encodeURIComponent(id)}/method`,{method})}async get(id){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.get: `id` must be a non-empty string");return this.call("GET",`/sessions/${encodeURIComponent(id)}`)}async list(query={}){let qs=new URLSearchParams;if(query.externalRef!==void 0){if(typeof query.externalRef!="string"||query.externalRef.length===0)throw new TypeError("sessions.list: `externalRef` must be a non-empty string");qs.set("externalRef",query.externalRef)}query.status!==void 0&&qs.set("status",query.status);let suffix=qs.size>0?`?${qs.toString()}`:"";return(await this.call("GET",`/sessions${suffix}`)).filter(s=>(query.externalRef===void 0||s.externalRef===query.externalRef)&&(query.status===void 0||s.status===query.status))}async portrait(id){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.portrait: `id` must be a non-empty string");return this.call("GET",`/sessions/${encodeURIComponent(id)}/portrait?format=url`)}async resultToken(id){if(typeof id!="string"||id.length===0)throw new TypeError("sessions.resultToken: `id` must be a non-empty string");return this.call("GET",`/sessions/${encodeURIComponent(id)}/result-token`)}};function assertId(method,id){if(typeof id!="string"||id.length===0)throw new TypeError(`subjects.${method}: \`id\` must be a non-empty string`)}var SubjectsApi=class{constructor(call){this.call=call}call;async get(id){return assertId("get",id),this.call("GET",`/subjects/${encodeURIComponent(id)}`)}async list(query){if(typeof query?.externalRef!="string"||query.externalRef.length===0)throw new TypeError("subjects.list: `externalRef` must be a non-empty string");let qs=new URLSearchParams({externalRef:query.externalRef});return this.call("GET",`/subjects?${qs.toString()}`)}async revoke(id,input={},opts={}){return assertId("revoke",id),this.call("POST",`/subjects/${encodeURIComponent(id)}/revoke`,input,opts.idempotencyKey)}async delete(id){return assertId("delete",id),this.call("DELETE",`/subjects/${encodeURIComponent(id)}`)}async createSession(target,input,opts={}){let path;if(typeof target=="string")assertId("createSession",target),path=SUBJECT_ID_RE.test(target)?`/subjects/${encodeURIComponent(target)}/sessions`:`/subjects/by-external-ref/${encodeURIComponent(target)}/sessions`;else if(target!==null&&typeof target=="object"&&"subjectId"in target)assertId("createSession",target.subjectId),path=`/subjects/${encodeURIComponent(target.subjectId)}/sessions`;else{let ref=target?.externalRef;assertId("createSession",ref),path=`/subjects/by-external-ref/${encodeURIComponent(ref)}/sessions`}if(input?.mode!=="recheck"&&input?.mode!=="step_up")throw new TypeError("subjects.createSession: `mode` must be 'recheck' or 'step_up'");let wire=await this.call("POST",path,input,opts.idempotencyKey);return toCreatedSession(wire)}},Sekyro=class{sessions;subjects;#transport;constructor(opts){if(typeof opts?.apiKey!="string"||opts.apiKey.length===0)throw new SekyroConfigError("missing_api_key");let transport=new Transport(opts);this.#transport=transport,this.sessions=new SessionsApi(transport.call.bind(transport)),this.subjects=new SubjectsApi(transport.call.bind(transport))}async listFlows(){return this.#transport.call("GET","/flows")}};function registryFromAnrpts(response,options){let p=response?.personne??{},required=(field,value)=>{let s=text(value);if(s===void 0)throw new SekyroConfigError("invalid_registry_record",`Sekyro AI: the ANRPTS answer has no usable \`${field}\``);return s},birthDate=isoDay(p.dateNaissance);if(birthDate===void 0)throw new SekyroConfigError("invalid_registry_record","Sekyro AI: the ANRPTS answer has no usable `personne.dateNaissance`");let photo=required("photo",response?.photo).replace(/\s+/g,""),fetchedAt=new Date(options?.fetchedAt??Number.NaN);if(Number.isNaN(fetchedAt.getTime()))throw new SekyroConfigError("invalid_registry_record","Sekyro AI: `fetchedAt` (when your system received the ANRPTS answer) is missing or not a valid date");let record={nni:required("personne.nni",p.nni),firstNameFr:required("personne.prenomFr",p.prenomFr),lastNameFr:required("personne.patronymeFr",p.patronymeFr),firstNameAr:required("personne.prenomAr",p.prenomAr),lastNameAr:required("personne.patronymeAr",p.patronymeAr),birthDate,photo,fetchedAt:fetchedAt.toISOString()},optional=[["fatherNameFr",p.perePrenomFr],["fatherNameAr",p.perePrenomAr],["birthPlaceFr",p.lieuNaissanceFr],["birthPlaceAr",p.lieuNaissanceAr]];for(let[key,value]of optional){let s=text(value);s!==void 0&&(record[key]=s)}let sex=text(p.sexeCode)?.toUpperCase();(sex==="M"||sex==="F")&&(record.sex=sex);let nationalities=Array.isArray(p.nationalities)?p.nationalities.map(text).filter(n=>n!==void 0).slice(0,5):[];nationalities.length>0&&(record.nationalities=nationalities);let photoDate=isoDay(response?.datePhoto);return photoDate!==void 0&&(record.photoDate=photoDate),record}function text(value){if(typeof value!="string")return;let s=value.trim();return s.length>0?s:void 0}function isoDay(value){let s=text(value);if(s===void 0)return;let iso=/^(\d{4})-(\d{2})-(\d{2})(?:$|T)/.exec(s),fr=/^(\d{2})\/(\d{2})\/(\d{4})$/.exec(s),[y,m,d]=iso?[iso[1],iso[2],iso[3]]:fr?[fr[3],fr[2],fr[1]]:[];if(y===void 0||m===void 0||d===void 0)return;let t=new Date(Date.UTC(Number(y),Number(m)-1,Number(d)));return t.getUTCFullYear()===Number(y)&&t.getUTCMonth()===Number(m)-1&&t.getUTCDate()===Number(d)?`${y}-${m}-${d}`:void 0}import{createHmac,timingSafeEqual}from"crypto";var SEKYRO_SIGNATURE_HEADER="x-sekyro-signature",LEGACY_SIGNATURE_HEADER="x-ibtikar-signature";function signatureFromHeaders(headers){return readHeader(headers,SEKYRO_SIGNATURE_HEADER)??readHeader(headers,LEGACY_SIGNATURE_HEADER)}function readHeader(headers,name){if(typeof headers.get=="function"){let v2=headers.get(name);return v2===null||v2===""?void 0:v2}let record=headers,key=Object.keys(record).find(k=>k.toLowerCase()===name);if(key===void 0)return;let raw=record[key],v=Array.isArray(raw)?raw[0]:raw;return v===void 0||v===""?void 0:v}var HEX64=/^[0-9a-f]{64}$/i;function verifyWebhook(rawBody,header,secret,opts={}){if(!header||!secret)throw new SekyroWebhookError("invalid_signature");let parts=header.split(",").map(part=>{let trimmed=part.trim(),eq=trimmed.indexOf("=");return eq===-1?[trimmed,""]:[trimmed.slice(0,eq).trim(),trimmed.slice(eq+1).trim()]}),tRaw=parts.find(([k])=>k==="t")?.[1],t=tRaw!==void 0&&/^\d+$/.test(tRaw)?Number(tRaw):Number.NaN,sigs=parts.filter(([k])=>k==="v1").map(([,v])=>v);if(!Number.isSafeInteger(t)||sigs.length===0)throw new SekyroWebhookError("malformed");let nowSec=Math.floor((opts.now?.()??Date.now())/1e3),tolerance=opts.toleranceSec??300;if(Math.abs(nowSec-t)>tolerance)throw new SekyroWebhookError("stale");let expected=createHmac("sha256",secret).update(`${t}.${rawBody}`).digest();if(!sigs.some(hex=>{if(!HEX64.test(hex))return!1;let got=Buffer.from(hex,"hex");return got.length===expected.length&&timingSafeEqual(got,expected)}))throw new SekyroWebhookError("invalid_signature");let parsed;try{parsed=JSON.parse(rawBody)}catch{throw new SekyroWebhookError("malformed","Signed body is not valid JSON")}if(parsed===null||typeof parsed!="object"||Array.isArray(parsed))throw new SekyroWebhookError("malformed","Signed body is not a JSON object");return parsed}import{createLocalJWKSet,errors as joseErrors,jwtVerify}from"jose";var DEFAULT_RESULT_TOKEN_ISSUER="https://api.sekyro.ai",RESULT_TOKEN_JWKS_PATH="/.well-known/sekyro/jwks.json",SANDBOX_KID_PREFIX="sbx-",JWKS_MAX_AGE_MS=600*1e3,JWKS_COOLDOWN_MS=30*1e3,JWKS_TIMEOUT_MS=5e3,jwksCache=new Map,LOOPBACK_HOSTS2=new Set(["localhost","127.0.0.1","[::1]"]);function assertSafeUrl(raw){let url;try{url=new URL(raw)}catch{throw new SekyroResultTokenError("invalid_options","`jwksUrl` (or `issuer`) is not a URL")}if(url.protocol!=="https:"&&!(url.protocol==="http:"&&LOOPBACK_HOSTS2.has(url.hostname)))throw new SekyroResultTokenError("invalid_options","`jwksUrl` must be an https: URL (http: is allowed only for localhost, 127.0.0.1 or [::1])")}async function fetchKeySet(url,fetchImpl){let res;try{res=await fetchImpl(url,{method:"GET",headers:{accept:"application/json"},signal:AbortSignal.timeout(JWKS_TIMEOUT_MS)})}catch(err){throw new SekyroResultTokenError("jwks_unavailable",void 0,err)}if(!res.ok)throw new SekyroResultTokenError("jwks_unavailable",`Key set answered HTTP ${res.status}`);let body;try{body=await res.json()}catch(err){throw new SekyroResultTokenError("jwks_unavailable","Key set is not JSON",err)}try{return createLocalJWKSet(body)}catch(err){throw new SekyroResultTokenError("jwks_unavailable","Key set is not a JWKS document",err)}}async function keySet(url,fetchImpl,nowMs,force){let entry=jwksCache.get(url);if(entry?.pending)return entry.pending;if(entry?.keys&&!force&&nowMs-entry.fetchedAt<JWKS_MAX_AGE_MS)return entry.keys;let pending=fetchKeySet(url,fetchImpl),next={fetchedAt:entry?.fetchedAt??0,pending};entry?.keys&&(next.keys=entry.keys),jwksCache.set(url,next);try{let keys=await pending;return jwksCache.set(url,{keys,fetchedAt:nowMs}),keys}catch(err){throw next.keys?jwksCache.set(url,{keys:next.keys,fetchedAt:next.fetchedAt}):jwksCache.delete(url),err}}function mapJoseError(err){if(err instanceof SekyroResultTokenError)return err;if(err instanceof joseErrors.JWTExpired)return new SekyroResultTokenError("expired",void 0,err);if(err instanceof joseErrors.JWTClaimValidationFailed){if(err.claim==="aud")return new SekyroResultTokenError("wrong_audience",void 0,err);if(err.claim==="iss")return new SekyroResultTokenError("wrong_issuer",void 0,err)}return new SekyroResultTokenError("invalid_token",void 0,err)}function str(v){return typeof v=="string"&&v.length>0?v:void 0}function appIdsOf(appId){let ids=Array.isArray(appId)?appId:[appId];if(ids.length===0||ids.some(id=>typeof id!="string"||id.length===0))throw new SekyroResultTokenError("invalid_options","`appId` is required (a non-empty string or array of them)");return ids}function matchedAud(aud,accepted){return typeof aud=="string"?aud:(aud??[]).find(a=>accepted.includes(a))??accepted[0]}function toResult(payload,claim,env,appId){let out={sessionId:String(claim.session_id),orgId:String(claim.org_id??""),appId,env,level:typeof claim.level=="number"?claim.level:null,assurance:str(claim.assurance)??null,outcome:str(claim.outcome)??null,unit:str(claim.unit)??null,verifiedAt:String(claim.verified_at??"")},subjectId=str(payload.sub);subjectId!==void 0&&(out.subjectId=subjectId);let externalRef=str(claim.external_ref);externalRef!==void 0&&(out.externalRef=externalRef);let flow=claim.flow;flow&&typeof flow.slug=="string"&&typeof flow.version=="number"&&(out.flow={slug:flow.slug,version:flow.version}),(claim.reuse_mode==="recheck"||claim.reuse_mode==="step_up")&&(out.reuseMode=claim.reuse_mode);let r=claim.risk;r&&typeof r=="object"&&(out.risk={level:r.level==="high"||r.level==="medium"?r.level:"low",vpn:r.vpn===!0,ipDocumentMismatch:r.ip_document_mismatch===!0,deviceTampered:r.device_tampered===!0});let c=claim.checks;return c&&typeof c=="object"&&(out.checks={attestation:c.attestation,captureProof:c.capture_proof,injectionSignals:c.injection_signals}),Array.isArray(claim.duplicates)&&(out.duplicates=claim.duplicates.map(d=>{let dup={subjectId:String(d.subject_id??d.subjectId??"")},ref=str(d.external_ref)??str(d.externalRef);return ref!==void 0&&(dup.externalRef=ref),dup})),out}async function verifyResultToken(token,opts){let appIds=appIdsOf(opts?.appId);if(typeof token!="string"||token.length===0)throw new SekyroResultTokenError("invalid_token","The token is empty");let issuer=(opts.issuer??DEFAULT_RESULT_TOKEN_ISSUER).replace(/\/+$/,""),jwksUrl=opts.jwksUrl??`${issuer}${RESULT_TOKEN_JWKS_PATH}`;assertSafeUrl(jwksUrl);let fetchImpl=opts.fetch??globalThis.fetch,now=opts.now??Date.now,verifyOpts={issuer,audience:appIds,algorithms:["ES256"],currentDate:new Date(now()),clockTolerance:opts.clockToleranceSec??30},verified;try{let keys=await keySet(jwksUrl,fetchImpl,now(),!1);try{verified=await jwtVerify(token,keys,verifyOpts)}catch(err){if(!(err instanceof joseErrors.JWKSNoMatchingKey))throw err;let entry=jwksCache.get(jwksUrl);if(entry&&now()-entry.fetchedAt<JWKS_COOLDOWN_MS)throw err;verified=await jwtVerify(token,await keySet(jwksUrl,fetchImpl,now(),!0),verifyOpts)}}catch(err){throw mapJoseError(err)}let{payload,protectedHeader}=verified,claim=payload.sekyro;if(typeof claim!="object"||claim===null||typeof claim.session_id!="string")throw new SekyroResultTokenError("invalid_token","The token has no `sekyro` claim");let c=claim,env=c.env;if(env!=="live"&&env!=="sandbox")throw new SekyroResultTokenError("invalid_token","The token has no valid `sekyro.env` claim");let sandboxKey=String(protectedHeader.kid??"").startsWith(SANDBOX_KID_PREFIX);if(env==="sandbox"!==sandboxKey)throw new SekyroResultTokenError("key_env_mismatch");if(opts.env!==void 0&&env!==opts.env)throw new SekyroResultTokenError("wrong_env",`The token is for ${env}, expected ${opts.env}`);return toResult(payload,c,env,matchedAud(payload.aud,appIds))}export{DEFAULT_BASE_URL,DEFAULT_RESULT_TOKEN_ISSUER,DEFAULT_TIMEOUT_MS,LEGACY_SIGNATURE_HEADER,RESULT_TOKEN_JWKS_PATH,SEKYRO_SIGNATURE_HEADER,Sekyro,SekyroApiError,SekyroConfigError,SekyroResultTokenError,SekyroWebhookError,isFlowError,registryFromAnrpts,signatureFromHeaders,verifyResultToken,verifyWebhook};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sekyro/node",
3
- "version": "0.1.0-rc.5",
4
- "description": "Sekyro AI server SDK for Node.js — create and read verification sessions, verify signed webhooks. Zero runtime dependencies.",
3
+ "version": "0.1.0-rc.7",
4
+ "description": "Sekyro AI server SDK for Node.js — create and read verification sessions, verify signed webhooks and result tokens.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -19,6 +19,36 @@
19
19
  "default": "./dist/index.cjs"
20
20
  }
21
21
  },
22
+ "./connect": {
23
+ "import": {
24
+ "types": "./dist/connect/index.d.ts",
25
+ "default": "./dist/connect/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/connect/index.d.cts",
29
+ "default": "./dist/connect/index.cjs"
30
+ }
31
+ },
32
+ "./connect/express": {
33
+ "import": {
34
+ "types": "./dist/connect/express/index.d.ts",
35
+ "default": "./dist/connect/express/index.js"
36
+ },
37
+ "require": {
38
+ "types": "./dist/connect/express/index.d.cts",
39
+ "default": "./dist/connect/express/index.cjs"
40
+ }
41
+ },
42
+ "./connect/next": {
43
+ "import": {
44
+ "types": "./dist/connect/next/index.d.ts",
45
+ "default": "./dist/connect/next/index.js"
46
+ },
47
+ "require": {
48
+ "types": "./dist/connect/next/index.d.cts",
49
+ "default": "./dist/connect/next/index.cjs"
50
+ }
51
+ },
22
52
  "./package.json": "./package.json"
23
53
  },
24
54
  "files": [
@@ -52,8 +82,8 @@
52
82
  "build": "tsup",
53
83
  "test": "vitest run",
54
84
  "typecheck": "tsc --noEmit",
55
- "api:report": "api-extractor run --local",
56
- "api:check": "api-extractor run"
85
+ "api:report": "api-extractor run --local && api-extractor run --local -c api-extractor.connect.json && api-extractor run --local -c api-extractor.connect-express.json && api-extractor run --local -c api-extractor.connect-next.json",
86
+ "api:check": "api-extractor run && api-extractor run -c api-extractor.connect.json && api-extractor run -c api-extractor.connect-express.json && api-extractor run -c api-extractor.connect-next.json"
57
87
  },
58
88
  "devDependencies": {
59
89
  "@microsoft/api-extractor": "^7",
@@ -61,5 +91,8 @@
61
91
  "tsup": "^8",
62
92
  "typescript": "^5.7",
63
93
  "vitest": "^3"
94
+ },
95
+ "dependencies": {
96
+ "jose": "^5.10.0"
64
97
  }
65
98
  }
package/sbom.cdx.json CHANGED
@@ -97,9 +97,9 @@
97
97
  "type": "application",
98
98
  "name": "node",
99
99
  "group": "@sekyro",
100
- "version": "0.1.0-rc.5",
101
- "bom-ref": "@sekyro/node@0.1.0-rc.5",
102
- "description": "Sekyro AI server SDK for Node.js — create and read verification sessions, verify signed webhooks. Zero runtime dependencies.",
100
+ "version": "0.1.0-rc.7",
101
+ "bom-ref": "@sekyro/node@0.1.0-rc.7",
102
+ "description": "Sekyro AI server SDK for Node.js — create and read verification sessions, verify signed webhooks and result tokens.",
103
103
  "licenses": [
104
104
  {
105
105
  "license": {
@@ -108,7 +108,7 @@
108
108
  }
109
109
  }
110
110
  ],
111
- "purl": "pkg:npm/%40sekyro/node@0.1.0-rc.5?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2FIBTIKAR-Technologies%2Fibtikar-identity.git%23packages%2Fsekyro-node",
111
+ "purl": "pkg:npm/%40sekyro/node@0.1.0-rc.7?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2FIBTIKAR-Technologies%2Fibtikar-identity.git%23packages%2Fsekyro-node",
112
112
  "externalReferences": [
113
113
  {
114
114
  "url": "https://github.com/IBTIKAR-Technologies/ibtikar-identity/issues",
@@ -144,10 +144,51 @@
144
144
  }
145
145
  ]
146
146
  },
147
- "components": [],
147
+ "components": [
148
+ {
149
+ "type": "library",
150
+ "name": "jose",
151
+ "version": "5.10.0",
152
+ "bom-ref": "@sekyro/node@0.1.0-rc.7|jose@5.10.0",
153
+ "licenses": [
154
+ {
155
+ "license": {
156
+ "id": "MIT",
157
+ "acknowledgement": "declared"
158
+ }
159
+ }
160
+ ],
161
+ "purl": "pkg:npm/jose@5.10.0",
162
+ "externalReferences": [
163
+ {
164
+ "url": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
165
+ "type": "distribution",
166
+ "hashes": [
167
+ {
168
+ "alg": "SHA-512",
169
+ "content": "b3edc097fa7d837d88abea2a5f1916fffee393656283a145d4216acd55e84d45eddaacfcf5859b2fe3b04b5ecd15812fc42df99f414a7863b62c6612c5e3361a"
170
+ }
171
+ ],
172
+ "comment": "as detected from npm-ls property \"resolved\" and property \"integrity\""
173
+ }
174
+ ],
175
+ "properties": [
176
+ {
177
+ "name": "cdx:npm:package:path",
178
+ "value": "node_modules/jose"
179
+ }
180
+ ]
181
+ }
182
+ ],
148
183
  "dependencies": [
149
184
  {
150
- "ref": "@sekyro/node@0.1.0-rc.5"
185
+ "ref": "@sekyro/node@0.1.0-rc.7",
186
+ "dependsOn": [
187
+ "@sekyro/node@0.1.0-rc.7|jose@5.10.0"
188
+ ]
189
+ },
190
+ {
191
+ "ref": "@sekyro/node@0.1.0-rc.7|jose@5.10.0"
151
192
  }
152
193
  ]
153
194
  }