/** Server-side Partner API v1. Never ship a connection secret to a browser. */ import {createHmac, timingSafeEqual} from 'node:crypto'; export type Capability='sso'|'services'|'subscriptions'|'invoices'|'products'|'orders'; export type PartnerEvent={version:1;id:string;type:string;entityId:string;revision:number;occurredAt:string;data:unknown}; export function signature(raw:string|Buffer,key:string,seconds=Math.floor(Date.now()/1000)){ return `t=${seconds},v1=${createHmac('sha256',key).update(seconds+'.').update(raw).digest('hex')}`; } export function verify(raw:string|Buffer,header:string,key:string,now=Math.floor(Date.now()/1000)){ const m=/^t=(\d{10}),v1=([a-f0-9]{64})$/.exec(header);if(!m||Math.abs(now-Number(m[1]))>300)return false; const digest=createHmac('sha256',key).update(m[1]+'.').update(raw).digest();return timingSafeEqual(digest,Buffer.from(m[2],'hex')); } export class NazPartner { constructor(private connectionId:string,private key:string,private origin='https://sso.naz.sa'){ if(new URL(origin).protocol!=='https:')throw Error('HTTPS required'); } async publish(event:PartnerEvent){ const body=JSON.stringify(event),r=await fetch(this.origin+'/api/v1/partner/events',{method:'POST',redirect:'error',signal:AbortSignal.timeout(15000),headers:{'Content-Type':'application/json','X-Naz-Connection':this.connectionId,'X-Naz-Signature':signature(body,this.key)},body}); if(!r.ok)throw Error(`NAZ HTTP ${r.status}`);return r.json() as Promise<{status:'applied'|'stale'|'rejected';reason?:string;duplicate?:boolean}>; } }