@catdoes/watch 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{Platform as t,Dimensions as e,AppState as s}from"react-native";import i from"react";import{jsx as n}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function o(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let e=5381;for(let s=0;s<t.length;s++)e=(e<<5)+e^t.charCodeAt(s);return(e>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const e=Math.max(1,this.config.maxBufferSize),s=t?.keepalive?Math.min(e,10):e,i=this.queue.slice(0,s);try{await this.doFlush(i,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(i.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,e){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const s=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:s};e?.keepalive&&(t.keepalive=!0);const i=await fetch(this.config.endpoint,t);if(401===i.status||403===i.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:i.status}),new Error("Invalid API key");if(429===i.status){const t=i.headers.get("Retry-After"),e=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=e,this.backoffUntil=Date.now()+e,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${e}ms`),new Error("Rate limited")}if(!i.ok)throw this.applyBackoff(),new Error(`HTTP ${i.status}`);const n=await i.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${n.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,e){this.disabledReason=t,this.disabledUntil=e===1/0?1/0:Date.now()+Math.max(0,e)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const e={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(e))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let e=null;try{e=await t.getItem(this.storageKey)}catch{return}if(e)try{const s=JSON.parse(e);let i=[];if(Array.isArray(s))i=s.filter(o);else if(r(s)&&1===s.v&&Array.isArray(s.events)){const e=s.apiKeyHash;if(e&&e!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}i=s.events.filter(o)}if(0===i.length)return void await t.removeItem(this.storageKey);this.queue=[...i,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${i.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(){return a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let s="";for(let i=0;i<8;i++)s+=t.charAt(e[i]%36);return s}let e="";for(let s=0;s<8;s++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),a}function l(){a=null}function u(t){a=t}function d(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}function f(){const e=t.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function p(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function y(){const s=d("ExpoDevice"),i=d("ExponentConstants"),n=d("ExpoLocalization"),r=f(),o={};try{const{width:t,height:s,scale:i}=e.get("window");o.screenWidth=Math.round(t),o.screenHeight=Math.round(s),o.screenScale=i}catch{}if(o.osName=t.OS,t.Version&&(o.osVersion=String(t.Version)),s&&"web"!==r)try{s.brand&&(o.brand=s.brand),s.manufacturer&&(o.manufacturer=s.manufacturer),s.modelName&&(o.modelName=s.modelName),s.deviceName&&(o.deviceName=s.deviceName),s.osName&&(o.osName=s.osName),s.osVersion&&(o.osVersion=s.osVersion),s.osBuildId&&(o.osBuildId=s.osBuildId),s.platformApiLevel&&(o.platformApiLevel=s.platformApiLevel),"boolean"==typeof s.isDevice&&(o.isDevice=s.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(o.appVersion=t.version),i.expoVersion&&(o.expoVersion=i.expoVersion),t?.name&&(o.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?o.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(o.bundleId=t.android.package)}catch{}if(n)try{const t=n.getLocales?.()?.[0]?.languageTag;t&&(o.locale=t);const e=n.getCalendars?.()?.[0]?.timeZone;e&&(o.timezone=e)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){o.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(o.browserName=e[1],o.browserVersion=e[2])}}catch{}return o}var w=null;function m(){return w||(w=y()),w}function g(){w=null}var v=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const e={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(e),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function b(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function C(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function D(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}}function E(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function _(t,e){return{type:"custom",message:t,data:e}}var W="1.2.0";function $(t){const e=function(t){if(!t)return t;let e=t;const s=e.indexOf("?");s>=0&&(e=e.slice(0,s));const i=e.indexOf("&");for(i>=0&&(e=e.slice(0,i));e.endsWith("/")&&e.length>1;)e=e.slice(0,-1);return e}(t),s=["/app/","/src/","/components/","/screens/"];for(const t of s){const s=e.indexOf(t);if(s>=0)return k(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return k(e)}function S(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function k(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let e=t.replace(/\/{2,}/g,"/");return e.startsWith("./")&&(e=e.slice(2)),e}var I=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||p(),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new v(this.config.maxBreadcrumbs);try{m()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,e){if(!this.shouldCapture())return;let s;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){s=t.stack}const i=this.ensureStack(t,s),n=this.getErrorKey(i);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(n);const r=this.buildEvent(i,e),o=this.config.beforeSend(r);o?this.transport.send(o):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,e="error"){if(!this.shouldCapture())return;const s=new Error(t);if(s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(s,{level:e,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,e){this.context[t]=e}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){let s={};try{s=m()}catch{}const i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:f(),sessionId:c(),deviceInfo:s,sdkVersion:W,extra:{...this.context,...e,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let s=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(s||(s=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),s){const t=$(s[1]);t&&S(t)&&(i.filename=t),i.lineno=parseInt(s[2],10),i.colno=parseInt(s[3],10)}void 0===i.lineno&&"number"==typeof e?.lineno&&(i.lineno=e.lineno),void 0===i.colno&&"number"==typeof e?.colno&&(i.colno=e.colno)}return i}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,e){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(e&&e.length>100&&/:\d+:\d+/.test(e)){const s=e.split("\n"),i=[];i.push(`${t.name}: ${t.message}`);let n=!1;for(const t of s){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const e=t.trim();e&&(e.includes("@")||e.startsWith("at "))&&(i.push(e),n=!0)}if(n&&i.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=i.join("\n"),Object.assign(e,t),e}}try{throw t}catch(t){const e=t;if(e.stack&&e.stack.length>100&&/:\d+:\d+/.test(e.stack))return e}return t}getErrorKey(t){const e=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${e}`}markErrorAsSeen(t){const e=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,e),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const e=this.recentErrors.get(t);if(null==e)return!1;const s=Date.now()-e<=this.config.dedupWindowMs;return s||this.recentErrors.delete(t),s}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),e=this.config.dedupWindowMs;for(const[s,i]of this.recentErrors.entries())t-i>e&&this.recentErrors.delete(s)}};I.instance=null;var M=I,x={init:t=>M.init(t),getInstance:()=>M.getInstance(),captureError(t,e){M.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){M.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){M.getInstance()?.addBreadcrumb(t)},setContext(t,e){M.getInstance()?.setContext(t,e)},setUser(t){M.getInstance()?.setUser(t)},flush:t=>M.getInstance()?.flush(t)||Promise.resolve()},A=!1,T=null,P=null,j=null,R=null,U=null,B=null;function q(e){A?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.OS?function(t){"undefined"!=typeof window&&(T=window.onerror,window.onerror=(e,s,i,n,r)=>{T&&T(e,s,i,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&s&&(o.filename=s,o.lineno=i,o.colno=n),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:n}),!1},U=e=>{if(e.defaultPrevented)return;const s=e.reason instanceof Error?e.reason:new Error(String(e.reason??"Unhandled Promise rejection"));t.captureError(s,{source:"global.unhandledrejection",isPromiseRejection:!0})},window.addEventListener("unhandledrejection",U),j=()=>{t.flush({keepalive:!0}).catch(()=>{})},R=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",j),window.addEventListener("pagehide",R))}(e):function(t){try{B=s.addEventListener("change",e=>{"background"!==e&&"inactive"!==e||t.flush().catch(()=>{})})}catch{}const e=global.ErrorUtils;e?(P=e.getGlobalHandler(),e.setGlobalHandler((e,s)=>{t.captureError(e,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&t.flush().catch(()=>{}),P&&P(e,s)})):t.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),A=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))}function z(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const n=s.find(t=>t instanceof Error);if(n)i=n;else{const t=s.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(i=new Error(t),i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(i,{source:"console.error",synthetic:!n})}}function F(){if(A){if("web"===t.OS&&"undefined"!=typeof window)window.onerror=T,T=null,j&&(window.removeEventListener("beforeunload",j),j=null),R&&(window.removeEventListener("pagehide",R),R=null),U&&(window.removeEventListener("unhandledrejection",U),U=null);else{const t=global.ErrorUtils;t&&P&&(t.setGlobalHandler(P),P=null),B&&(B.remove(),B=null)}A=!1}}var N=class extends i.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=M.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:s}=this.state,{children:i,fallback:r}=this.props;return t&&e?r?n(r,{error:e,errorInfo:s,resetError:this.resetError}):null:i}};function O(t,e){const s=t.displayName||t.name||"Component",i=s=>n(N,{...e,children:n(t,{...s})});return i.displayName=`withWatchErrorBoundary(${s})`,i}export{v as BreadcrumbManager,W as SDK_VERSION,x as Watch,M as WatchClient,N as WatchErrorBoundary,g as clearDeviceInfoCache,y as collectDeviceInfo,E as createConsoleBreadcrumb,_ as createCustomBreadcrumb,D as createHttpBreadcrumb,b as createNavigationBreadcrumb,C as createUIBreadcrumb,$ as deriveReadableFile,m as getCachedDeviceInfo,p as getEnvironment,f as getPlatform,c as getSessionId,S as isUsableFilename,F as removeGlobalHandlers,l as resetSession,u as setSessionId,z as setupConsoleErrorCapture,q as setupGlobalHandlers,O as withWatchErrorBoundary};
1
+ import t from"expo-constants";import*as e from"expo-device";import{Platform as i,Dimensions as r,AppState as n}from"react-native";import s from"react-native/Libraries/Core/ExceptionsManager";import{uuid as o}from"expo-modules-core";import{Directory as a,Paths as h,File as c}from"expo-file-system";import u from"react";import{jsx as l}from"react/jsx-runtime";var d=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const e={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(e),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function f(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function p(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function m(t,e,i){return{type:"http",message:`${t} ${e}${i?` [${i}]`:""}`,data:{method:t,url:e,statusCode:i}}}function y(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function v(t,e){return{type:"custom",message:t,data:e}}function w(){return"ios"===i.OS?"ios":"android"===i.OS?"android":"web"}function g(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function b(t,e,i){try{const r=i();null!=r&&(t[e]=r)}catch{}}var E=null;function $(){return E||(E=function(){const n={},s=w();try{const t=r.get("window");n.screenWidth=t.width,n.screenHeight=t.height,n.screenScale=t.scale,n.fontScale=t.fontScale}catch{}b(n,"osName",()=>String(i.OS)),b(n,"osVersion",()=>null===i.Version||void 0===i.Version?void 0:String(i.Version)),"web"!==s&&(b(n,"brand",()=>e.brand??void 0),b(n,"manufacturer",()=>e.manufacturer??void 0),b(n,"modelName",()=>e.modelName??void 0),b(n,"modelId",()=>null===e.modelId||void 0===e.modelId?void 0:String(e.modelId)),b(n,"designName",()=>e.designName??void 0),b(n,"productName",()=>e.productName??void 0),b(n,"deviceName",()=>e.deviceName??void 0),b(n,"deviceType",()=>function(t){if(null!=t)return["unknown","phone","tablet","desktop","tv"][t]}(e.deviceType)),b(n,"deviceYearClass",()=>e.deviceYearClass??void 0),b(n,"memoryTotal",()=>e.totalMemory??void 0),b(n,"supportedCpuArchitectures",()=>e.supportedCpuArchitectures??void 0),b(n,"osName",()=>e.osName??void 0),b(n,"osVersion",()=>e.osVersion??void 0),b(n,"osBuildId",()=>e.osBuildId??void 0),b(n,"osInternalBuildId",()=>e.osInternalBuildId??void 0),b(n,"osBuildFingerprint",()=>e.osBuildFingerprint??void 0),b(n,"platformApiLevel",()=>e.platformApiLevel??void 0),b(n,"isDevice",()=>e.isDevice)),b(n,"appName",()=>t.expoConfig?.name),b(n,"appSlug",()=>t.expoConfig?.slug),b(n,"appVersion",()=>t.expoConfig?.version),b(n,"expoSdkVersion",()=>t.expoConfig?.sdkVersion),b(n,"runtimeVersion",()=>{const e=t.expoConfig?.runtimeVersion;return"string"==typeof e?e:void 0}),b(n,"executionEnvironment",()=>t.executionEnvironment?String(t.executionEnvironment):void 0),b(n,"expoRuntimeVersion",()=>t.expoRuntimeVersion??void 0),b(n,"expoVersion",()=>t.expoVersion??void 0),"ios"===s?(b(n,"bundleId",()=>t.expoConfig?.ios?.bundleIdentifier??void 0),b(n,"appBuildNumber",()=>t.expoConfig?.ios?.buildNumber??void 0)):"android"===s&&(b(n,"bundleId",()=>t.expoConfig?.android?.package??void 0),b(n,"appBuildNumber",()=>{const e=t.expoConfig?.android?.versionCode;return null==e?void 0:String(e)})),b(n,"jsEngine",()=>{if("web"===s)return"unknown";const t=globalThis.HermesInternal;return null!==t&&"object"==typeof t?"hermes":"jsc"});try{const t=Intl.DateTimeFormat().resolvedOptions();t.locale&&(n.locale=t.locale),t.timeZone&&(n.timezone=t.timeZone)}catch{}if("web"===s)try{const t=window.navigator?.userAgent;if(t){n.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(?:\.\d+)?)/);e&&(n.browserName=e[1],n.browserVersion=e[2])}}catch{}return n}()),E}var _=Symbol.for("@catdoes/watch/runtime-state-v2");function D(){const t=globalThis;return t[_]||(t[_]={capturedErrors:new WeakSet,handoffDepth:0,activeClient:null,clientInstance:null}),t[_]}function S(t){("object"==typeof t&&null!==t||"function"==typeof t)&&D().capturedErrors.add(t)}function k(t){return("object"==typeof t&&null!==t||"function"==typeof t)&&D().capturedErrors.has(t)}function T(t){const e=D();e.handoffDepth++;try{return t()}finally{e.handoffDepth--}}function C(){return D().handoffDepth>0}function j(t){D().activeClient=t}function I(){return D().activeClient}function x(){return D().clientInstance}var M=null,N=null;function W(t){try{const e=new URL(t);return`${e.origin}${e.pathname}`.slice(0,200)}catch{return t.split(/[?#]/,1)[0].slice(0,200)}}function U(t){if(j(t),N||"function"!=typeof globalThis.fetch)return;const e=globalThis.fetch;M=e,N=async(t,i)=>{const r=Date.now();let n="",s="GET",o=!1;try{n=function(t){return"string"==typeof t?t:"undefined"!=typeof URL&&t instanceof URL?t.href:"url"in t?t.url:String(t)}(t),s=function(t,e){return e?.method?e.method.toUpperCase():"undefined"!=typeof Request&&t instanceof Request?t.method.toUpperCase():"GET"}(t,i),o=n.startsWith(I()?.getConfig().endpoint??"")}catch{}try{const a=await e(t,i);if(!o)try{const t=W(n);I()?.addBreadcrumb({type:"http",message:`${s} ${t} ${a.status}`,data:{method:s,url:t,status:a.status,durationMs:Date.now()-r}})}catch{}return a}catch(t){if(!o)try{const e=W(n);I()?.addBreadcrumb({type:"http",message:`${s} ${e} failed`,data:{method:s,url:e,status:null,durationMs:Date.now()-r,error:t instanceof Error?t.name:"Error"}})}catch{}throw t}},globalThis.fetch=N}function A(){N&&M&&globalThis.fetch===N&&(globalThis.fetch=M),N=null,M=null}var P=!1,R=!1,B=!1,V=null,F=null;function z(t,e){const i=I();i?.getConfig().debug&&(void 0===e?console.debug(`[CatDoes Watch] ${t}`):console.debug(`[CatDoes Watch] ${t}:`,e))}function L(t){if(void 0===t)return"";if("[object Error]"===Object.prototype.toString.call(t))try{return Error.prototype.toString.call(t)}catch{return String(t)}if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}function q(t){return` at ${t.methodName||"<unknown>"} (${t.file||"<unknown>"}:${t.lineNumber??0}:${t.column??0})`}var O=null,H=null,Q=null,G=null;function J(t){j(t),"web"===i.OS?function(t){j(t),"undefined"==typeof window||O||(O=t=>{const e=t.error??new Error(t.message||"Unknown error");I()?.captureError(e,{source:"global.error",filename:t.filename,lineno:t.lineno,colno:t.colno})},H=t=>{t.defaultPrevented||I()?.captureError(t.reason,{source:"global.unhandledrejection",isPromiseRejection:!0})},Q=()=>{const t=I();t?.commit(),t?.flush({keepalive:!0}).catch(()=>{})},G=()=>{const t=I();t?.commit(),t?.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("error",O),window.addEventListener("unhandledrejection",H),window.addEventListener("beforeunload",Q),window.addEventListener("pagehide",G))}(t):function(t){j(t),function(){if(!P)try{const t=globalThis.ErrorUtils;if(!t)return void z("ErrorUtils not available");V=t.getGlobalHandler();const e=(t,e)=>{const i=V,r=I();if(!r)return void(i&&T(()=>i(t,e)));try{r.captureError(t,{source:"ErrorUtils.globalHandler",isFatal:e})}catch(t){z("Global handler capture failed",t)}if(e&&r.commit(),!i)return;if(!e||"development"===r.getConfig().environment){if(e)try{T(()=>i(t,e))}finally{r.flush().catch(()=>{})}else T(()=>i(t,e));return}const n=r.getConfig().fatalFlushTimeoutMs;if(n<=0)return r.flush().catch(()=>{}),void T(()=>i(t,e));r.flushWithTimeout(n).finally(()=>T(()=>i(t,e)))};t.setGlobalHandler(e),P=!0}catch(t){z("Failed to install ErrorUtils hook",t)}}(),function(){if(!R)try{const t=globalThis.HermesInternal;if(!t?.hasPromise?.()||!t.enablePromiseRejectionTracker)return;t.enablePromiseRejectionTracker({allRejections:!0,onUnhandled:(t,e)=>{const i=I();if(i)try{i.captureError(function(t){if(t instanceof Error)return t;const e=new Error(L(t));return e.name="UnhandledPromiseRejection",e}(e),{source:"HermesInternal.promiseRejectionTracker",isPromiseRejection:!0,rejectionId:t})}catch(t){z("Promise rejection capture failed",t)}if("undefined"!=typeof __DEV__&&__DEV__)try{!function(t,e){const i=`Uncaught (in promise, id: ${t})`,r=L(e),n=new Error(`${i} ${r??""}`);n.cause=e,n.stack="object"==typeof e&&null!==e&&"stack"in e?`${i} ${String(e.stack??"")}`:`${i} ${r??""}`,S(n),T(()=>s.handleException(n,!1))}(t,e)}catch(t){z("Promise rejection forwarding failed",t)}},onHandled:t=>{"undefined"!=typeof __DEV__&&__DEV__&&console.warn(`Promise rejection handled (id: ${t})\nThis means you can ignore any previous messages of the form "Uncaught (in promise, id: ${t})"`)}}),R=!0}catch(t){z("Failed to install promise rejection tracker",t)}}(),function(){if(!B)try{s.unstable_setExceptionDecorator(t=>{try{const e=I();if(e&&t.isFatal&&!C()){try{e.captureError(function(t){const e=new Error(t.originalMessage??t.message);t.name&&(e.name=t.name);const i=t.extraData?.rawStack;return"string"==typeof i?e.stack=i:Array.isArray(t.stack)&&(e.stack=[`${e.name}: ${e.message}`,...t.stack.map(q)].join("\n")),e}(t),{source:"ExceptionsManager.exceptionDecorator",isFatal:!0,componentStack:t.componentStack??void 0})}finally{e.commit()}e.flush().catch(()=>{})}}catch(t){z("Exception decorator capture failed",t)}return t}),B=!0}catch(t){z("Failed to install exception decorator",t)}}(),function(){if(!F)try{F=n.addEventListener("change",t=>{if("background"===t||"inactive"===t){const t=I();t?.commit(),t?.flush().catch(()=>{})}})}catch(t){z("Failed to install AppState flush",t)}}()}(t)}function K(){j(null),F?.remove(),F=null,"undefined"!=typeof window&&(O&&window.removeEventListener("error",O),H&&window.removeEventListener("unhandledrejection",H),Q&&window.removeEventListener("beforeunload",Q),G&&window.removeEventListener("pagehide",G)),O=null,H=null,Q=null,G=null,A()}var Y=!1,X=null;function Z(t){if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}var tt=null;function et(){const t="abcdefghijklmnopqrstuvwxyz0123456789",e=new Uint8Array(8);try{if("undefined"!=typeof crypto&&crypto.getRandomValues)return crypto.getRandomValues(e),Array.from(e,e=>t[e%36]).join("")}catch{}let i="";for(let e=0;e<8;e++)i+=t.charAt(Math.floor(36*Math.random()));return i}function it(){try{return o.v4()}catch{return`${Date.now().toString(36)}${et()}`}}function rt(){return tt||(tt=`sess_${Date.now()}_${function(){try{return o.v4().replace(/-/g,"").slice(0,8)}catch{return et()}}()}`),tt}function nt(){tt=null}function st(t){tt=t}function ot(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let e=t.replace(/\/{2,}/g,"/");return e.startsWith("./")&&(e=e.slice(2)),e}function at(t,e,i){t&&console.debug(`[CatDoes Watch] Queue ${e} failed:`,i)}var ht=class{constructor(t,e=!1){this.debug=e,this.directoryReady=!1,this.directory=new a(h.cache,"catdoes-watch"),this.file=new c(this.directory,`${t}.json`)}read(){try{return this.file.exists?this.file.textSync():null}catch(t){return at(this.debug,"read",t),null}}write(t){try{this.directoryReady||(this.directory.create({idempotent:!0,intermediates:!0}),this.directoryReady=!0),this.file.write(t)}catch(t){at(this.debug,"write",t)}}remove(){try{this.file.exists&&this.file.delete()}catch(t){at(this.debug,"remove",t)}}},ct=class{constructor(t,e=!1){this.key=t,this.debug=e}getStorage(){try{return"undefined"==typeof window?null:window.localStorage}catch(t){return at(this.debug,"localStorage access",t),null}}read(){try{return this.getStorage()?.getItem(this.key)??null}catch(t){return at(this.debug,"read",t),null}}write(t){try{this.getStorage()?.setItem(this.key,t)}catch(t){at(this.debug,"write",t)}}remove(){try{this.getStorage()?.removeItem(this.key)}catch(t){at(this.debug,"remove",t)}}},ut=3e5;function lt(t){return"object"==typeof t&&null!==t}function dt(t){return lt(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}function ft(t){return t instanceof Error?t.message:String(t)}function pt(t){const e=new WeakSet;return JSON.stringify(t,(t,i)=>{if("bigint"==typeof i)return i.toString();if("object"==typeof i&&null!==i){if(e.has(i))return"[Circular]";e.add(i)}return i})}var mt=class{constructor(t){if(this.config=t,this.queue=[],this.inFlight=[],this.queueBytes=0,this.inFlightBytes=0,this.flushTimer=null,this.flushTimerAt=0,this.flushPromise=null,this.dirty=!1,this.lastWriteAt=null,this.writeTimer=null,this.backoffMs=0,this.backoffUntil=0,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.lastFlush=null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,this.apiKeyHash=t.apiKey?function(t){let e=5381;for(let i=0;i<t.length;i++)e=(e<<5)+e^t.charCodeAt(i);return(e>>>0).toString(36)}(t.apiKey):"",this.store=t.apiKey?t.storage??function(t,e=!1){const r=`@catdoes_watch_queue_v2_${t}`;return"web"===i.OS?new ct(r,e):new ht(r,e)}(this.apiKeyHash,t.debug):null,!t.apiKey)return this.disabledReason="auth",this.disabledUntil=1/0,void(t.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."));this.hydrate(),this.queue.length>0&&this.scheduleFlush(0)}send(t){if(!this.config.apiKey||this.isDisabled())return;const e=this.createEntry(t);this.queue.push(e),this.queueBytes+=e.serialized.length,this.trimQueue(),this.persist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=Math.max(1,this.config.maxBufferSize)?this.flush():this.scheduleFlush()}async flush(t){for(;;){if(!this.flushPromise){if(0===this.queue.length)return;this.cancelScheduledFlush(),this.flushPromise=this.flushBatch(t).finally(()=>{this.flushPromise=null})}if(!await this.flushPromise)return}}async flushWithTimeout(t){let e=null;const i=new Promise(i=>{e=setTimeout(i,Math.max(0,t))});try{await Promise.race([this.flush().catch(()=>{}),i])}finally{e&&clearTimeout(e)}}get queueSize(){return this.inFlight.length+this.queue.length}getStats(){const t=Date.now(),e="failures"===this.disabledReason&&t>=this.disabledUntil;return{disabledReason:e?null:this.disabledReason,backoffUntil:this.backoffUntil>t?this.backoffUntil:null,consecutiveFailures:e?0:this.consecutiveFailures,lastFlush:this.lastFlush?{...this.lastFlush}:null}}commit(){if(this.writeTimer&&(clearTimeout(this.writeTimer),this.writeTimer=null),this.store&&this.dirty){this.dirty=!1,this.lastWriteAt=Date.now();try{const t=Date.now(),e=this.backoffUntil>t||"failures"===this.disabledReason&&this.disabledUntil>t;if(0===this.inFlight.length&&0===this.queue.length&&!e)return void this.store.remove();this.store.write(this.serializePersistedQueue())}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue serialization failed:",t)}}}clear(){this.queue=[],this.inFlight=[],this.queueBytes=0,this.inFlightBytes=0,this.persist(),this.commit(),this.cancelScheduledFlush()}shutdown(){this.clear()}scheduleFlush(t=this.config.flushInterval){const e=Math.max(0,t),i=Date.now()+e;this.flushTimer&&this.flushTimerAt<=i||(this.flushTimer&&clearTimeout(this.flushTimer),this.flushTimerAt=i,this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushTimerAt=0,this.flush()},e))}cancelScheduledFlush(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null,this.flushTimerAt=0)}async flushBatch(t){if(0===this.queue.length)return!1;if(this.isDisabled())return this.disabledUntil!==1/0&&this.scheduleFlush(this.disabledUntil-Date.now()),!1;if(Date.now()<this.backoffUntil)return this.scheduleFlush(this.backoffUntil-Date.now()),!1;const e=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(e,10):e,r=this.queue.splice(0,i),n=r.reduce((t,e)=>t+e.serialized.length,0);this.queueBytes-=n,this.inFlight=r,this.inFlightBytes=n,this.persist();const s=(new Date).toISOString();this.lastFlush={at:s,status:null,accepted:null};let o=!1;try{const e=await fetch(this.config.endpoint,this.requestInit(r,t));if(401===e.status||403===e.status)return this.lastFlush={at:s,status:e.status,accepted:null,error:"Invalid API key"},this.queue=[],this.queueBytes=0,o=!0,this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),!1;if(429===e.status){o=this.applyBackoff();const t=1e3*Number.parseInt(e.headers.get("Retry-After")??"",10);return Number.isFinite(t)&&t>this.backoffMs&&(this.backoffMs=t,this.backoffUntil=Date.now()+t),this.lastFlush={at:s,status:e.status,accepted:null,error:"Rate limited"},!1}if(!e.ok)return this.lastFlush={at:s,status:e.status,accepted:null,error:`HTTP ${e.status}`},o=this.applyBackoff(),!1;let i={};try{i=await e.json()}catch{}return this.lastFlush={at:s,status:e.status,accepted:"number"==typeof i.accepted?i.accepted:null},this.backoffMs=0,this.backoffUntil=0,this.consecutiveFailures=0,o=!0,!0}catch(t){return this.lastFlush={at:s,status:null,accepted:null,error:ft(t)},o=this.applyBackoff(),!1}finally{this.inFlight===r&&(o||(this.queue=[...r,...this.queue],this.queueBytes+=n),this.inFlight=[],this.inFlightBytes=0),this.trimQueue(),this.persist(),this.queue.length>0&&!this.isDisabled()&&this.scheduleFlush(Math.max(0,this.backoffUntil-Date.now()))}}requestInit(t,e){const r={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:`{"events":[${t.map(t=>t.serialized).join(",")}]}`};return"web"===i.OS&&e?.keepalive&&(r.keepalive=!0),r}applyBackoff(){return this.consecutiveFailures++,this.backoffMs=Math.min(1e3*2**(this.consecutiveFailures-1),ut),this.backoffUntil=Date.now()+this.backoffMs,this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.queue=[],this.queueBytes=0,this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"),!0)}isDisabled(){return"failures"===this.disabledReason&&Date.now()>=this.disabledUntil&&(this.disabledReason=null,this.disabledUntil=0,this.consecutiveFailures=0,this.persist()),null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,e){this.disabledReason=t,this.disabledUntil=e===1/0?1/0:Date.now()+Math.max(0,e)}trimQueue(){let t=0,e=this.queueBytes,i=this.queue.length;for(;t<this.queue.length&&(this.inFlight.length+i>100||this.persistedSize(i,e)>512e3);)e-=this.queue[t].serialized.length,i--,t++;t>0&&(this.queue=this.queue.slice(t),this.queueBytes=e)}buildPayloadMetadata(){const t=Date.now(),e={v:2,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash};return this.backoffUntil>t&&(e.backoffUntil=this.backoffUntil),this.consecutiveFailures>0&&(e.consecutiveFailures=this.consecutiveFailures),"failures"===this.disabledReason&&Number.isFinite(this.disabledUntil)&&this.disabledUntil>t&&(e.disabledUntil=this.disabledUntil),e}allEntries(){return[...this.inFlight,...this.queue]}persistedSize(t,e){const i=pt(this.buildPayloadMetadata()).length,r=this.inFlight.length+t;return i+12+this.inFlightBytes+e+Math.max(0,r-1)}createEntry(t){return{serialized:pt(t)}}persist(){if(!this.store)return;this.dirty=!0;const t=null===this.lastWriteAt?500:Date.now()-this.lastWriteAt;t>=500?this.commit():this.writeTimer||(this.writeTimer=setTimeout(()=>{this.writeTimer=null,this.commit()},500-t))}serializePersistedQueue(){const t=pt(this.buildPayloadMetadata()),e=this.allEntries();return`${t.slice(0,-1)},"events":[${e.map(t=>t.serialized).join(",")}]}`}hydrate(){if(!this.store)return;let t;try{t=this.store.read()}catch(t){return void(this.config.debug&&console.debug("[CatDoes Watch] Queue hydration failed:",t))}if(t)try{const e=JSON.parse(t);if(!lt(e)||2!==e.v||e.apiKeyHash!==this.apiKeyHash||!Array.isArray(e.events))return void this.store.remove();this.queue=e.events.filter(dt).map(t=>this.createEntry(t)),this.queueBytes=this.queue.reduce((t,e)=>t+e.serialized.length,0),this.restoreThrottleState(e),this.trimQueue(),0===this.queue.length&&0===this.backoffUntil&&null===this.disabledReason?this.store.remove():this.persist()}catch{try{this.store.remove()}catch{}}}restoreThrottleState(t){const e=Date.now(),i=Number(t.backoffUntil);Number.isFinite(i)&&i>e&&(this.backoffUntil=Math.min(i,e+ut),this.backoffMs=this.backoffUntil-e);const r=Number(t.consecutiveFailures);Number.isFinite(r)&&r>0&&(this.consecutiveFailures=Math.min(Math.floor(r),this.maxConsecutiveFailures));const n=Number(t.disabledUntil);Number.isFinite(n)&&n>e&&this.disable("failures",Math.min(n-e,this.failuresCooldownMs))}},yt="2.0.0";function vt(t,e){try{const i=t[e];return"string"==typeof i?i:void 0}catch{return}}function wt(t,e){return void 0===t?void 0:t.slice(0,e)}var gt=class _WatchClient{constructor(t){this.context={},this.user=null,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint??"https://app.catdoes.com/api/watch/ingest",environment:t.environment??g(),debug:t.debug??!1,installGlobalHandlers:t.installGlobalHandlers??!0,captureConsoleErrors:t.captureConsoleErrors??!1,captureHttpBreadcrumbs:t.captureHttpBreadcrumbs??!1,maxBreadcrumbs:t.maxBreadcrumbs??20,maxBufferSize:t.maxBufferSize??10,flushInterval:t.flushInterval??5e3,fatalFlushTimeoutMs:t.fatalFlushTimeoutMs??2e3,beforeSend:t.beforeSend??(t=>t),dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage},this.transport=new mt({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new d(this.config.maxBreadcrumbs),this.context={...t.initialContext};try{$()}catch{}}static init(t){let e=x();return e||(e=new _WatchClient(t),function(t){D().clientInstance=t}(e),t.apiKey?(j(e),e.config.installGlobalHandlers&&J(e),e.config.captureConsoleErrors&&function(t){j(t),Y||(X=console.error,console.error=(...t)=>{try{const e=I(),i=t.find(t=>t instanceof Error),r=t.map(Z).join(" ");if(e&&!C()&&!r.startsWith("Warning: ")&&(!i||!k(i))&&!0!==i?.isComponentError){const t=i??new Error(r);if(!i&&t.stack){const e=t.stack.split("\n");t.stack=[e[0],...e.slice(3)].join("\n")}S(t),e.captureError(t,{source:"console.error",synthetic:!i})}}catch{}X?.apply(console,t)},Y=!0)}(e),e.config.captureHttpBreadcrumbs&&U(e)):"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported.")),e}static getInstance(){return x()}captureError(t,e){if(!this.config.apiKey)return;const i=function(t){if(t instanceof Error)return t;if("string"==typeof t)return new Error(t);if("object"==typeof t&&null!==t){const e=vt(t,"message");if(void 0!==e){const i=new Error(e),r=vt(t,"name"),n=vt(t,"stack");return r&&(i.name=r),n&&(i.stack=n),i}}try{return new Error(String(t))}catch{return new Error("Unknown error")}}(t);let r;S(i);try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){r=t.stack}const n=this.getErrorKey(i);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(n);const s=this.ensureStack(i,r),o=this.buildEvent(s,e),a=this.config.beforeSend(o);a&&this.transport.send(a)}captureMessage(t,e="error"){const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:e,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,e){this.context[t]=e}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}async flushWithTimeout(t){await this.transport.flushWithTimeout(t)}commit(){this.transport.commit()}getStats(){return{queueSize:this.transport.queueSize,sessionId:rt(),transport:this.transport.getStats()}}getConfig(){return this.config}get initialized(){return!0}buildEvent(t,e){let i={};try{i=$()}catch{}const r="string"==typeof e?.componentStack?wt(e.componentStack,4e3):void 0,n={...this.context,...e};delete n.componentStack;const s={eventId:it(),message:wt(t.message||"Unknown error",1e4),stack:wt(t.stack,16e3),componentStack:r,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:w(),sessionId:rt(),deviceInfo:i,sdkVersion:yt,extra:{...n,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const e=function(t){if(!t)return t;let e=t;const i=e.indexOf("?");i>=0&&(e=e.slice(0,i));const r=e.indexOf("&");for(r>=0&&(e=e.slice(0,r));e.endsWith("/")&&e.length>1;)e=e.slice(0,-1);return e}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=e.indexOf(t);if(i>=0)return ot(e.slice(i+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return ot(e)}(i[1]);!t||!(o=t)||o.includes("node_modules")||/\.bundle(\/|$|:)/.test(o)||/bundle(\.js|\.map)$/.test(o)||o.startsWith("[native code]")||o.startsWith("native ")||(s.filename=t),s.lineno=Number.parseInt(i[2],10),s.colno=Number.parseInt(i[3],10)}void 0===s.lineno&&"number"==typeof e?.lineno&&(s.lineno=e.lineno),void 0===s.colno&&"number"==typeof e?.colno&&(s.colno=e.colno),s.filename||"string"!=typeof e?.filename||(s.filename=e.filename)}var o;return s}ensureStack(t,e){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(e&&e.length>100&&/:\d+:\d+/.test(e)){const i=e.split("\n").filter(t=>!(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))).map(t=>t.trim()).filter(t=>t.includes("@")||t.startsWith("at "));if(i.length>0){const e=new Error(t.message);return e.name=t.name,e.stack=[`${t.name}: ${t.message}`,...i].join("\n"),Object.assign(e,t),e}}return t}getErrorKey(t){return`${t.message}::${t.stack?.split("\n")[1]?.trim()??""}`}hasSeenErrorRecently(t){const e=this.recentErrors.get(t);return void 0!==e&&(Date.now()-e<=this.config.dedupWindowMs||(this.recentErrors.delete(t),!1))}markErrorAsSeen(t){if(this.recentErrors.delete(t),this.recentErrors.set(t,Date.now()),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{const t=Date.now()-this.config.dedupWindowMs;for(const[e,i]of this.recentErrors)i<t&&this.recentErrors.delete(e);this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}},bt={init:t=>gt.init(t),getInstance:()=>gt.getInstance(),captureError(t,e){gt.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){gt.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){gt.getInstance()?.addBreadcrumb(t)},setContext(t,e){gt.getInstance()?.setContext(t,e)},clearContext(t){gt.getInstance()?.clearContext(t)},setUser(t){gt.getInstance()?.setUser(t)},flush:t=>gt.getInstance()?.flush(t)??Promise.resolve(),flushWithTimeout:t=>gt.getInstance()?.flushWithTimeout(t)??Promise.resolve(),getStats:()=>gt.getInstance()?.getStats()??null},Et=class extends u.Component{constructor(){super(...arguments),this.state={hasError:!1,error:null,errorInfo:null},this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,e){this.setState({errorInfo:e});const{captureErrors:i=!0,onError:r}=this.props;i&&!k(t)&&I()?.captureError(t,{componentStack:e.componentStack??void 0,source:"WatchErrorBoundary"}),r?.(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&e?n?l(n,{error:e,errorInfo:i??{componentStack:null},resetError:this.resetError}):null:r}};function $t(t,e){const i=t.displayName||t.name||"Component",r=i=>l(Et,{...e,children:l(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r}Et.displayName="WatchErrorBoundary";export{d as BreadcrumbManager,yt as SDK_VERSION,bt as Watch,gt as WatchClient,Et as WatchErrorBoundary,y as createConsoleBreadcrumb,v as createCustomBreadcrumb,m as createHttpBreadcrumb,f as createNavigationBreadcrumb,p as createUIBreadcrumb,$ as getDeviceInfo,g as getEnvironment,w as getPlatform,rt as getSessionId,U as installHttpBreadcrumbs,K as removeGlobalHandlers,A as removeHttpBreadcrumbs,nt as resetSession,st as setSessionId,J as setupGlobalHandlers,$t as withWatchErrorBoundary};
package/dist/react.d.mts CHANGED
@@ -1,33 +1,13 @@
1
1
  import React from 'react';
2
2
 
3
- /**
4
- * CatDoes Watch SDK - React Error Boundary
5
- *
6
- * A React Error Boundary component that automatically captures
7
- * errors and reports them to CatDoes Watch.
8
- */
9
-
10
3
  interface WatchErrorBoundaryProps {
11
- /**
12
- * The children to render.
13
- */
14
4
  children: React.ReactNode;
15
- /**
16
- * A fallback component to render when an error occurs.
17
- */
18
5
  fallback?: React.ComponentType<{
19
6
  error: Error;
20
7
  errorInfo: React.ErrorInfo;
21
8
  resetError: () => void;
22
9
  }>;
23
- /**
24
- * A callback invoked when an error is caught.
25
- */
26
10
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
27
- /**
28
- * Whether to capture errors to CatDoes Watch.
29
- * @default true
30
- */
31
11
  captureErrors?: boolean;
32
12
  }
33
13
  interface WatchErrorBoundaryState {
@@ -35,22 +15,14 @@ interface WatchErrorBoundaryState {
35
15
  error: Error | null;
36
16
  errorInfo: React.ErrorInfo | null;
37
17
  }
38
- /**
39
- * React Error Boundary that integrates with CatDoes Watch.
40
- */
41
18
  declare class WatchErrorBoundary extends React.Component<WatchErrorBoundaryProps, WatchErrorBoundaryState> {
42
- constructor(props: WatchErrorBoundaryProps);
19
+ static displayName: string;
20
+ state: WatchErrorBoundaryState;
43
21
  static getDerivedStateFromError(error: Error): Partial<WatchErrorBoundaryState>;
44
22
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
45
- /**
46
- * Resets the error state, allowing the children to be re-rendered.
47
- */
48
23
  resetError: () => void;
49
24
  render(): React.ReactNode;
50
25
  }
51
- /**
52
- * Higher-order component that wraps a component with WatchErrorBoundary.
53
- */
54
26
  declare function withWatchErrorBoundary<P extends object>(Component: React.ComponentType<P>, errorBoundaryProps?: Omit<WatchErrorBoundaryProps, "children">): React.ComponentType<P>;
55
27
 
56
28
  export { WatchErrorBoundary, type WatchErrorBoundaryProps, withWatchErrorBoundary };
package/dist/react.d.ts CHANGED
@@ -1,33 +1,13 @@
1
1
  import React from 'react';
2
2
 
3
- /**
4
- * CatDoes Watch SDK - React Error Boundary
5
- *
6
- * A React Error Boundary component that automatically captures
7
- * errors and reports them to CatDoes Watch.
8
- */
9
-
10
3
  interface WatchErrorBoundaryProps {
11
- /**
12
- * The children to render.
13
- */
14
4
  children: React.ReactNode;
15
- /**
16
- * A fallback component to render when an error occurs.
17
- */
18
5
  fallback?: React.ComponentType<{
19
6
  error: Error;
20
7
  errorInfo: React.ErrorInfo;
21
8
  resetError: () => void;
22
9
  }>;
23
- /**
24
- * A callback invoked when an error is caught.
25
- */
26
10
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
27
- /**
28
- * Whether to capture errors to CatDoes Watch.
29
- * @default true
30
- */
31
11
  captureErrors?: boolean;
32
12
  }
33
13
  interface WatchErrorBoundaryState {
@@ -35,22 +15,14 @@ interface WatchErrorBoundaryState {
35
15
  error: Error | null;
36
16
  errorInfo: React.ErrorInfo | null;
37
17
  }
38
- /**
39
- * React Error Boundary that integrates with CatDoes Watch.
40
- */
41
18
  declare class WatchErrorBoundary extends React.Component<WatchErrorBoundaryProps, WatchErrorBoundaryState> {
42
- constructor(props: WatchErrorBoundaryProps);
19
+ static displayName: string;
20
+ state: WatchErrorBoundaryState;
43
21
  static getDerivedStateFromError(error: Error): Partial<WatchErrorBoundaryState>;
44
22
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
45
- /**
46
- * Resets the error state, allowing the children to be re-rendered.
47
- */
48
23
  resetError: () => void;
49
24
  render(): React.ReactNode;
50
25
  }
51
- /**
52
- * Higher-order component that wraps a component with WatchErrorBoundary.
53
- */
54
26
  declare function withWatchErrorBoundary<P extends object>(Component: React.ComponentType<P>, errorBoundaryProps?: Omit<WatchErrorBoundaryProps, "children">): React.ComponentType<P>;
55
27
 
56
28
  export { WatchErrorBoundary, type WatchErrorBoundaryProps, withWatchErrorBoundary };
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- "use strict";var t=require("react"),s=require("react-native"),i=require("react/jsx-runtime");function e(t){return t&&t.t?t:{default:t}}var r=e(t);function n(t){return"object"==typeof t&&null!==t}function h(t){return!!n(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var o=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(h);else if(n(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(h)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function u(){const t=s.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null;function d(){return l||(l=function(){const t=c("ExpoDevice"),i=c("ExponentConstants"),e=c("ExpoLocalization"),r=u(),n={};try{const{width:t,height:i,scale:e}=s.Dimensions.get("window");n.screenWidth=Math.round(t),n.screenHeight=Math.round(i),n.screenScale=e}catch{}if(n.osName=s.Platform.OS,s.Platform.Version&&(n.osVersion=String(s.Platform.Version)),t&&"web"!==r)try{t.brand&&(n.brand=t.brand),t.manufacturer&&(n.manufacturer=t.manufacturer),t.modelName&&(n.modelName=t.modelName),t.deviceName&&(n.deviceName=t.deviceName),t.osName&&(n.osName=t.osName),t.osVersion&&(n.osVersion=t.osVersion),t.osBuildId&&(n.osBuildId=t.osBuildId),t.platformApiLevel&&(n.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(n.isDevice=t.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(n.appVersion=t.version),i.expoVersion&&(n.expoVersion=i.expoVersion),t?.name&&(n.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?n.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(n.bundleId=t.android.package)}catch{}if(e)try{const t=e.getLocales?.()?.[0]?.languageTag;t&&(n.locale=t);const s=e.getCalendars?.()?.[0]?.timeZone;s&&(n.timezone=s)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){n.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(n.browserName=s[1],n.browserVersion=s[2])}}catch{}return n}()),l}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function p(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var y=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new o({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{d()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=d()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:(a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),a),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return p(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return p(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};y.instance=null;var w=y,m=class extends r.default.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=w.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:e}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?i.jsx(n,{error:s,errorInfo:e,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=m,exports.withWatchErrorBoundary=function(t,s){const e=t.displayName||t.name||"Component",r=e=>i.jsx(m,{...s,children:i.jsx(t,{...e})});return r.displayName=`withWatchErrorBoundary(${e})`,r};
1
+ "use strict";var r=require("react"),t=require("react/jsx-runtime");function o(r){return r&&r.t?r:{default:r}}var e=o(r),n=Symbol.for("@catdoes/watch/runtime-state-v2");function c(){const r=globalThis;return r[n]||(r[n]={capturedErrors:new WeakSet,handoffDepth:0,activeClient:null,clientInstance:null}),r[n]}var s=class extends e.default.Component{constructor(){super(...arguments),this.state={hasError:!1,error:null,errorInfo:null},this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})}}static getDerivedStateFromError(r){return{hasError:!0,error:r}}componentDidCatch(r,t){this.setState({errorInfo:t});const{captureErrors:o=!0,onError:e}=this.props;o&&!function(r){return("object"==typeof r&&null!==r||"function"==typeof r)&&c().capturedErrors.has(r)}(r)&&c().activeClient?.captureError(r,{componentStack:t.componentStack??void 0,source:"WatchErrorBoundary"}),e?.(r,t)}render(){const{hasError:r,error:o,errorInfo:e}=this.state,{children:n,fallback:c}=this.props;return r&&o?c?t.jsx(c,{error:o,errorInfo:e??{componentStack:null},resetError:this.resetError}):null:n}};s.displayName="WatchErrorBoundary",exports.WatchErrorBoundary=s,exports.withWatchErrorBoundary=function(r,o){const e=r.displayName||r.name||"Component",n=e=>t.jsx(s,{...o,children:t.jsx(r,{...e})});return n.displayName=`withWatchErrorBoundary(${e})`,n};
package/dist/react.mjs CHANGED
@@ -1 +1 @@
1
- import t from"react";import{Platform as s,Dimensions as i}from"react-native";import{jsx as e}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function n(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(n);else if(r(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(n)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},o=null;function a(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function c(){const t=s.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var u=null;function l(){return u||(u=function(){const t=a("ExpoDevice"),e=a("ExponentConstants"),r=a("ExpoLocalization"),n=c(),h={};try{const{width:t,height:s,scale:e}=i.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(s),h.screenScale=e}catch{}if(h.osName=s.OS,s.Version&&(h.osVersion=String(s.Version)),t&&"web"!==n)try{t.brand&&(h.brand=t.brand),t.manufacturer&&(h.manufacturer=t.manufacturer),t.modelName&&(h.modelName=t.modelName),t.deviceName&&(h.deviceName=t.deviceName),t.osName&&(h.osName=t.osName),t.osVersion&&(h.osVersion=t.osVersion),t.osBuildId&&(h.osBuildId=t.osBuildId),t.platformApiLevel&&(h.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(h.isDevice=t.isDevice)}catch{}if(e)try{let t=null;"string"==typeof e.manifest?t=JSON.parse(e.manifest):e.manifest&&"object"==typeof e.manifest&&(t=e.manifest),t?.version&&(h.appVersion=t.version),e.expoVersion&&(h.expoVersion=e.expoVersion),t?.name&&(h.appName=t.name),"ios"===n&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===n&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(r)try{const t=r.getLocales?.()?.[0]?.languageTag;t&&(h.locale=t);const s=r.getCalendars?.()?.[0]?.timeZone;s&&(h.timezone=s)}catch{}if("web"===n&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(h.browserName=s[1],h.browserVersion=s[2])}}catch{}return h}()),u}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function d(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var p=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{l()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=l()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:c(),sessionId:(o||(o=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),o),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return d(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return d(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};p.instance=null;var y=p,m=class extends t.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=y.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?e(n,{error:s,errorInfo:i,resetError:this.resetError}):null:r}};function w(t,s){const i=t.displayName||t.name||"Component",r=i=>e(m,{...s,children:e(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r}export{m as WatchErrorBoundary,w as withWatchErrorBoundary};
1
+ import r from"react";import{jsx as o}from"react/jsx-runtime";var t=Symbol.for("@catdoes/watch/runtime-state-v2");function n(){const r=globalThis;return r[t]||(r[t]={capturedErrors:new WeakSet,handoffDepth:0,activeClient:null,clientInstance:null}),r[t]}var e=class extends r.Component{constructor(){super(...arguments),this.state={hasError:!1,error:null,errorInfo:null},this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})}}static getDerivedStateFromError(r){return{hasError:!0,error:r}}componentDidCatch(r,o){this.setState({errorInfo:o});const{captureErrors:t=!0,onError:e}=this.props;t&&!function(r){return("object"==typeof r&&null!==r||"function"==typeof r)&&n().capturedErrors.has(r)}(r)&&n().activeClient?.captureError(r,{componentStack:o.componentStack??void 0,source:"WatchErrorBoundary"}),e?.(r,o)}render(){const{hasError:r,error:t,errorInfo:n}=this.state,{children:e,fallback:c}=this.props;return r&&t?c?o(c,{error:t,errorInfo:n??{componentStack:null},resetError:this.resetError}):null:e}};function c(r,t){const n=r.displayName||r.name||"Component",c=n=>o(e,{...t,children:o(r,{...n})});return c.displayName=`withWatchErrorBoundary(${n})`,c}e.displayName="WatchErrorBoundary";export{e as WatchErrorBoundary,c as withWatchErrorBoundary};
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@catdoes/watch",
3
- "version": "1.2.0",
4
- "description": "Error tracking and monitoring SDK for React Native and Expo apps - by CatDoes Inc.",
3
+ "version": "2.0.0",
4
+ "description": "Error tracking and monitoring SDK for Expo apps by CatDoes Inc.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "sideEffects": false,
8
9
  "exports": {
9
10
  ".": {
10
11
  "types": "./dist/index.d.ts",
@@ -15,7 +16,13 @@
15
16
  "types": "./dist/react.d.ts",
16
17
  "import": "./dist/react.mjs",
17
18
  "require": "./dist/react.js"
18
- }
19
+ },
20
+ "./expo-router": {
21
+ "types": "./dist/expo-router.d.ts",
22
+ "import": "./dist/expo-router.mjs",
23
+ "require": "./dist/expo-router.js"
24
+ },
25
+ "./package.json": "./package.json"
19
26
  },
20
27
  "files": [
21
28
  "dist",
@@ -26,8 +33,10 @@
26
33
  "build:dev": "tsup && node scripts/check-dist.mjs",
27
34
  "dev": "tsup --watch",
28
35
  "clean": "rm -rf dist",
29
- "prepublishOnly": "npm run build",
30
- "typecheck": "tsc --noEmit"
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "typecheck": "tsc --noEmit",
39
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
31
40
  },
32
41
  "keywords": [
33
42
  "error-tracking",
@@ -48,17 +57,43 @@
48
57
  "url": "https://github.com/catdoes/watch/issues"
49
58
  },
50
59
  "engines": {
51
- "node": ">=18"
60
+ "node": ">=20"
52
61
  },
53
62
  "peerDependencies": {
54
- "react": ">=18.0.0",
55
- "react-native": ">=0.72.0"
63
+ "expo": ">=57.0.0",
64
+ "expo-constants": ">=57.0.0",
65
+ "expo-device": ">=57.0.0",
66
+ "expo-file-system": ">=57.0.0",
67
+ "expo-modules-core": ">=57.0.0",
68
+ "expo-router": ">=57.0.0",
69
+ "react": ">=19.2.0",
70
+ "react-native": ">=0.86.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "expo-router": {
74
+ "optional": true
75
+ }
56
76
  },
57
77
  "devDependencies": {
58
- "@types/react": "^19.2.0",
59
- "cross-env": "^7.0.3",
60
- "terser": "^5.37.0",
61
- "tsup": "^8.3.5",
62
- "typescript": "~5.9.2"
78
+ "@testing-library/dom": "^10.4.1",
79
+ "@testing-library/react": "^16.3.3",
80
+ "@types/react": "~19.2.2",
81
+ "cross-env": "^10.1.0",
82
+ "expo": "57.0.20",
83
+ "expo-constants": "57.0.17",
84
+ "expo-device": "57.0.1",
85
+ "expo-file-system": "57.0.6",
86
+ "expo-modules-core": "57.0.16",
87
+ "expo-router": "57.0.19",
88
+ "jsdom": "^30.0.1",
89
+ "react": "19.2.3",
90
+ "react-dom": "19.2.3",
91
+ "react-native": "0.86.3",
92
+ "react-native-reanimated": "4.5.1",
93
+ "react-native-worklets": "0.10.1",
94
+ "terser": "^5.44.0",
95
+ "tsup": "^8.5.1",
96
+ "typescript": "~5.9.2",
97
+ "vitest": "^5.0.0"
63
98
  }
64
99
  }