@dismissible/react-client 2.1.0-canary.6.0a7a428 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(l,d){typeof exports=="object"&&typeof module!="undefined"?d(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],d):(l=typeof globalThis!="undefined"?globalThis:l||self,d(l.DismissibleClient={},l.React.jsxRuntime,l.React))})(this,(function(l,d,a){"use strict";var $e=Object.defineProperty,Te=Object.defineProperties;var He=Object.getOwnPropertyDescriptors;var Y=Object.getOwnPropertySymbols;var me=Object.prototype.hasOwnProperty,ye=Object.prototype.propertyIsEnumerable;var te=(l,d,a)=>d in l?$e(l,d,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[d]=a,v=(l,d)=>{for(var a in d||(d={}))me.call(d,a)&&te(l,a,d[a]);if(Y)for(var a of Y(d))ye.call(d,a)&&te(l,a,d[a]);return l},q=(l,d)=>Te(l,He(d));var re=(l,d)=>{var a={};for(var S in l)me.call(l,S)&&d.indexOf(S)<0&&(a[S]=l[S]);if(l!=null&&Y)for(var S of Y(l))d.indexOf(S)<0&&ye.call(l,S)&&(a[S]=l[S]);return a};var N=(l,d,a)=>te(l,typeof d!="symbol"?d+"":d,a);var I=(l,d,a)=>new Promise((S,k)=>{var G=$=>{try{M(a.next($))}catch(L){k(L)}},J=$=>{try{M(a.throw($))}catch(L){k(L)}},M=$=>$.done?S($.value):Promise.resolve($.value).then(G,J);M((a=a.apply(l,d)).next())});const S=(r,t,e)=>{try{const n=`${t}_${r}`,i=localStorage.getItem(n);if(!i)return null;const{data:s,timestamp:o}=JSON.parse(i);return e&&Date.now()-o>e?(localStorage.removeItem(n),null):s}catch(n){return null}},k=(r,t,e)=>{try{const n=`${e}_${r}`,i={data:t,timestamp:Date.now()};localStorage.setItem(n,JSON.stringify(i))}catch(n){console.warn("Failed to cache dismissible item:",n)}},G=(r,t)=>{try{const e=`${t}_${r}`;localStorage.removeItem(e)}catch(e){console.warn("Failed to remove cached dismissible item:",e)}},J=a.createContext(null),M=()=>{const r=a.useContext(J);if(!r)throw new Error("useDismissibleContext must be used within a DismissibleProvider");return r},$="dismissible",L=(r,t={})=>{const{initialData:e,enableCache:n=!0,cachePrefix:i=$,cacheExpiration:s}=t,{userId:o,client:c,baseUrl:m,getAuthHeaders:b,batchScheduler:p}=M(),y=`${o}-${r}`,u=a.useRef({enableCache:n,cachePrefix:i,cacheExpiration:s}),h=a.useRef(r),x=a.useRef(y),A=a.useRef(null),[B,H]=a.useState(!1),[D,j]=a.useState(),[T,U]=a.useState(()=>{if(e)return e;if(n){const f=S(y,i,s);if(f)return f}}),P=a.useCallback(()=>I(null,null,function*(){var C;if(n){const w=S(y,i,s);if(w!=null&&w.dismissedAt){p.primeCache(w),U(w),H(!1);return}}(C=A.current)==null||C.abort();const f=new AbortController;A.current=f,H(!0),j(void 0);try{const w=yield p.getItem(r);if(f.signal.aborted)return;U(w),n&&k(y,w,i)}catch(w){if(w instanceof Error&&w.name==="AbortError"||f.signal.aborted)return;j(w instanceof Error?w:new Error("Unknown error occurred"))}finally{f.signal.aborted||H(!1)}}),[r,y,n,i,s,p]);a.useEffect(()=>{const f=h.current!==r,C=x.current!==y;f||C?(h.current=r,x.current=y,P()):e||P()},[r,y,e,P]),a.useEffect(()=>()=>{var f;(f=A.current)==null||f.abort()},[]),a.useEffect(()=>{const f=u.current;(f.enableCache!==n||f.cachePrefix!==i||f.cacheExpiration!==s)&&(f.cachePrefix!==i&&G(y,f.cachePrefix),!n&&f.enableCache&&G(y,f.cachePrefix),u.current={enableCache:n,cachePrefix:i,cacheExpiration:s},P())},[n,i,s,y,P]);const Z=a.useCallback(()=>I(null,null,function*(){j(void 0);try{const f=yield b(),C=yield c.dismiss({userId:o,itemId:r,baseUrl:m,authHeaders:f});U(C),p.updateCache(C),n&&k(y,C,i)}catch(f){throw j(f instanceof Error?f:new Error("Failed to dismiss item")),f}}),[r,o,y,n,i,c,m,b,p]),W=a.useCallback(()=>I(null,null,function*(){j(void 0);try{const f=yield b(),C=yield c.restore({userId:o,itemId:r,baseUrl:m,authHeaders:f});U(C),p.updateCache(C),n&&k(y,C,i)}catch(f){throw j(f instanceof Error?f:new Error("Failed to restore item")),f}}),[r,o,y,n,i,c,m,b,p]);return{dismissedAt:T==null?void 0:T.dismissedAt,dismiss:Z,restore:W,isLoading:B,error:D,item:T}},be=()=>d.jsx("div",{className:"dismissible-loading","aria-live":"polite",children:"Loading..."}),pe=()=>d.jsx("div",{className:"dismissible-error",role:"alert",children:"Unable to load content. Please try again later."}),we=({onDismiss:r,ariaLabel:t})=>d.jsx("button",{className:"dismissible-button",onClick:r,"aria-label":t,type:"button",children:"×"}),ge=({itemId:r,children:t,onDismiss:e,LoadingComponent:n=be,ErrorComponent:i=pe,DismissButtonComponent:s=we,enableCache:o,cachePrefix:c,cacheExpiration:m,ignoreErrors:b=!1})=>{const{dismissedAt:p,isLoading:y,error:u,dismiss:h}=L(r,{enableCache:o,cachePrefix:c,cacheExpiration:m}),[x,A]=a.useState(!1),[B,H]=a.useState(r);r!==B&&(H(r),A(!1));const D=()=>I(null,null,function*(){A(!0);try{yield h(),e==null||e()}catch(j){A(!1)}});return y&&n?d.jsx(n,{itemId:r}):y&&!n?null:u&&i&&!b?d.jsx(i,{itemId:r,error:u}):p||x?null:d.jsxs("div",{className:"dismissible-container",children:[d.jsx("div",{className:"dismissible-content",children:t}),s?d.jsx(s,{onDismiss:D,ariaLabel:`Dismiss ${r}`}):null]})},se=r=>I(null,null,function*(){if(typeof r=="function")try{const t=r();return yield Promise.resolve(t)}catch(t){console.warn("Failed to resolve JWT from function:",t);return}return r}),ne=r=>I(null,null,function*(){const t=yield se(r);return t?{Authorization:`Bearer ${t}`}:{}}),ve=/\{[^{}]+\}/g,Ce=()=>{var r,t;return typeof process=="object"&&Number.parseInt((t=(r=process==null?void 0:process.versions)==null?void 0:r.node)==null?void 0:t.substring(0,2))>=18&&process.versions.undici};function Re(){return Math.random().toString(36).slice(2,11)}function Ee(r){let y=v({},r),{baseUrl:t="",Request:e=globalThis.Request,fetch:n=globalThis.fetch,querySerializer:i,bodySerializer:s,headers:o,requestInitExt:c=void 0}=y,m=re(y,["baseUrl","Request","fetch","querySerializer","bodySerializer","headers","requestInitExt"]);c=Ce()?c:void 0,t=le(t);const b=[];function p(u,h){return I(this,null,function*(){var he;const de=h||{},{baseUrl:x,fetch:A=n,Request:B=e,headers:H,params:D={},parseAs:j="json",querySerializer:T,bodySerializer:U=s!=null?s:qe,body:P,middleware:Z=[]}=de,W=re(de,["baseUrl","fetch","Request","headers","params","parseAs","querySerializer","bodySerializer","body","middleware"]);let f=t;x&&(f=(he=le(x))!=null?he:t);let C=typeof i=="function"?i:ae(i);T&&(C=typeof T=="function"?T:ae(v(v({},typeof i=="object"?i:{}),T)));const w=P===void 0?void 0:U(P,ce(o,H,D.header)),Ae=ce(w===void 0||w instanceof FormData?{}:{"Content-Type":"application/json"},o,H,D.header),O=[...b,...Z],De=q(v(v({redirect:"follow"},m),W),{body:w,headers:Ae});let Q,V,F=new B(Ie(u,{baseUrl:f,params:D,querySerializer:C}),De),g;for(const E in W)E in F||(F[E]=W[E]);if(O.length){Q=Re(),V=Object.freeze({baseUrl:f,fetch:A,parseAs:j,querySerializer:C,bodySerializer:U});for(const E of O)if(E&&typeof E=="object"&&typeof E.onRequest=="function"){const R=yield E.onRequest({request:F,schemaPath:u,params:D,options:V,id:Q});if(R)if(R instanceof B)F=R;else if(R instanceof Response){g=R;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!g){try{g=yield A(F,c)}catch(E){let R=E;if(O.length)for(let z=O.length-1;z>=0;z--){const X=O[z];if(X&&typeof X=="object"&&typeof X.onError=="function"){const _=yield X.onError({request:F,error:R,schemaPath:u,params:D,options:V,id:Q});if(_){if(_ instanceof Response){R=void 0,g=_;break}if(_ instanceof Error){R=_;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(R)throw R}if(O.length)for(let E=O.length-1;E>=0;E--){const R=O[E];if(R&&typeof R=="object"&&typeof R.onResponse=="function"){const z=yield R.onResponse({request:F,response:g,schemaPath:u,params:D,options:V,id:Q});if(z){if(!(z instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");g=z}}}}if(g.status===204||F.method==="HEAD"||g.headers.get("Content-Length")==="0")return g.ok?{data:void 0,response:g}:{error:void 0,response:g};if(g.ok)return j==="stream"?{data:g.body,response:g}:{data:yield g[j](),response:g};let ee=yield g.text();try{ee=JSON.parse(ee)}catch(E){}return{error:ee,response:g}})}return{request(u,h,x){return p(h,q(v({},x),{method:u.toUpperCase()}))},GET(u,h){return p(u,q(v({},h),{method:"GET"}))},PUT(u,h){return p(u,q(v({},h),{method:"PUT"}))},POST(u,h){return p(u,q(v({},h),{method:"POST"}))},DELETE(u,h){return p(u,q(v({},h),{method:"DELETE"}))},OPTIONS(u,h){return p(u,q(v({},h),{method:"OPTIONS"}))},HEAD(u,h){return p(u,q(v({},h),{method:"HEAD"}))},PATCH(u,h){return p(u,q(v({},h),{method:"PATCH"}))},TRACE(u,h){return p(u,q(v({},h),{method:"TRACE"}))},use(...u){for(const h of u)if(h){if(typeof h!="object"||!("onRequest"in h||"onResponse"in h||"onError"in h))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");b.push(h)}},eject(...u){for(const h of u){const x=b.indexOf(h);x!==-1&&b.splice(x,1)}}}}function K(r,t,e){if(t==null)return"";if(typeof t=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${r}=${(e==null?void 0:e.allowReserved)===!0?t:encodeURIComponent(t)}`}function ie(r,t,e){if(!t||typeof t!="object")return"";const n=[],i={simple:",",label:".",matrix:";"}[e.style]||"&";if(e.style!=="deepObject"&&e.explode===!1){for(const c in t)n.push(c,e.allowReserved===!0?t[c]:encodeURIComponent(t[c]));const o=n.join(",");switch(e.style){case"form":return`${r}=${o}`;case"label":return`.${o}`;case"matrix":return`;${r}=${o}`;default:return o}}for(const o in t){const c=e.style==="deepObject"?`${r}[${o}]`:o;n.push(K(c,t[o],e))}const s=n.join(i);return e.style==="label"||e.style==="matrix"?`${i}${s}`:s}function oe(r,t,e){if(!Array.isArray(t))return"";if(e.explode===!1){const s={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[e.style]||",",o=(e.allowReserved===!0?t:t.map(c=>encodeURIComponent(c))).join(s);switch(e.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${r}=${o}`;default:return`${r}=${o}`}}const n={simple:",",label:".",matrix:";"}[e.style]||"&",i=[];for(const s of t)e.style==="simple"||e.style==="label"?i.push(e.allowReserved===!0?s:encodeURIComponent(s)):i.push(K(r,s,e));return e.style==="label"||e.style==="matrix"?`${n}${i.join(n)}`:i.join(n)}function ae(r){return function(e){const n=[];if(e&&typeof e=="object")for(const i in e){const s=e[i];if(s!=null){if(Array.isArray(s)){if(s.length===0)continue;n.push(oe(i,s,q(v({style:"form",explode:!0},r==null?void 0:r.array),{allowReserved:(r==null?void 0:r.allowReserved)||!1})));continue}if(typeof s=="object"){n.push(ie(i,s,q(v({style:"deepObject",explode:!0},r==null?void 0:r.object),{allowReserved:(r==null?void 0:r.allowReserved)||!1})));continue}n.push(K(i,s,r))}}return n.join("&")}}function Se(r,t){var n;let e=r;for(const i of(n=r.match(ve))!=null?n:[]){let s=i.substring(1,i.length-1),o=!1,c="simple";if(s.endsWith("*")&&(o=!0,s=s.substring(0,s.length-1)),s.startsWith(".")?(c="label",s=s.substring(1)):s.startsWith(";")&&(c="matrix",s=s.substring(1)),!t||t[s]===void 0||t[s]===null)continue;const m=t[s];if(Array.isArray(m)){e=e.replace(i,oe(s,m,{style:c,explode:o}));continue}if(typeof m=="object"){e=e.replace(i,ie(s,m,{style:c,explode:o}));continue}if(c==="matrix"){e=e.replace(i,`;${K(s,m)}`);continue}e=e.replace(i,c==="label"?`.${encodeURIComponent(m)}`:encodeURIComponent(m))}return e}function qe(r,t){var e,n;return r instanceof FormData?r:t&&(t.get instanceof Function?(e=t.get("Content-Type"))!=null?e:t.get("content-type"):(n=t["Content-Type"])!=null?n:t["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(r).toString():JSON.stringify(r)}function Ie(r,t){var i,s;let e=`${t.baseUrl}${r}`;(i=t.params)!=null&&i.path&&(e=Se(e,t.params.path));let n=t.querySerializer((s=t.params.query)!=null?s:{});return n.startsWith("?")&&(n=n.substring(1)),n&&(e+=`?${n}`),e}function ce(...r){const t=new Headers;for(const e of r){if(!e||typeof e!="object")continue;const n=e instanceof Headers?e.entries():Object.entries(e);for(const[i,s]of n)if(s===null)t.delete(i);else if(Array.isArray(s))for(const o of s)t.append(i,o);else s!==void 0&&t.set(i,s)}return t}function le(r){return r.endsWith("/")?r.substring(0,r.length-1):r}const ue=r=>{const t=Ee({baseUrl:r,headers:{}});return{getOrCreate:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s,signal:o}=e,{data:c,error:m}=yield t.GET("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s,signal:o});if(m||!c)throw new Error("Failed to fetch dismissible item");return c.data}),batchGetOrCreate:e=>I(null,null,function*(){const{userId:n,itemIds:i,authHeaders:s,signal:o}=e,{data:c,error:m}=yield t.POST("/v1/users/{userId}/items",{params:{path:{userId:n}},body:{items:i},headers:s,signal:o});if(m||!c)throw new Error("Failed to batch fetch dismissible items");return c.data}),dismiss:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s}=e,{data:o,error:c}=yield t.DELETE("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s});if(c||!o)throw new Error("Failed to dismiss item");return o.data}),restore:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s}=e,{data:o,error:c}=yield t.POST("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s});if(c||!o)throw new Error("Failed to restore item");return o.data})}},xe=r=>{try{const t=new URL(r),e=t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="[::1]",n=t.protocol==="https:";return{isSecure:n||e,isLocalhost:e,isHttps:n}}catch(t){return{isSecure:!1,isLocalhost:!1,isHttps:!1}}};class fe{constructor(t){N(this,"config");N(this,"pendingRequests",[]);N(this,"isScheduled",!1);N(this,"cache",new Map);N(this,"inFlightRequests",new Map);var e;this.config=q(v({},t),{maxBatchSize:(e=t.maxBatchSize)!=null?e:50})}getItem(t){const e=this.cache.get(t);if(e)return Promise.resolve(e);const n=this.inFlightRequests.get(t);if(n)return n;let i,s;const o=new Promise((c,m)=>{i=c,s=m});return this.pendingRequests.push({itemId:t,resolve:i,reject:s}),this.inFlightRequests.set(t,o),o.catch(()=>{}).finally(()=>{this.inFlightRequests.delete(t)}),this.isScheduled||(this.isScheduled=!0,queueMicrotask(()=>this.executeBatch())),o}primeCache(t){this.cache.set(t.itemId,t)}updateCache(t){this.cache.set(t.itemId,t)}clearCache(){this.cache.clear()}executeBatch(){return I(this,null,function*(){this.isScheduled=!1;const t=this.pendingRequests;if(this.pendingRequests=[],t.length===0)return;const e=new Map;for(const s of t){const o=e.get(s.itemId);o?o.push(s):e.set(s.itemId,[s])}const n=Array.from(e.keys()),i=[];for(let s=0;s<n.length;s+=this.config.maxBatchSize)i.push(n.slice(s,s+this.config.maxBatchSize));try{const s=yield this.config.getAuthHeaders(),c=(yield Promise.all(i.map(b=>this.config.client.batchGetOrCreate({userId:this.config.userId,itemIds:b,baseUrl:this.config.baseUrl,authHeaders:s})))).flat(),m=new Map;for(const b of c)m.set(b.itemId,b),this.cache.set(b.itemId,b);for(const[b,p]of e){const y=m.get(b);if(y)for(const u of p)u.resolve(y);else{const u=new Error(`Item ${b} not found in batch response`);for(const h of p)h.reject(u)}}}catch(s){const o=s instanceof Error?s:new Error("Batch request failed");for(const c of t)c.reject(o)}})}}const je=({userId:r,jwt:t,baseUrl:e,client:n,children:i})=>{a.useEffect(()=>{const{isSecure:b}=xe(e);b||console.warn(`[dismissible] Insecure baseUrl "${e}". Use https:// in production (or localhost for development). JWT tokens may be exposed over insecure connections.`)},[e]);const s=a.useMemo(()=>n!=null?n:ue(e),[n,e]),o=a.useMemo(()=>()=>I(null,null,function*(){return yield ne(t)}),[t]),c=a.useMemo(()=>new fe({userId:r,baseUrl:e,client:s,getAuthHeaders:o}),[r,e,s,o]),m=a.useMemo(()=>({userId:r,jwt:t,baseUrl:e,getAuthHeaders:o,client:s,batchScheduler:c}),[r,t,e,o,s,c]);return d.jsx(J.Provider,{value:m,children:i})};l.BatchScheduler=fe,l.Dismissible=ge,l.DismissibleContext=J,l.DismissibleProvider=je,l.createDefaultClient=ue,l.getAuthHeaders=ne,l.resolveJwt=se,l.useDismissibleContext=M,l.useDismissibleItem=L,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(l,h){typeof exports=="object"&&typeof module!="undefined"?h(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],h):(l=typeof globalThis!="undefined"?globalThis:l||self,h(l.DismissibleClient={},l.React.jsxRuntime,l.React))})(this,(function(l,h,a){"use strict";var $e=Object.defineProperty,Te=Object.defineProperties;var Ie=Object.getOwnPropertyDescriptors;var X=Object.getOwnPropertySymbols;var de=Object.prototype.hasOwnProperty,he=Object.prototype.propertyIsEnumerable;var fe=(l,h,a)=>h in l?$e(l,h,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[h]=a,w=(l,h)=>{for(var a in h||(h={}))de.call(h,a)&&fe(l,a,h[a]);if(X)for(var a of X(h))he.call(h,a)&&fe(l,a,h[a]);return l},A=(l,h)=>Te(l,Ie(h));var ee=(l,h)=>{var a={};for(var R in l)de.call(l,R)&&h.indexOf(R)<0&&(a[R]=l[R]);if(l!=null&&X)for(var R of X(l))h.indexOf(R)<0&&he.call(l,R)&&(a[R]=l[R]);return a};var j=(l,h,a)=>new Promise((R,z)=>{var _=x=>{try{F(a.next(x))}catch(L){z(L)}},J=x=>{try{F(a.throw(x))}catch(L){z(L)}},F=x=>x.done?R(x.value):Promise.resolve(x.value).then(_,J);F((a=a.apply(l,h)).next())});const R=(t,r,e)=>{try{const s=`${r}_${t}`,i=localStorage.getItem(s);if(!i)return null;const{data:n,timestamp:o}=JSON.parse(i);return e&&Date.now()-o>e?(localStorage.removeItem(s),null):n}catch(s){return null}},z=(t,r,e)=>{try{const s=`${e}_${t}`,i={data:r,timestamp:Date.now()};localStorage.setItem(s,JSON.stringify(i))}catch(s){console.warn("Failed to cache dismissible item:",s)}},_=(t,r)=>{try{const e=`${r}_${t}`;localStorage.removeItem(e)}catch(e){console.warn("Failed to remove cached dismissible item:",e)}},J=a.createContext(null),F=()=>{const t=a.useContext(J);if(!t)throw new Error("useDismissibleContext must be used within a DismissibleProvider");return t},x="dismissible",L=(t,r={})=>{const{initialData:e,enableCache:s=!0,cachePrefix:i=x,cacheExpiration:n}=r,o=F(),{userId:c,client:y,baseUrl:S}=o,m=`${c}-${t}`,I=a.useRef({enableCache:s,cachePrefix:i,cacheExpiration:n}),d=a.useRef(t),f=a.useRef(m),D=a.useRef(null),[H,q]=a.useState(!1),[N,v]=a.useState(),[$,T]=a.useState(()=>{if(e)return e;if(s){const u=R(m,i,n);if(u)return u}}),U=a.useCallback(()=>j(null,null,function*(){var C;if(s){const p=R(m,i,n);if(p!=null&&p.dismissedAt){T(p),q(!1);return}}(C=D.current)==null||C.abort();const u=new AbortController;D.current=u,q(!0),v(void 0);try{const p=yield o.getAuthHeaders(),M=yield y.getOrCreate({userId:c,itemId:t,baseUrl:S,authHeaders:p,signal:u.signal});T(M),s&&z(m,M,i)}catch(p){if(p instanceof Error&&p.name==="AbortError")return;v(p instanceof Error?p:new Error("Unknown error occurred"))}finally{q(!1)}}),[t,c,m,s,i,n,y,S,o]);a.useEffect(()=>{const u=d.current!==t,C=f.current!==m;u||C?(d.current=t,f.current=m,U()):e||U()},[t,m,e,U]),a.useEffect(()=>()=>{var u;(u=D.current)==null||u.abort()},[]),a.useEffect(()=>{const u=I.current;(u.enableCache!==s||u.cachePrefix!==i||u.cacheExpiration!==n)&&(u.cachePrefix!==i&&_(m,u.cachePrefix),!s&&u.enableCache&&_(m,u.cachePrefix),I.current={enableCache:s,cachePrefix:i,cacheExpiration:n},U())},[s,i,n,m,U]);const G=a.useCallback(()=>j(null,null,function*(){v(void 0);try{const u=yield o.getAuthHeaders(),C=yield y.dismiss({userId:c,itemId:t,baseUrl:S,authHeaders:u});T(C),s&&z(m,C,i)}catch(u){throw v(u instanceof Error?u:new Error("Failed to dismiss item")),u}}),[t,c,m,s,i,y,S,o]),Y=a.useCallback(()=>j(null,null,function*(){v(void 0);try{const u=yield o.getAuthHeaders(),C=yield y.restore({userId:c,itemId:t,baseUrl:S,authHeaders:u});T(C),s&&z(m,C,i)}catch(u){throw v(u instanceof Error?u:new Error("Failed to restore item")),u}}),[t,c,m,s,i,y,S,o]);return{dismissedAt:$==null?void 0:$.dismissedAt,dismiss:G,restore:Y,isLoading:H,error:N,item:$}},me=()=>h.jsx("div",{className:"dismissible-loading","aria-live":"polite",children:"Loading..."}),ye=()=>h.jsx("div",{className:"dismissible-error",role:"alert",children:"Unable to load content. Please try again later."}),be=({onDismiss:t,ariaLabel:r})=>h.jsx("button",{className:"dismissible-button",onClick:t,"aria-label":r,type:"button",children:"×"}),we=({itemId:t,children:r,onDismiss:e,LoadingComponent:s=me,ErrorComponent:i=ye,DismissButtonComponent:n=be,enableCache:o,cachePrefix:c,cacheExpiration:y,ignoreErrors:S=!1})=>{const{dismissedAt:m,isLoading:I,error:d,dismiss:f}=L(t,{enableCache:o,cachePrefix:c,cacheExpiration:y}),[D,H]=a.useState(!1),[q,N]=a.useState(t);t!==q&&(N(t),H(!1));const v=()=>j(null,null,function*(){H(!0);try{yield f(),e==null||e()}catch($){H(!1)}});return I&&s?h.jsx(s,{itemId:t}):I&&!s?null:d&&i&&!S?h.jsx(i,{itemId:t,error:d}):m||D?null:h.jsxs("div",{className:"dismissible-container",children:[h.jsx("div",{className:"dismissible-content",children:r}),n?h.jsx(n,{onDismiss:v,ariaLabel:`Dismiss ${t}`}):null]})},te=t=>j(null,null,function*(){if(typeof t=="function")try{const r=t();return yield Promise.resolve(r)}catch(r){console.warn("Failed to resolve JWT from function:",r);return}return t}),re=t=>j(null,null,function*(){const r=yield te(t);return r?{Authorization:`Bearer ${r}`}:{}}),pe=/\{[^{}]+\}/g,ge=()=>{var t,r;return typeof process=="object"&&Number.parseInt((r=(t=process==null?void 0:process.versions)==null?void 0:t.node)==null?void 0:r.substring(0,2))>=18&&process.versions.undici};function ve(){return Math.random().toString(36).slice(2,11)}function Ce(t){let I=w({},t),{baseUrl:r="",Request:e=globalThis.Request,fetch:s=globalThis.fetch,querySerializer:i,bodySerializer:n,headers:o,requestInitExt:c=void 0}=I,y=ee(I,["baseUrl","Request","fetch","querySerializer","bodySerializer","headers","requestInitExt"]);c=ge()?c:void 0,r=ae(r);const S=[];function m(d,f){return j(this,null,function*(){var ue;const le=f||{},{baseUrl:D,fetch:H=s,Request:q=e,headers:N,params:v={},parseAs:$="json",querySerializer:T,bodySerializer:U=n!=null?n:Re,body:G,middleware:Y=[]}=le,u=ee(le,["baseUrl","fetch","Request","headers","params","parseAs","querySerializer","bodySerializer","body","middleware"]);let C=r;D&&(C=(ue=ae(D))!=null?ue:r);let p=typeof i=="function"?i:ie(i);T&&(p=typeof T=="function"?T:ie(w(w({},typeof i=="object"?i:{}),T)));const M=G===void 0?void 0:U(G,oe(o,N,v.header)),je=oe(M===void 0||M instanceof FormData?{}:{"Content-Type":"application/json"},o,N,v.header),P=[...S,...Y],xe=A(w(w({redirect:"follow"},y),u),{body:M,headers:je});let K,Q,O=new q(Se(d,{baseUrl:C,params:v,querySerializer:p}),xe),b;for(const E in u)E in O||(O[E]=u[E]);if(P.length){K=ve(),Q=Object.freeze({baseUrl:C,fetch:H,parseAs:$,querySerializer:p,bodySerializer:U});for(const E of P)if(E&&typeof E=="object"&&typeof E.onRequest=="function"){const g=yield E.onRequest({request:O,schemaPath:d,params:v,options:Q,id:K});if(g)if(g instanceof q)O=g;else if(g instanceof Response){b=g;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!b){try{b=yield H(O,c)}catch(E){let g=E;if(P.length)for(let k=P.length-1;k>=0;k--){const V=P[k];if(V&&typeof V=="object"&&typeof V.onError=="function"){const W=yield V.onError({request:O,error:g,schemaPath:d,params:v,options:Q,id:K});if(W){if(W instanceof Response){g=void 0,b=W;break}if(W instanceof Error){g=W;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(g)throw g}if(P.length)for(let E=P.length-1;E>=0;E--){const g=P[E];if(g&&typeof g=="object"&&typeof g.onResponse=="function"){const k=yield g.onResponse({request:O,response:b,schemaPath:d,params:v,options:Q,id:K});if(k){if(!(k instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");b=k}}}}if(b.status===204||O.method==="HEAD"||b.headers.get("Content-Length")==="0")return b.ok?{data:void 0,response:b}:{error:void 0,response:b};if(b.ok)return $==="stream"?{data:b.body,response:b}:{data:yield b[$](),response:b};let Z=yield b.text();try{Z=JSON.parse(Z)}catch(E){}return{error:Z,response:b}})}return{request(d,f,D){return m(f,A(w({},D),{method:d.toUpperCase()}))},GET(d,f){return m(d,A(w({},f),{method:"GET"}))},PUT(d,f){return m(d,A(w({},f),{method:"PUT"}))},POST(d,f){return m(d,A(w({},f),{method:"POST"}))},DELETE(d,f){return m(d,A(w({},f),{method:"DELETE"}))},OPTIONS(d,f){return m(d,A(w({},f),{method:"OPTIONS"}))},HEAD(d,f){return m(d,A(w({},f),{method:"HEAD"}))},PATCH(d,f){return m(d,A(w({},f),{method:"PATCH"}))},TRACE(d,f){return m(d,A(w({},f),{method:"TRACE"}))},use(...d){for(const f of d)if(f){if(typeof f!="object"||!("onRequest"in f||"onResponse"in f||"onError"in f))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");S.push(f)}},eject(...d){for(const f of d){const D=S.indexOf(f);D!==-1&&S.splice(D,1)}}}}function B(t,r,e){if(r==null)return"";if(typeof r=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${(e==null?void 0:e.allowReserved)===!0?r:encodeURIComponent(r)}`}function se(t,r,e){if(!r||typeof r!="object")return"";const s=[],i={simple:",",label:".",matrix:";"}[e.style]||"&";if(e.style!=="deepObject"&&e.explode===!1){for(const c in r)s.push(c,e.allowReserved===!0?r[c]:encodeURIComponent(r[c]));const o=s.join(",");switch(e.style){case"form":return`${t}=${o}`;case"label":return`.${o}`;case"matrix":return`;${t}=${o}`;default:return o}}for(const o in r){const c=e.style==="deepObject"?`${t}[${o}]`:o;s.push(B(c,r[o],e))}const n=s.join(i);return e.style==="label"||e.style==="matrix"?`${i}${n}`:n}function ne(t,r,e){if(!Array.isArray(r))return"";if(e.explode===!1){const n={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[e.style]||",",o=(e.allowReserved===!0?r:r.map(c=>encodeURIComponent(c))).join(n);switch(e.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${t}=${o}`;default:return`${t}=${o}`}}const s={simple:",",label:".",matrix:";"}[e.style]||"&",i=[];for(const n of r)e.style==="simple"||e.style==="label"?i.push(e.allowReserved===!0?n:encodeURIComponent(n)):i.push(B(t,n,e));return e.style==="label"||e.style==="matrix"?`${s}${i.join(s)}`:i.join(s)}function ie(t){return function(e){const s=[];if(e&&typeof e=="object")for(const i in e){const n=e[i];if(n!=null){if(Array.isArray(n)){if(n.length===0)continue;s.push(ne(i,n,A(w({style:"form",explode:!0},t==null?void 0:t.array),{allowReserved:(t==null?void 0:t.allowReserved)||!1})));continue}if(typeof n=="object"){s.push(se(i,n,A(w({style:"deepObject",explode:!0},t==null?void 0:t.object),{allowReserved:(t==null?void 0:t.allowReserved)||!1})));continue}s.push(B(i,n,t))}}return s.join("&")}}function Ee(t,r){var s;let e=t;for(const i of(s=t.match(pe))!=null?s:[]){let n=i.substring(1,i.length-1),o=!1,c="simple";if(n.endsWith("*")&&(o=!0,n=n.substring(0,n.length-1)),n.startsWith(".")?(c="label",n=n.substring(1)):n.startsWith(";")&&(c="matrix",n=n.substring(1)),!r||r[n]===void 0||r[n]===null)continue;const y=r[n];if(Array.isArray(y)){e=e.replace(i,ne(n,y,{style:c,explode:o}));continue}if(typeof y=="object"){e=e.replace(i,se(n,y,{style:c,explode:o}));continue}if(c==="matrix"){e=e.replace(i,`;${B(n,y)}`);continue}e=e.replace(i,c==="label"?`.${encodeURIComponent(y)}`:encodeURIComponent(y))}return e}function Re(t,r){var e,s;return t instanceof FormData?t:r&&(r.get instanceof Function?(e=r.get("Content-Type"))!=null?e:r.get("content-type"):(s=r["Content-Type"])!=null?s:r["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(t).toString():JSON.stringify(t)}function Se(t,r){var i,n;let e=`${r.baseUrl}${t}`;(i=r.params)!=null&&i.path&&(e=Ee(e,r.params.path));let s=r.querySerializer((n=r.params.query)!=null?n:{});return s.startsWith("?")&&(s=s.substring(1)),s&&(e+=`?${s}`),e}function oe(...t){const r=new Headers;for(const e of t){if(!e||typeof e!="object")continue;const s=e instanceof Headers?e.entries():Object.entries(e);for(const[i,n]of s)if(n===null)r.delete(i);else if(Array.isArray(n))for(const o of n)r.append(i,o);else n!==void 0&&r.set(i,n)}return r}function ae(t){return t.endsWith("/")?t.substring(0,t.length-1):t}const ce=t=>{const r=Ce({baseUrl:t,headers:{}});return{getOrCreate:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n,signal:o}=e,{data:c,error:y}=yield r.GET("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n,signal:o});if(y||!c)throw new Error("Failed to fetch dismissible item");return c.data}),dismiss:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n}=e,{data:o,error:c}=yield r.DELETE("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n});if(c||!o)throw new Error("Failed to dismiss item");return o.data}),restore:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n}=e,{data:o,error:c}=yield r.POST("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n});if(c||!o)throw new Error("Failed to restore item");return o.data})}},De=t=>{try{const r=new URL(t),e=r.hostname==="localhost"||r.hostname==="127.0.0.1"||r.hostname==="[::1]",s=r.protocol==="https:";return{isSecure:s||e,isLocalhost:e,isHttps:s}}catch(r){return{isSecure:!1,isLocalhost:!1,isHttps:!1}}},Ae=({userId:t,jwt:r,baseUrl:e,client:s,children:i})=>{const{isSecure:n}=De(e);n||console.warn(`[dismissible] Insecure baseUrl "${e}". Use https:// in production (or localhost for development). JWT tokens may be exposed over insecure connections.`);const o=a.useMemo(()=>s!=null?s:ce(e),[s,e]),c=a.useMemo(()=>({userId:t,jwt:r,baseUrl:e,getAuthHeaders:()=>j(null,null,function*(){return yield re(r)}),client:o}),[t,r,e,o]);return h.jsx(J.Provider,{value:c,children:i})};l.Dismissible=we,l.DismissibleContext=J,l.DismissibleProvider=Ae,l.createDefaultClient=ce,l.getAuthHeaders=re,l.resolveJwt=te,l.useDismissibleContext=F,l.useDismissibleItem=L,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/root.d.ts
CHANGED
|
@@ -53,21 +53,6 @@ export type DismissClientParams = BaseDismissibleClientParams;
|
|
|
53
53
|
* Parameters for the restore client method
|
|
54
54
|
*/
|
|
55
55
|
export type RestoreClientParams = BaseDismissibleClientParams;
|
|
56
|
-
/**
|
|
57
|
-
* Parameters for the batchGetOrCreate client method
|
|
58
|
-
*/
|
|
59
|
-
export interface BatchGetOrCreateClientParams {
|
|
60
|
-
/** User ID for the current user */
|
|
61
|
-
userId: string;
|
|
62
|
-
/** Array of dismissible item IDs to fetch (max 50) */
|
|
63
|
-
itemIds: string[];
|
|
64
|
-
/** Base URL for API requests */
|
|
65
|
-
baseUrl: string;
|
|
66
|
-
/** Authentication headers */
|
|
67
|
-
authHeaders: AuthHeaders;
|
|
68
|
-
/** AbortSignal for request cancellation */
|
|
69
|
-
signal?: AbortSignal;
|
|
70
|
-
}
|
|
71
56
|
/**
|
|
72
57
|
* Interface for custom HTTP clients
|
|
73
58
|
*
|
|
@@ -92,8 +77,6 @@ export interface BatchGetOrCreateClientParams {
|
|
|
92
77
|
export interface DismissibleClient {
|
|
93
78
|
/** Get or create a dismissible item */
|
|
94
79
|
getOrCreate: (params: GetOrCreateClientParams) => Promise<DismissibleItem>;
|
|
95
|
-
/** Batch get or create multiple dismissible items (max 50) */
|
|
96
|
-
batchGetOrCreate: (params: BatchGetOrCreateClientParams) => Promise<DismissibleItem[]>;
|
|
97
80
|
/** Dismiss an item */
|
|
98
81
|
dismiss: (params: DismissClientParams) => Promise<DismissibleItem>;
|
|
99
82
|
/** Restore a dismissed item */
|
|
@@ -114,19 +97,6 @@ export interface DismissibleProviderProps {
|
|
|
114
97
|
/** Child components */
|
|
115
98
|
children: React.ReactNode;
|
|
116
99
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Batch scheduler interface for request coalescing
|
|
119
|
-
*/
|
|
120
|
-
export interface IBatchScheduler {
|
|
121
|
-
/** Request a dismissible item (batched with other requests in same tick) */
|
|
122
|
-
getItem: (itemId: string) => Promise<DismissibleItem>;
|
|
123
|
-
/** Pre-populate cache with an item */
|
|
124
|
-
primeCache: (item: DismissibleItem) => void;
|
|
125
|
-
/** Update an item in the cache */
|
|
126
|
-
updateCache: (item: DismissibleItem) => void;
|
|
127
|
-
/** Clear the in-memory cache */
|
|
128
|
-
clearCache: () => void;
|
|
129
|
-
}
|
|
130
100
|
/**
|
|
131
101
|
* Context value provided by DismissibleProvider
|
|
132
102
|
*/
|
|
@@ -141,6 +111,4 @@ export interface DismissibleContextValue {
|
|
|
141
111
|
getAuthHeaders: () => Promise<AuthHeaders>;
|
|
142
112
|
/** The HTTP client to use for API requests */
|
|
143
113
|
client: DismissibleClient;
|
|
144
|
-
/** Batch scheduler for coalescing getOrCreate requests */
|
|
145
|
-
batchScheduler: IBatchScheduler;
|
|
146
114
|
}
|
package/package.json
CHANGED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { DismissibleClient, DismissibleItem, AuthHeaders } from '../types/dismissible.types';
|
|
2
|
-
/**
|
|
3
|
-
* Configuration for the BatchScheduler
|
|
4
|
-
*/
|
|
5
|
-
export interface BatchSchedulerConfig {
|
|
6
|
-
/** User ID for API requests */
|
|
7
|
-
userId: string;
|
|
8
|
-
/** Base URL for API requests */
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
/** The HTTP client to use */
|
|
11
|
-
client: DismissibleClient;
|
|
12
|
-
/** Function to get auth headers */
|
|
13
|
-
getAuthHeaders: () => Promise<AuthHeaders>;
|
|
14
|
-
/** Maximum items per batch (API limit is 50) */
|
|
15
|
-
maxBatchSize?: number;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* BatchScheduler implements DataLoader-style request batching.
|
|
19
|
-
*
|
|
20
|
-
* When multiple components request items in the same JavaScript tick,
|
|
21
|
-
* the scheduler coalesces them into a single batch API call using
|
|
22
|
-
* queueMicrotask to defer execution until after synchronous code completes.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const scheduler = new BatchScheduler({
|
|
27
|
-
* userId: "user-123",
|
|
28
|
-
* baseUrl: "https://api.example.com",
|
|
29
|
-
* client: dismissibleClient,
|
|
30
|
-
* getAuthHeaders: async () => ({ Authorization: "Bearer ..." }),
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* // These three calls in the same tick will be batched into one API call
|
|
34
|
-
* const [item1, item2, item3] = await Promise.all([
|
|
35
|
-
* scheduler.getItem("item-1"),
|
|
36
|
-
* scheduler.getItem("item-2"),
|
|
37
|
-
* scheduler.getItem("item-3"),
|
|
38
|
-
* ]);
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export declare class BatchScheduler {
|
|
42
|
-
private config;
|
|
43
|
-
private pendingRequests;
|
|
44
|
-
private isScheduled;
|
|
45
|
-
private cache;
|
|
46
|
-
private inFlightRequests;
|
|
47
|
-
constructor(config: BatchSchedulerConfig);
|
|
48
|
-
/**
|
|
49
|
-
* Request a dismissible item. If called multiple times in the same
|
|
50
|
-
* JavaScript tick, requests will be batched into a single API call.
|
|
51
|
-
*
|
|
52
|
-
* @param itemId - The item ID to fetch
|
|
53
|
-
* @returns Promise resolving to the DismissibleItem
|
|
54
|
-
*/
|
|
55
|
-
getItem(itemId: string): Promise<DismissibleItem>;
|
|
56
|
-
/**
|
|
57
|
-
* Pre-populate the cache with items (e.g., from localStorage)
|
|
58
|
-
*/
|
|
59
|
-
primeCache(item: DismissibleItem): void;
|
|
60
|
-
/**
|
|
61
|
-
* Update an item in the cache (e.g., after dismiss/restore)
|
|
62
|
-
*/
|
|
63
|
-
updateCache(item: DismissibleItem): void;
|
|
64
|
-
/**
|
|
65
|
-
* Clear the in-memory cache
|
|
66
|
-
*/
|
|
67
|
-
clearCache(): void;
|
|
68
|
-
/**
|
|
69
|
-
* Execute the batched requests
|
|
70
|
-
*/
|
|
71
|
-
private executeBatch;
|
|
72
|
-
}
|