@catdoes/watch 1.0.0 → 1.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.
package/README.md CHANGED
@@ -6,12 +6,12 @@ Error tracking and monitoring SDK for React Native and Expo apps.
6
6
 
7
7
  ## Features
8
8
 
9
- - 🚨 **Automatic Error Capture** - Catches unhandled errors and promise rejections
10
- - 🎯 **React Error Boundary** - Integrated error boundary component
11
- - 📍 **Breadcrumbs** - Track user actions leading up to errors
12
- - 📱 **Cross-Platform** - Works on iOS, Android, and Web
13
- - 🔄 **Offline Support** - Queues events when offline, sends when reconnected
14
- - **Lightweight** - Minimal impact on app performance
9
+ - **Automatic Error Capture** - Catches unhandled errors and promise rejections
10
+ - **React Error Boundary** - Integrated error boundary component
11
+ - **Breadcrumbs** - Track user actions leading up to errors
12
+ - **Cross-Platform** - Works on iOS, Android, and Web
13
+ - **Offline Support** - Queues events when offline, sends when reconnected
14
+ - **Lightweight** - Minimal impact on app performance
15
15
 
16
16
  ## Installation
17
17
 
package/dist/index.d.mts CHANGED
@@ -481,6 +481,6 @@ declare function isUsableFilename(filename: string): boolean;
481
481
  *
482
482
  * Keep this value updated when making SDK changes.
483
483
  */
484
- declare const SDK_VERSION = "1.0.0";
484
+ declare const SDK_VERSION = "1.1.0";
485
485
 
486
486
  export { type Breadcrumb, type BreadcrumbInput, BreadcrumbManager, type DeviceInfo, type IngestResponse, SDK_VERSION, Watch, WatchClient, type WatchConfig, type WatchConfigResolved, type WatchEvent, clearDeviceInfoCache, collectDeviceInfo, createConsoleBreadcrumb, createCustomBreadcrumb, createHttpBreadcrumb, createNavigationBreadcrumb, createUIBreadcrumb, deriveReadableFile, getCachedDeviceInfo, getEnvironment, getPlatform, getSessionId, isUsableFilename, removeGlobalHandlers, resetSession, setSessionId, setupConsoleErrorCapture, setupGlobalHandlers };
package/dist/index.d.ts CHANGED
@@ -481,6 +481,6 @@ declare function isUsableFilename(filename: string): boolean;
481
481
  *
482
482
  * Keep this value updated when making SDK changes.
483
483
  */
484
- declare const SDK_VERSION = "1.0.0";
484
+ declare const SDK_VERSION = "1.1.0";
485
485
 
486
486
  export { type Breadcrumb, type BreadcrumbInput, BreadcrumbManager, type DeviceInfo, type IngestResponse, SDK_VERSION, Watch, WatchClient, type WatchConfig, type WatchConfigResolved, type WatchEvent, clearDeviceInfoCache, collectDeviceInfo, createConsoleBreadcrumb, createCustomBreadcrumb, createHttpBreadcrumb, createNavigationBreadcrumb, createUIBreadcrumb, deriveReadableFile, getCachedDeviceInfo, getEnvironment, getPlatform, getSessionId, isUsableFilename, removeGlobalHandlers, resetSession, setSessionId, setupConsoleErrorCapture, setupGlobalHandlers };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var t=require("react-native"),e=require("react"),s=require("react/jsx-runtime");function i(t){return t&&t.t?t:{default:t}}var r=i(e),n=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),o=null;try{o=n("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function a(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 r=await i.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,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(){o&&(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(o)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(o)try{if(0===this.queue.length)return void await o.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await o.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!o)return;let t=null;try{t=await o.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(a);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await o.removeItem(this.storageKey)}catch{}return}s=e.events.filter(a)}if(0===s.length)return void await o.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await o.removeItem(this.storageKey)}catch{}}}},u=null;function l(){return u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=n("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),u}function d(){const e=t.Platform.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function f(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function p(){const e=function(){try{return n("expo-device")}catch{return null}}(),s=function(){try{return n("expo-constants").default}catch{return null}}(),i=function(){try{return n("expo-localization")}catch{return null}}(),r=d(),o={};try{const{width:e,height:s,scale:i}=t.Dimensions.get("window");o.screenWidth=Math.round(e),o.screenHeight=Math.round(s),o.screenScale=i}catch{}if(o.osName=t.Platform.OS,t.Platform.Version&&(o.osVersion=String(t.Platform.Version)),e&&"web"!==r)try{e.brand&&(o.brand=e.brand),e.manufacturer&&(o.manufacturer=e.manufacturer),e.modelName&&(o.modelName=e.modelName),e.deviceName&&(o.deviceName=e.deviceName),e.osName&&(o.osName=e.osName),e.osVersion&&(o.osVersion=e.osVersion),e.osBuildId&&(o.osBuildId=e.osBuildId),e.platformApiLevel&&(o.platformApiLevel=e.platformApiLevel),"boolean"==typeof e.isDevice&&(o.isDevice=e.isDevice)}catch{}if(s)try{const t=s.expoConfig||s.manifest;t?.version&&(o.appVersion=t.version),s.expoVersion&&(o.expoVersion=s.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(i)try{i.locale&&(o.locale=i.locale),i.timezone&&(o.timezone=i.timezone)}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 y=null;function w(){return y||(y=p()),y}var m=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))}},g="1.0.0";function v(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 D(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return D(e)}function C(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function D(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 b=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||f(),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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new m(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),o=this.config.beforeSend(n);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){const s=w(),i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:d(),sessionId:l(),deviceInfo:s,sdkVersion:g,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=v(s[1]);t&&C(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 r=!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),r=!0)}if(r&&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)}};b.instance=null;var E=b,x={init:t=>E.init(t),getInstance:()=>E.getInstance(),captureError(t,e){E.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){E.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){E.getInstance()?.addBreadcrumb(t)},setContext(t,e){E.getInstance()?.setContext(t,e)},setUser(t){E.getInstance()?.setUser(t)},flush:t=>E.getInstance()?.flush(t)||Promise.resolve()},_=!1,W=null,$=null,S=null,k=null,I=null,q=null,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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=E.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&e?n?s.jsx(n,{error:e,errorInfo:i,resetError:this.resetError}):null:r}};exports.BreadcrumbManager=m,exports.SDK_VERSION=g,exports.Watch=x,exports.WatchClient=E,exports.WatchErrorBoundary=M,exports.clearDeviceInfoCache=function(){y=null},exports.collectDeviceInfo=p,exports.createConsoleBreadcrumb=function(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}},exports.createCustomBreadcrumb=function(t,e){return{type:"custom",message:t,data:e}},exports.createHttpBreadcrumb=function(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}},exports.createNavigationBreadcrumb=function(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}},exports.createUIBreadcrumb=function(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}},exports.deriveReadableFile=v,exports.getCachedDeviceInfo=w,exports.getEnvironment=f,exports.getPlatform=d,exports.getSessionId=l,exports.isUsableFilename=C,exports.removeGlobalHandlers=function(){if(_){if("web"===t.Platform.OS&&"undefined"!=typeof window)window.onerror=W,W=null,S&&(window.removeEventListener("beforeunload",S),S=null),k&&(window.removeEventListener("pagehide",k),k=null),I&&(window.removeEventListener("unhandledrejection",I),I=null);else{const t=global.ErrorUtils;t&&$&&(t.setGlobalHandler($),$=null),q&&(q.remove(),q=null)}_=!1}},exports.resetSession=function(){u=null},exports.setSessionId=function(t){u=t},exports.setupConsoleErrorCapture=function(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const r=s.find(t=>t instanceof Error);if(r)i=r;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:!r})}},exports.setupGlobalHandlers=function(e){_?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.Platform.OS?function(t){"undefined"!=typeof window&&(W=window.onerror,window.onerror=(e,s,i,r,n)=>{W&&W(e,s,i,r,n);const o=n||new Error("string"==typeof e?e:"Unknown error");return!n&&s&&(o.filename=s,o.lineno=i,o.colno=r),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:r}),!1},I=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",I),S=()=>{t.flush({keepalive:!0}).catch(()=>{})},k=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",S),window.addEventListener("pagehide",k))}(e):function(e){try{q=t.AppState.addEventListener("change",t=>{"background"!==t&&"inactive"!==t||e.flush().catch(()=>{})})}catch{}const s=global.ErrorUtils;s?($=s.getGlobalHandler(),s.setGlobalHandler((t,s)=>{e.captureError(t,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&e.flush().catch(()=>{}),$&&$(t,s)})):e.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),_=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))},exports.withWatchErrorBoundary=function(t,e){const i=t.displayName||t.name||"Component",r=i=>s.jsx(M,{...e,children:s.jsx(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r};
1
+ var t=require("react-native"),e=require("react"),s=require("react/jsx-runtime");function i(t){return t&&t.t?t:{default:t}}var r=i(e),n=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),o=null;try{o=n("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function a(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 r=await i.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,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(){o&&(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(o)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(o)try{if(0===this.queue.length)return void await o.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await o.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!o)return;let t=null;try{t=await o.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(a);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await o.removeItem(this.storageKey)}catch{}return}s=e.events.filter(a)}if(0===s.length)return void await o.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await o.removeItem(this.storageKey)}catch{}}}},u=null;function l(){return u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=n("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),u}function d(){const e=t.Platform.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function f(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function p(){const e=function(){try{return n("expo-device")}catch{return null}}(),s=function(){try{return n("expo-constants").default}catch{return null}}(),i=function(){try{return n("expo-localization")}catch{return null}}(),r=d(),o={};try{const{width:e,height:s,scale:i}=t.Dimensions.get("window");o.screenWidth=Math.round(e),o.screenHeight=Math.round(s),o.screenScale=i}catch{}if(o.osName=t.Platform.OS,t.Platform.Version&&(o.osVersion=String(t.Platform.Version)),e&&"web"!==r)try{e.brand&&(o.brand=e.brand),e.manufacturer&&(o.manufacturer=e.manufacturer),e.modelName&&(o.modelName=e.modelName),e.deviceName&&(o.deviceName=e.deviceName),e.osName&&(o.osName=e.osName),e.osVersion&&(o.osVersion=e.osVersion),e.osBuildId&&(o.osBuildId=e.osBuildId),e.platformApiLevel&&(o.platformApiLevel=e.platformApiLevel),"boolean"==typeof e.isDevice&&(o.isDevice=e.isDevice)}catch{}if(s)try{const t=s.expoConfig||s.manifest;t?.version&&(o.appVersion=t.version),s.expoVersion&&(o.expoVersion=s.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(i)try{i.locale&&(o.locale=i.locale),i.timezone&&(o.timezone=i.timezone)}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 y=null;function w(){return y||(y=p()),y}var m=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))}},g="1.1.0";function v(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 D(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return D(e)}function C(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function D(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 b=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||f(),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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new m(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),o=this.config.beforeSend(n);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){const s=w(),i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:d(),sessionId:l(),deviceInfo:s,sdkVersion:g,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=v(s[1]);t&&C(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 r=!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),r=!0)}if(r&&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)}};b.instance=null;var E=b,x={init:t=>E.init(t),getInstance:()=>E.getInstance(),captureError(t,e){E.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){E.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){E.getInstance()?.addBreadcrumb(t)},setContext(t,e){E.getInstance()?.setContext(t,e)},setUser(t){E.getInstance()?.setUser(t)},flush:t=>E.getInstance()?.flush(t)||Promise.resolve()},_=!1,W=null,$=null,S=null,k=null,I=null,q=null,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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=E.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&e?n?s.jsx(n,{error:e,errorInfo:i,resetError:this.resetError}):null:r}};exports.BreadcrumbManager=m,exports.SDK_VERSION=g,exports.Watch=x,exports.WatchClient=E,exports.WatchErrorBoundary=M,exports.clearDeviceInfoCache=function(){y=null},exports.collectDeviceInfo=p,exports.createConsoleBreadcrumb=function(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}},exports.createCustomBreadcrumb=function(t,e){return{type:"custom",message:t,data:e}},exports.createHttpBreadcrumb=function(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}},exports.createNavigationBreadcrumb=function(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}},exports.createUIBreadcrumb=function(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}},exports.deriveReadableFile=v,exports.getCachedDeviceInfo=w,exports.getEnvironment=f,exports.getPlatform=d,exports.getSessionId=l,exports.isUsableFilename=C,exports.removeGlobalHandlers=function(){if(_){if("web"===t.Platform.OS&&"undefined"!=typeof window)window.onerror=W,W=null,S&&(window.removeEventListener("beforeunload",S),S=null),k&&(window.removeEventListener("pagehide",k),k=null),I&&(window.removeEventListener("unhandledrejection",I),I=null);else{const t=global.ErrorUtils;t&&$&&(t.setGlobalHandler($),$=null),q&&(q.remove(),q=null)}_=!1}},exports.resetSession=function(){u=null},exports.setSessionId=function(t){u=t},exports.setupConsoleErrorCapture=function(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const r=s.find(t=>t instanceof Error);if(r)i=r;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:!r})}},exports.setupGlobalHandlers=function(e){_?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.Platform.OS?function(t){"undefined"!=typeof window&&(W=window.onerror,window.onerror=(e,s,i,r,n)=>{W&&W(e,s,i,r,n);const o=n||new Error("string"==typeof e?e:"Unknown error");return!n&&s&&(o.filename=s,o.lineno=i,o.colno=r),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:r}),!1},I=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",I),S=()=>{t.flush({keepalive:!0}).catch(()=>{})},k=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",S),window.addEventListener("pagehide",k))}(e):function(e){try{q=t.AppState.addEventListener("change",t=>{"background"!==t&&"inactive"!==t||e.flush().catch(()=>{})})}catch{}const s=global.ErrorUtils;s?($=s.getGlobalHandler(),s.setGlobalHandler((t,s)=>{e.captureError(t,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&e.flush().catch(()=>{}),$&&$(t,s)})):e.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),_=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))},exports.withWatchErrorBoundary=function(t,e){const i=t.displayName||t.name||"Component",r=i=>s.jsx(M,{...e,children:s.jsx(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{Platform as t,Dimensions as e,AppState as i}from"react-native";import s from"react";import{jsx as n}from"react/jsx-runtime";var r=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),o=null;try{o=r("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function a(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 i=0;i<t.length;i++)e=(e<<5)+e^t.charCodeAt(i);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),i=t?.keepalive?Math.min(e,10):e,s=this.queue.slice(0,i);try{await this.doFlush(s,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(s.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 i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};e?.keepalive&&(t.keepalive=!0);const s=await fetch(this.config.endpoint,t);if(401===s.status||403===s.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:s.status}),new Error("Invalid API key");if(429===s.status){const t=s.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(!s.ok)throw this.applyBackoff(),new Error(`HTTP ${s.status}`);const n=await s.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(){o&&(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(o)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(o)try{if(0===this.queue.length)return void await o.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await o.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!o)return;let t=null;try{t=await o.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let i=[];if(Array.isArray(e))i=e.filter(a);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await o.removeItem(this.storageKey)}catch{}return}i=e.events.filter(a)}if(0===i.length)return void await o.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 o.removeItem(this.storageKey)}catch{}}}},u=null;function l(){return u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=r("expo-crypto");if(e?.getRandomValues){const i=new Uint8Array(8);e.getRandomValues(i);let s="";for(let e=0;e<8;e++)s+=t.charAt(i[e]%36);return s}}catch{}if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let i="";for(let s=0;s<8;s++)i+=t.charAt(e[s]%36);return i}let e="";for(let i=0;i<8;i++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),u}function d(){u=null}function f(t){u=t}function p(){const e=t.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function y(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function w(){const i=function(){try{return r("expo-device")}catch{return null}}(),s=function(){try{return r("expo-constants").default}catch{return null}}(),n=function(){try{return r("expo-localization")}catch{return null}}(),o=p(),h={};try{const{width:t,height:i,scale:s}=e.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(i),h.screenScale=s}catch{}if(h.osName=t.OS,t.Version&&(h.osVersion=String(t.Version)),i&&"web"!==o)try{i.brand&&(h.brand=i.brand),i.manufacturer&&(h.manufacturer=i.manufacturer),i.modelName&&(h.modelName=i.modelName),i.deviceName&&(h.deviceName=i.deviceName),i.osName&&(h.osName=i.osName),i.osVersion&&(h.osVersion=i.osVersion),i.osBuildId&&(h.osBuildId=i.osBuildId),i.platformApiLevel&&(h.platformApiLevel=i.platformApiLevel),"boolean"==typeof i.isDevice&&(h.isDevice=i.isDevice)}catch{}if(s)try{const t=s.expoConfig||s.manifest;t?.version&&(h.appVersion=t.version),s.expoVersion&&(h.expoVersion=s.expoVersion),t?.name&&(h.appName=t.name),"ios"===o&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===o&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(n)try{n.locale&&(h.locale=n.locale),n.timezone&&(h.timezone=n.timezone)}catch{}if("web"===o&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(h.browserName=e[1],h.browserVersion=e[2])}}catch{}return h}var m=null;function g(){return m||(m=w()),m}function v(){m=null}var C=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 D(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function b(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function E(t,e,i){return{type:"http",message:`${t} ${e}${i?` [${i}]`:""}`,data:{method:t,url:e,statusCode:i}}}function _(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function W(t,e){return{type:"custom",message:t,data:e}}var $="1.0.0";function S(t){const e=function(t){if(!t)return t;let e=t;const i=e.indexOf("?");i>=0&&(e=e.slice(0,i));const s=e.indexOf("&");for(s>=0&&(e=e.slice(0,s));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 I(e.slice(i+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return I(e)}function k(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function I(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 x=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||y(),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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new C(this.config.maxBreadcrumbs),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 i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const s=this.ensureStack(t,i),n=this.getErrorKey(s);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",s.message));this.markErrorAsSeen(n);const r=this.buildEvent(s,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 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)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){const i=g(),s={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:p(),sessionId:l(),deviceInfo:i,sdkVersion:$,extra:{...this.context,...e,...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=S(i[1]);t&&k(t)&&(s.filename=t),s.lineno=parseInt(i[2],10),s.colno=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)}return s}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 i=e.split("\n"),s=[];s.push(`${t.name}: ${t.message}`);let n=!1;for(const t of i){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 "))&&(s.push(e),n=!0)}if(n&&s.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=s.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 i=Date.now()-e<=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(),e=this.config.dedupWindowMs;for(const[i,s]of this.recentErrors.entries())t-s>e&&this.recentErrors.delete(i)}};x.instance=null;var M=x,A={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()},q=!1,P=null,T=null,U=null,R=null,j=null,B=null;function z(e){q?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.OS?function(t){"undefined"!=typeof window&&(P=window.onerror,window.onerror=(e,i,s,n,r)=>{P&&P(e,i,s,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&i&&(o.filename=i,o.lineno=s,o.colno=n),t.captureError(o,{source:"global.onerror",filename:i,lineno:s,colno:n}),!1},j=e=>{if(e.defaultPrevented)return;const i=e.reason instanceof Error?e.reason:new Error(String(e.reason??"Unhandled Promise rejection"));t.captureError(i,{source:"global.unhandledrejection",isPromiseRejection:!0})},window.addEventListener("unhandledrejection",j),U=()=>{t.flush({keepalive:!0}).catch(()=>{})},R=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",U),window.addEventListener("pagehide",R))}(e):function(t){try{B=i.addEventListener("change",e=>{"background"!==e&&"inactive"!==e||t.flush().catch(()=>{})})}catch{}const e=global.ErrorUtils;e?(T=e.getGlobalHandler(),e.setGlobalHandler((e,i)=>{t.captureError(e,{source:"ErrorUtils.globalHandler",isFatal:i}),i&&t.flush().catch(()=>{}),T&&T(e,i)})):t.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),q=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))}function F(t){const e=console.error;console.error=(...i)=>{let s;e.apply(console,i);const n=i.find(t=>t instanceof Error);if(n)s=n;else{const t=i.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(s=new Error(t),s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(s,{source:"console.error",synthetic:!n})}}function N(){if(q){if("web"===t.OS&&"undefined"!=typeof window)window.onerror=P,P=null,U&&(window.removeEventListener("beforeunload",U),U=null),R&&(window.removeEventListener("pagehide",R),R=null),j&&(window.removeEventListener("unhandledrejection",j),j=null);else{const t=global.ErrorUtils;t&&T&&(t.setGlobalHandler(T),T=null),B&&(B.remove(),B=null)}q=!1}}var O=class extends s.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:i=!0,onError:s}=this.props;if(i){const i=M.getInstance();i&&i.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}s&&s(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:s,fallback:r}=this.props;return t&&e?r?n(r,{error:e,errorInfo:i,resetError:this.resetError}):null:s}};function L(t,e){const i=t.displayName||t.name||"Component",s=i=>n(O,{...e,children:n(t,{...i})});return s.displayName=`withWatchErrorBoundary(${i})`,s}export{C as BreadcrumbManager,$ as SDK_VERSION,A as Watch,M as WatchClient,O as WatchErrorBoundary,v as clearDeviceInfoCache,w as collectDeviceInfo,_ as createConsoleBreadcrumb,W as createCustomBreadcrumb,E as createHttpBreadcrumb,D as createNavigationBreadcrumb,b as createUIBreadcrumb,S as deriveReadableFile,g as getCachedDeviceInfo,y as getEnvironment,p as getPlatform,l as getSessionId,k as isUsableFilename,N as removeGlobalHandlers,d as resetSession,f as setSessionId,F as setupConsoleErrorCapture,z as setupGlobalHandlers,L as withWatchErrorBoundary};
1
+ import{Platform as t,Dimensions as e,AppState as i}from"react-native";import s from"react";import{jsx as n}from"react/jsx-runtime";var r=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),o=null;try{o=r("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function a(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 i=0;i<t.length;i++)e=(e<<5)+e^t.charCodeAt(i);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),i=t?.keepalive?Math.min(e,10):e,s=this.queue.slice(0,i);try{await this.doFlush(s,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(s.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 i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};e?.keepalive&&(t.keepalive=!0);const s=await fetch(this.config.endpoint,t);if(401===s.status||403===s.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:s.status}),new Error("Invalid API key");if(429===s.status){const t=s.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(!s.ok)throw this.applyBackoff(),new Error(`HTTP ${s.status}`);const n=await s.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(){o&&(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(o)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(o)try{if(0===this.queue.length)return void await o.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await o.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!o)return;let t=null;try{t=await o.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let i=[];if(Array.isArray(e))i=e.filter(a);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await o.removeItem(this.storageKey)}catch{}return}i=e.events.filter(a)}if(0===i.length)return void await o.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 o.removeItem(this.storageKey)}catch{}}}},u=null;function l(){return u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=r("expo-crypto");if(e?.getRandomValues){const i=new Uint8Array(8);e.getRandomValues(i);let s="";for(let e=0;e<8;e++)s+=t.charAt(i[e]%36);return s}}catch{}if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let i="";for(let s=0;s<8;s++)i+=t.charAt(e[s]%36);return i}let e="";for(let i=0;i<8;i++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),u}function d(){u=null}function f(t){u=t}function p(){const e=t.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function y(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function w(){const i=function(){try{return r("expo-device")}catch{return null}}(),s=function(){try{return r("expo-constants").default}catch{return null}}(),n=function(){try{return r("expo-localization")}catch{return null}}(),o=p(),h={};try{const{width:t,height:i,scale:s}=e.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(i),h.screenScale=s}catch{}if(h.osName=t.OS,t.Version&&(h.osVersion=String(t.Version)),i&&"web"!==o)try{i.brand&&(h.brand=i.brand),i.manufacturer&&(h.manufacturer=i.manufacturer),i.modelName&&(h.modelName=i.modelName),i.deviceName&&(h.deviceName=i.deviceName),i.osName&&(h.osName=i.osName),i.osVersion&&(h.osVersion=i.osVersion),i.osBuildId&&(h.osBuildId=i.osBuildId),i.platformApiLevel&&(h.platformApiLevel=i.platformApiLevel),"boolean"==typeof i.isDevice&&(h.isDevice=i.isDevice)}catch{}if(s)try{const t=s.expoConfig||s.manifest;t?.version&&(h.appVersion=t.version),s.expoVersion&&(h.expoVersion=s.expoVersion),t?.name&&(h.appName=t.name),"ios"===o&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===o&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(n)try{n.locale&&(h.locale=n.locale),n.timezone&&(h.timezone=n.timezone)}catch{}if("web"===o&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(h.browserName=e[1],h.browserVersion=e[2])}}catch{}return h}var m=null;function g(){return m||(m=w()),m}function v(){m=null}var C=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 D(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function b(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function E(t,e,i){return{type:"http",message:`${t} ${e}${i?` [${i}]`:""}`,data:{method:t,url:e,statusCode:i}}}function _(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function W(t,e){return{type:"custom",message:t,data:e}}var $="1.1.0";function S(t){const e=function(t){if(!t)return t;let e=t;const i=e.indexOf("?");i>=0&&(e=e.slice(0,i));const s=e.indexOf("&");for(s>=0&&(e=e.slice(0,s));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 I(e.slice(i+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return I(e)}function k(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function I(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 x=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||y(),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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new C(this.config.maxBreadcrumbs),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 i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const s=this.ensureStack(t,i),n=this.getErrorKey(s);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",s.message));this.markErrorAsSeen(n);const r=this.buildEvent(s,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 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)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){const i=g(),s={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:p(),sessionId:l(),deviceInfo:i,sdkVersion:$,extra:{...this.context,...e,...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=S(i[1]);t&&k(t)&&(s.filename=t),s.lineno=parseInt(i[2],10),s.colno=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)}return s}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 i=e.split("\n"),s=[];s.push(`${t.name}: ${t.message}`);let n=!1;for(const t of i){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 "))&&(s.push(e),n=!0)}if(n&&s.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=s.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 i=Date.now()-e<=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(),e=this.config.dedupWindowMs;for(const[i,s]of this.recentErrors.entries())t-s>e&&this.recentErrors.delete(i)}};x.instance=null;var M=x,A={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()},q=!1,P=null,T=null,U=null,R=null,j=null,B=null;function z(e){q?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.OS?function(t){"undefined"!=typeof window&&(P=window.onerror,window.onerror=(e,i,s,n,r)=>{P&&P(e,i,s,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&i&&(o.filename=i,o.lineno=s,o.colno=n),t.captureError(o,{source:"global.onerror",filename:i,lineno:s,colno:n}),!1},j=e=>{if(e.defaultPrevented)return;const i=e.reason instanceof Error?e.reason:new Error(String(e.reason??"Unhandled Promise rejection"));t.captureError(i,{source:"global.unhandledrejection",isPromiseRejection:!0})},window.addEventListener("unhandledrejection",j),U=()=>{t.flush({keepalive:!0}).catch(()=>{})},R=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",U),window.addEventListener("pagehide",R))}(e):function(t){try{B=i.addEventListener("change",e=>{"background"!==e&&"inactive"!==e||t.flush().catch(()=>{})})}catch{}const e=global.ErrorUtils;e?(T=e.getGlobalHandler(),e.setGlobalHandler((e,i)=>{t.captureError(e,{source:"ErrorUtils.globalHandler",isFatal:i}),i&&t.flush().catch(()=>{}),T&&T(e,i)})):t.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),q=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))}function F(t){const e=console.error;console.error=(...i)=>{let s;e.apply(console,i);const n=i.find(t=>t instanceof Error);if(n)s=n;else{const t=i.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(s=new Error(t),s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(s,{source:"console.error",synthetic:!n})}}function N(){if(q){if("web"===t.OS&&"undefined"!=typeof window)window.onerror=P,P=null,U&&(window.removeEventListener("beforeunload",U),U=null),R&&(window.removeEventListener("pagehide",R),R=null),j&&(window.removeEventListener("unhandledrejection",j),j=null);else{const t=global.ErrorUtils;t&&T&&(t.setGlobalHandler(T),T=null),B&&(B.remove(),B=null)}q=!1}}var O=class extends s.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:i=!0,onError:s}=this.props;if(i){const i=M.getInstance();i&&i.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}s&&s(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:s,fallback:r}=this.props;return t&&e?r?n(r,{error:e,errorInfo:i,resetError:this.resetError}):null:s}};function L(t,e){const i=t.displayName||t.name||"Component",s=i=>n(O,{...e,children:n(t,{...i})});return s.displayName=`withWatchErrorBoundary(${i})`,s}export{C as BreadcrumbManager,$ as SDK_VERSION,A as Watch,M as WatchClient,O as WatchErrorBoundary,v as clearDeviceInfoCache,w as collectDeviceInfo,_ as createConsoleBreadcrumb,W as createCustomBreadcrumb,E as createHttpBreadcrumb,D as createNavigationBreadcrumb,b as createUIBreadcrumb,S as deriveReadableFile,g as getCachedDeviceInfo,y as getEnvironment,p as getPlatform,l as getSessionId,k as isUsableFilename,N as removeGlobalHandlers,d as resetSession,f as setSessionId,F as setupConsoleErrorCapture,z as setupGlobalHandlers,L as withWatchErrorBoundary};
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- var t=require("react"),e=require("react-native"),s=require("react/jsx-runtime");function i(t){return t&&t.t?t:{default:t}}var r=i(t),n=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),h=null;try{h=n("@react-native-async-storage/async-storage").default}catch{}function o(t){return"object"==typeof t&&null!==t}function a(t){return!!o(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 r=await i.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,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(){h&&(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(h)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(h)try{if(0===this.queue.length)return void await h.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await h.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!h)return;let t=null;try{t=await h.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(a);else if(o(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await h.removeItem(this.storageKey)}catch{}return}s=e.events.filter(a)}if(0===s.length)return void await h.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await h.removeItem(this.storageKey)}catch{}}}},u=null;function l(){const t=e.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var f=null,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 p(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 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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new d(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),h=this.config.beforeSend(n);h?this.transport.send(h):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,s){const i=(f||(f=function(){const t=function(){try{return n("expo-device")}catch{return null}}(),s=function(){try{return n("expo-constants").default}catch{return null}}(),i=function(){try{return n("expo-localization")}catch{return null}}(),r=l(),h={};try{const{width:t,height:s,scale:i}=e.Dimensions.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(s),h.screenScale=i}catch{}if(h.osName=e.Platform.OS,e.Platform.Version&&(h.osVersion=String(e.Platform.Version)),t&&"web"!==r)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(s)try{const t=s.expoConfig||s.manifest;t?.version&&(h.appVersion=t.version),s.expoVersion&&(h.expoVersion=s.expoVersion),t?.name&&(h.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(i)try{i.locale&&(h.locale=i.locale),i.timezone&&(h.timezone=i.timezone)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(h.browserName=e[1],h.browserVersion=e[2])}}catch{}return h}()),f),r={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:l(),sessionId:(u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=n("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),u),deviceInfo:i,sdkVersion:"1.0.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let e=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(e||(e=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),e){const t=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 p(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return p(e)}(e[1]);!t||!(h=t)||h.includes("node_modules")||/\.bundle(\/|$|:)/.test(h)||/bundle(\.js|\.map)$/.test(h)||h.startsWith("[native code]")||h.startsWith("native ")||(r.filename=t),r.lineno=parseInt(e[2],10),r.colno=parseInt(e[3],10)}void 0===r.lineno&&"number"==typeof s?.lineno&&(r.lineno=s.lineno),void 0===r.colno&&"number"==typeof s?.colno&&(r.colno=s.colno)}var h;return r}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 r=!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),r=!0)}if(r&&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)}};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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=w.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&e?n?s.jsx(n,{error:e,errorInfo:i,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=m,exports.withWatchErrorBoundary=function(t,e){const i=t.displayName||t.name||"Component",r=i=>s.jsx(m,{...e,children:s.jsx(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r};
1
+ var t=require("react"),e=require("react-native"),s=require("react/jsx-runtime");function i(t){return t&&t.t?t:{default:t}}var r=i(t),n=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),h=null;try{h=n("@react-native-async-storage/async-storage").default}catch{}function o(t){return"object"==typeof t&&null!==t}function a(t){return!!o(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var c=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.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 r=await i.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,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(){h&&(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(h)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(h)try{if(0===this.queue.length)return void await h.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await h.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!h)return;let t=null;try{t=await h.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(a);else if(o(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await h.removeItem(this.storageKey)}catch{}return}s=e.events.filter(a)}if(0===s.length)return void await h.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await h.removeItem(this.storageKey)}catch{}}}},u=null;function l(){const t=e.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var f=null,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 p(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 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},this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new d(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),h=this.config.beforeSend(n);h?this.transport.send(h):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,s){const i=(f||(f=function(){const t=function(){try{return n("expo-device")}catch{return null}}(),s=function(){try{return n("expo-constants").default}catch{return null}}(),i=function(){try{return n("expo-localization")}catch{return null}}(),r=l(),h={};try{const{width:t,height:s,scale:i}=e.Dimensions.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(s),h.screenScale=i}catch{}if(h.osName=e.Platform.OS,e.Platform.Version&&(h.osVersion=String(e.Platform.Version)),t&&"web"!==r)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(s)try{const t=s.expoConfig||s.manifest;t?.version&&(h.appVersion=t.version),s.expoVersion&&(h.expoVersion=s.expoVersion),t?.name&&(h.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(i)try{i.locale&&(h.locale=i.locale),i.timezone&&(h.timezone=i.timezone)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(h.browserName=e[1],h.browserVersion=e[2])}}catch{}return h}()),f),r={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:l(),sessionId:(u||(u=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=n("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),u),deviceInfo:i,sdkVersion:"1.1.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let e=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(e||(e=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),e){const t=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 p(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return p(e)}(e[1]);!t||!(h=t)||h.includes("node_modules")||/\.bundle(\/|$|:)/.test(h)||/bundle(\.js|\.map)$/.test(h)||h.startsWith("[native code]")||h.startsWith("native ")||(r.filename=t),r.lineno=parseInt(e[2],10),r.colno=parseInt(e[3],10)}void 0===r.lineno&&"number"==typeof s?.lineno&&(r.lineno=s.lineno),void 0===r.colno&&"number"==typeof s?.colno&&(r.colno=s.colno)}var h;return r}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 r=!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),r=!0)}if(r&&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)}};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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=w.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&e?n?s.jsx(n,{error:e,errorInfo:i,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=m,exports.withWatchErrorBoundary=function(t,e){const i=t.displayName||t.name||"Component",r=i=>s.jsx(m,{...e,children:s.jsx(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r};
package/dist/react.mjs CHANGED
@@ -1 +1 @@
1
- import t from"react";import{Platform as e,Dimensions as s}from"react-native";import{jsx as i}from"react/jsx-runtime";var r=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),n=null;try{n=r("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function o(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var a=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.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 r=await i.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,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(){n&&(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(n)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(n)try{if(0===this.queue.length)return void await n.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await n.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!n)return;let t=null;try{t=await n.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(o);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await n.removeItem(this.storageKey)}catch{}return}s=e.events.filter(o)}if(0===s.length)return void await n.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await n.removeItem(this.storageKey)}catch{}}}},c=null;function u(){const t=e.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null,f=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 d(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 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},this.transport=new a({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new f(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),h=this.config.beforeSend(n);h?this.transport.send(h):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,i){const n=(l||(l=function(){const t=function(){try{return r("expo-device")}catch{return null}}(),i=function(){try{return r("expo-constants").default}catch{return null}}(),n=function(){try{return r("expo-localization")}catch{return null}}(),h=u(),o={};try{const{width:t,height:e,scale:i}=s.get("window");o.screenWidth=Math.round(t),o.screenHeight=Math.round(e),o.screenScale=i}catch{}if(o.osName=e.OS,e.Version&&(o.osVersion=String(e.Version)),t&&"web"!==h)try{t.brand&&(o.brand=t.brand),t.manufacturer&&(o.manufacturer=t.manufacturer),t.modelName&&(o.modelName=t.modelName),t.deviceName&&(o.deviceName=t.deviceName),t.osName&&(o.osName=t.osName),t.osVersion&&(o.osVersion=t.osVersion),t.osBuildId&&(o.osBuildId=t.osBuildId),t.platformApiLevel&&(o.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(o.isDevice=t.isDevice)}catch{}if(i)try{const t=i.expoConfig||i.manifest;t?.version&&(o.appVersion=t.version),i.expoVersion&&(o.expoVersion=i.expoVersion),t?.name&&(o.appName=t.name),"ios"===h&&t?.ios?.bundleIdentifier?o.bundleId=t.ios.bundleIdentifier:"android"===h&&t?.android?.package&&(o.bundleId=t.android.package)}catch{}if(n)try{n.locale&&(o.locale=n.locale),n.timezone&&(o.timezone=n.timezone)}catch{}if("web"===h&&"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}()),l),h={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:(c||(c=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=r("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),c),deviceInfo:n,sdkVersion:"1.0.0",extra:{...this.context,...i,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let e=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(e||(e=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),e){const t=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 d(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return d(e)}(e[1]);!t||!(o=t)||o.includes("node_modules")||/\.bundle(\/|$|:)/.test(o)||/bundle(\.js|\.map)$/.test(o)||o.startsWith("[native code]")||o.startsWith("native ")||(h.filename=t),h.lineno=parseInt(e[2],10),h.colno=parseInt(e[3],10)}void 0===h.lineno&&"number"==typeof i?.lineno&&(h.lineno=i.lineno),void 0===h.colno&&"number"==typeof i?.colno&&(h.colno=i.colno)}var o;return h}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 r=!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),r=!0)}if(r&&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)}};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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=y.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:r,fallback:n}=this.props;return t&&e?n?i(n,{error:e,errorInfo:s,resetError:this.resetError}):null:r}};function w(t,e){const s=t.displayName||t.name||"Component",r=s=>i(m,{...e,children:i(t,{...s})});return r.displayName=`withWatchErrorBoundary(${s})`,r}export{m as WatchErrorBoundary,w as withWatchErrorBoundary};
1
+ import t from"react";import{Platform as e,Dimensions as s}from"react-native";import{jsx as i}from"react/jsx-runtime";var r=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),n=null;try{n=r("@react-native-async-storage/async-storage").default}catch{}function h(t){return"object"==typeof t&&null!==t}function o(t){return!!h(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var a=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.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 r=await i.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,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(){n&&(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(n)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){if(n)try{if(0===this.queue.length)return void await n.removeItem(this.storageKey);const t={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await n.setItem(this.storageKey,JSON.stringify(t))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){if(!n)return;let t=null;try{t=await n.getItem(this.storageKey)}catch{return}if(t)try{const e=JSON.parse(t);let s=[];if(Array.isArray(e))s=e.filter(o);else if(h(e)&&1===e.v&&Array.isArray(e.events)){const t=e.apiKeyHash;if(t&&t!==this.apiKeyHash){try{await n.removeItem(this.storageKey)}catch{}return}s=e.events.filter(o)}if(0===s.length)return void await n.removeItem(this.storageKey);this.queue=[...s,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${s.length} queued event(s)`)}catch{try{await n.removeItem(this.storageKey)}catch{}}}},c=null;function u(){const t=e.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null,f=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 d(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 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},this.transport=new a({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug}),this.breadcrumbs=new f(this.config.maxBreadcrumbs),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),r=this.getErrorKey(i);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(r);const n=this.buildEvent(i,e),h=this.config.beforeSend(n);h?this.transport.send(h):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,i){const n=(l||(l=function(){const t=function(){try{return r("expo-device")}catch{return null}}(),i=function(){try{return r("expo-constants").default}catch{return null}}(),n=function(){try{return r("expo-localization")}catch{return null}}(),h=u(),o={};try{const{width:t,height:e,scale:i}=s.get("window");o.screenWidth=Math.round(t),o.screenHeight=Math.round(e),o.screenScale=i}catch{}if(o.osName=e.OS,e.Version&&(o.osVersion=String(e.Version)),t&&"web"!==h)try{t.brand&&(o.brand=t.brand),t.manufacturer&&(o.manufacturer=t.manufacturer),t.modelName&&(o.modelName=t.modelName),t.deviceName&&(o.deviceName=t.deviceName),t.osName&&(o.osName=t.osName),t.osVersion&&(o.osVersion=t.osVersion),t.osBuildId&&(o.osBuildId=t.osBuildId),t.platformApiLevel&&(o.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(o.isDevice=t.isDevice)}catch{}if(i)try{const t=i.expoConfig||i.manifest;t?.version&&(o.appVersion=t.version),i.expoVersion&&(o.expoVersion=i.expoVersion),t?.name&&(o.appName=t.name),"ios"===h&&t?.ios?.bundleIdentifier?o.bundleId=t.ios.bundleIdentifier:"android"===h&&t?.android?.package&&(o.bundleId=t.android.package)}catch{}if(n)try{n.locale&&(o.locale=n.locale),n.timezone&&(o.timezone=n.timezone)}catch{}if("web"===h&&"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}()),l),h={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:(c||(c=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";try{const e=r("expo-crypto");if(e?.getRandomValues){const s=new Uint8Array(8);e.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}}catch{}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}()}`),c),deviceInfo:n,sdkVersion:"1.1.0",extra:{...this.context,...i,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let e=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(e||(e=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),e){const t=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 d(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return d(e)}(e[1]);!t||!(o=t)||o.includes("node_modules")||/\.bundle(\/|$|:)/.test(o)||/bundle(\.js|\.map)$/.test(o)||o.startsWith("[native code]")||o.startsWith("native ")||(h.filename=t),h.lineno=parseInt(e[2],10),h.colno=parseInt(e[3],10)}void 0===h.lineno&&"number"==typeof i?.lineno&&(h.lineno=i.lineno),void 0===h.colno&&"number"==typeof i?.colno&&(h.colno=i.colno)}var o;return h}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 r=!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),r=!0)}if(r&&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)}};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,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=y.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:r,fallback:n}=this.props;return t&&e?n?i(n,{error:e,errorInfo:s,resetError:this.resetError}):null:r}};function w(t,e){const s=t.displayName||t.name||"Component",r=s=>i(m,{...e,children:i(t,{...s})});return r.displayName=`withWatchErrorBoundary(${s})`,r}export{m as WatchErrorBoundary,w as withWatchErrorBoundary};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catdoes/watch",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Error tracking and monitoring SDK for React Native and Expo apps - by CatDoes Inc.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",