@catdoes/watch 1.1.0 → 1.2.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
@@ -21,13 +21,12 @@ npm install @catdoes/watch
21
21
  yarn add @catdoes/watch
22
22
  ```
23
23
 
24
- ### Peer Dependencies
24
+ ### Optional Integrations
25
25
 
26
- Make sure you have these peer dependencies installed:
26
+ The SDK has no hard dependencies beyond React / React Native:
27
27
 
28
- ```bash
29
- npx expo install @react-native-async-storage/async-storage expo-crypto
30
- ```
28
+ - **Offline queue persistence** — pass an AsyncStorage-compatible object via the `storage` option (see below). Without it, queued events are kept in memory only.
29
+ - **Device metadata** (model, OS build, app version, locale) — enriched automatically when the app uses Expo and has `expo-device` / `expo-constants` / `expo-localization` installed. Read from Expo's native module registry at runtime; never imported by the SDK.
31
30
 
32
31
  ## Quick Start
33
32
 
@@ -36,12 +35,14 @@ npx expo install @react-native-async-storage/async-storage expo-crypto
36
35
  In your app's entry point (e.g., `_layout.tsx` for Expo Router):
37
36
 
38
37
  ```typescript
38
+ import AsyncStorage from "@react-native-async-storage/async-storage";
39
39
  import { Watch, setupGlobalHandlers } from "@catdoes/watch";
40
40
 
41
41
  // Initialize Watch with your API key
42
42
  const watchClient = Watch.init({
43
43
  apiKey: process.env.EXPO_PUBLIC_CATDOES_WATCH_KEY || "",
44
44
  debug: __DEV__, // Enable debug logging in development
45
+ storage: AsyncStorage, // Optional: persist the event queue across launches
45
46
  });
46
47
 
47
48
  // Set up global error handlers
package/dist/index.d.mts CHANGED
@@ -7,6 +7,25 @@ import 'react';
7
7
  * These types define the structure of error events, configuration,
8
8
  * and other data used by the Watch SDK.
9
9
  */
10
+ /**
11
+ * Minimal async key-value storage used to persist the event queue across
12
+ * launches. Compatible with `@react-native-async-storage/async-storage`'s
13
+ * default export and `window.localStorage`-style wrappers.
14
+ *
15
+ * The SDK never imports a storage module itself — dynamic `require()` of
16
+ * optional dependencies is reported as a fatal error by Metro in release
17
+ * builds. Pass an implementation explicitly:
18
+ *
19
+ * ```ts
20
+ * import AsyncStorage from "@react-native-async-storage/async-storage";
21
+ * initCatDoesWatch({ apiKey, storage: AsyncStorage });
22
+ * ```
23
+ */
24
+ interface WatchStorage {
25
+ getItem(key: string): Promise<string | null>;
26
+ setItem(key: string, value: string): Promise<void>;
27
+ removeItem(key: string): Promise<void>;
28
+ }
10
29
  /**
11
30
  * Configuration options for initializing the Watch client.
12
31
  */
@@ -74,6 +93,11 @@ interface WatchConfig {
74
93
  * @default 500
75
94
  */
76
95
  dedupMaxEntries?: number;
96
+ /**
97
+ * Storage implementation used to persist queued events across launches
98
+ * (e.g. AsyncStorage). Persistence is disabled when omitted.
99
+ */
100
+ storage?: WatchStorage;
77
101
  }
78
102
  /**
79
103
  * Required configuration with defaults applied.
@@ -90,6 +114,7 @@ interface WatchConfigResolved {
90
114
  debug: boolean;
91
115
  dedupWindowMs: number;
92
116
  dedupMaxEntries: number;
117
+ storage: WatchStorage | null;
93
118
  }
94
119
  /**
95
120
  * Device and environment information collected automatically.
@@ -481,6 +506,6 @@ declare function isUsableFilename(filename: string): boolean;
481
506
  *
482
507
  * Keep this value updated when making SDK changes.
483
508
  */
484
- declare const SDK_VERSION = "1.1.0";
509
+ declare const SDK_VERSION = "1.2.0";
485
510
 
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 };
511
+ export { type Breadcrumb, type BreadcrumbInput, BreadcrumbManager, type DeviceInfo, type IngestResponse, SDK_VERSION, Watch, WatchClient, type WatchConfig, type WatchConfigResolved, type WatchEvent, type WatchStorage, clearDeviceInfoCache, collectDeviceInfo, createConsoleBreadcrumb, createCustomBreadcrumb, createHttpBreadcrumb, createNavigationBreadcrumb, createUIBreadcrumb, deriveReadableFile, getCachedDeviceInfo, getEnvironment, getPlatform, getSessionId, isUsableFilename, removeGlobalHandlers, resetSession, setSessionId, setupConsoleErrorCapture, setupGlobalHandlers };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,25 @@ import 'react';
7
7
  * These types define the structure of error events, configuration,
8
8
  * and other data used by the Watch SDK.
9
9
  */
10
+ /**
11
+ * Minimal async key-value storage used to persist the event queue across
12
+ * launches. Compatible with `@react-native-async-storage/async-storage`'s
13
+ * default export and `window.localStorage`-style wrappers.
14
+ *
15
+ * The SDK never imports a storage module itself — dynamic `require()` of
16
+ * optional dependencies is reported as a fatal error by Metro in release
17
+ * builds. Pass an implementation explicitly:
18
+ *
19
+ * ```ts
20
+ * import AsyncStorage from "@react-native-async-storage/async-storage";
21
+ * initCatDoesWatch({ apiKey, storage: AsyncStorage });
22
+ * ```
23
+ */
24
+ interface WatchStorage {
25
+ getItem(key: string): Promise<string | null>;
26
+ setItem(key: string, value: string): Promise<void>;
27
+ removeItem(key: string): Promise<void>;
28
+ }
10
29
  /**
11
30
  * Configuration options for initializing the Watch client.
12
31
  */
@@ -74,6 +93,11 @@ interface WatchConfig {
74
93
  * @default 500
75
94
  */
76
95
  dedupMaxEntries?: number;
96
+ /**
97
+ * Storage implementation used to persist queued events across launches
98
+ * (e.g. AsyncStorage). Persistence is disabled when omitted.
99
+ */
100
+ storage?: WatchStorage;
77
101
  }
78
102
  /**
79
103
  * Required configuration with defaults applied.
@@ -90,6 +114,7 @@ interface WatchConfigResolved {
90
114
  debug: boolean;
91
115
  dedupWindowMs: number;
92
116
  dedupMaxEntries: number;
117
+ storage: WatchStorage | null;
93
118
  }
94
119
  /**
95
120
  * Device and environment information collected automatically.
@@ -481,6 +506,6 @@ declare function isUsableFilename(filename: string): boolean;
481
506
  *
482
507
  * Keep this value updated when making SDK changes.
483
508
  */
484
- declare const SDK_VERSION = "1.1.0";
509
+ declare const SDK_VERSION = "1.2.0";
485
510
 
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 };
511
+ export { type Breadcrumb, type BreadcrumbInput, BreadcrumbManager, type DeviceInfo, type IngestResponse, SDK_VERSION, Watch, WatchClient, type WatchConfig, type WatchConfigResolved, type WatchEvent, type WatchStorage, 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.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};
1
+ "use strict";var t=require("react-native"),e=require("react"),s=require("react/jsx-runtime");function i(t){return t&&t.t?t:{default:t}}var n=i(e);function r(t){return"object"==typeof t&&null!==t}function o(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let e=5381;for(let s=0;s<t.length;s++)e=(e<<5)+e^t.charCodeAt(s);return(e>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const e=Math.max(1,this.config.maxBufferSize),s=t?.keepalive?Math.min(e,10):e,i=this.queue.slice(0,s);try{await this.doFlush(i,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(i.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,e){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const s=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:s};e?.keepalive&&(t.keepalive=!0);const i=await fetch(this.config.endpoint,t);if(401===i.status||403===i.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:i.status}),new Error("Invalid API key");if(429===i.status){const t=i.headers.get("Retry-After"),e=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=e,this.backoffUntil=Date.now()+e,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${e}ms`),new Error("Rate limited")}if(!i.ok)throw this.applyBackoff(),new Error(`HTTP ${i.status}`);const n=await i.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${n.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,e){this.disabledReason=t,this.disabledUntil=e===1/0?1/0:Date.now()+Math.max(0,e)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const e={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(e))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let e=null;try{e=await t.getItem(this.storageKey)}catch{return}if(e)try{const s=JSON.parse(e);let i=[];if(Array.isArray(s))i=s.filter(o);else if(r(s)&&1===s.v&&Array.isArray(s.events)){const e=s.apiKeyHash;if(e&&e!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}i=s.events.filter(o)}if(0===i.length)return void await t.removeItem(this.storageKey);this.queue=[...i,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${i.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(){return a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let s="";for(let i=0;i<8;i++)s+=t.charAt(e[i]%36);return s}let e="";for(let s=0;s<8;s++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),a}function l(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}function u(){const e=t.Platform.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function d(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function f(){const e=l("ExpoDevice"),s=l("ExponentConstants"),i=l("ExpoLocalization"),n=u(),r={};try{const{width:e,height:s,scale:i}=t.Dimensions.get("window");r.screenWidth=Math.round(e),r.screenHeight=Math.round(s),r.screenScale=i}catch{}if(r.osName=t.Platform.OS,t.Platform.Version&&(r.osVersion=String(t.Platform.Version)),e&&"web"!==n)try{e.brand&&(r.brand=e.brand),e.manufacturer&&(r.manufacturer=e.manufacturer),e.modelName&&(r.modelName=e.modelName),e.deviceName&&(r.deviceName=e.deviceName),e.osName&&(r.osName=e.osName),e.osVersion&&(r.osVersion=e.osVersion),e.osBuildId&&(r.osBuildId=e.osBuildId),e.platformApiLevel&&(r.platformApiLevel=e.platformApiLevel),"boolean"==typeof e.isDevice&&(r.isDevice=e.isDevice)}catch{}if(s)try{let t=null;"string"==typeof s.manifest?t=JSON.parse(s.manifest):s.manifest&&"object"==typeof s.manifest&&(t=s.manifest),t?.version&&(r.appVersion=t.version),s.expoVersion&&(r.expoVersion=s.expoVersion),t?.name&&(r.appName=t.name),"ios"===n&&t?.ios?.bundleIdentifier?r.bundleId=t.ios.bundleIdentifier:"android"===n&&t?.android?.package&&(r.bundleId=t.android.package)}catch{}if(i)try{const t=i.getLocales?.()?.[0]?.languageTag;t&&(r.locale=t);const e=i.getCalendars?.()?.[0]?.timeZone;e&&(r.timezone=e)}catch{}if("web"===n&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){r.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(r.browserName=e[1],r.browserVersion=e[2])}}catch{}return r}var p=null;function y(){return p||(p=f()),p}var w=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))}},m="1.2.0";function g(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 b(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return b(e)}function v(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function b(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 C=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||d(),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new w(this.config.maxBreadcrumbs);try{y()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,e){if(!this.shouldCapture())return;let s;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){s=t.stack}const i=this.ensureStack(t,s),n=this.getErrorKey(i);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(n);const r=this.buildEvent(i,e),o=this.config.beforeSend(r);o?this.transport.send(o):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,e="error"){if(!this.shouldCapture())return;const s=new Error(t);if(s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(s,{level:e,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,e){this.context[t]=e}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){let s={};try{s=y()}catch{}const i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:c(),deviceInfo:s,sdkVersion:m,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=g(s[1]);t&&v(t)&&(i.filename=t),i.lineno=parseInt(s[2],10),i.colno=parseInt(s[3],10)}void 0===i.lineno&&"number"==typeof e?.lineno&&(i.lineno=e.lineno),void 0===i.colno&&"number"==typeof e?.colno&&(i.colno=e.colno)}return i}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,e){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(e&&e.length>100&&/:\d+:\d+/.test(e)){const s=e.split("\n"),i=[];i.push(`${t.name}: ${t.message}`);let n=!1;for(const t of s){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const e=t.trim();e&&(e.includes("@")||e.startsWith("at "))&&(i.push(e),n=!0)}if(n&&i.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=i.join("\n"),Object.assign(e,t),e}}try{throw t}catch(t){const e=t;if(e.stack&&e.stack.length>100&&/:\d+:\d+/.test(e.stack))return e}return t}getErrorKey(t){const e=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${e}`}markErrorAsSeen(t){const e=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,e),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const e=this.recentErrors.get(t);if(null==e)return!1;const s=Date.now()-e<=this.config.dedupWindowMs;return s||this.recentErrors.delete(t),s}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),e=this.config.dedupWindowMs;for(const[s,i]of this.recentErrors.entries())t-i>e&&this.recentErrors.delete(s)}};C.instance=null;var D=C,E={init:t=>D.init(t),getInstance:()=>D.getInstance(),captureError(t,e){D.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){D.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){D.getInstance()?.addBreadcrumb(t)},setContext(t,e){D.getInstance()?.setContext(t,e)},setUser(t){D.getInstance()?.setUser(t)},flush:t=>D.getInstance()?.flush(t)||Promise.resolve()},x=!1,_=null,W=null,$=null,S=null,k=null,I=null,M=class extends n.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=D.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:n,fallback:r}=this.props;return t&&e?r?s.jsx(r,{error:e,errorInfo:i,resetError:this.resetError}):null:n}};exports.BreadcrumbManager=w,exports.SDK_VERSION=m,exports.Watch=E,exports.WatchClient=D,exports.WatchErrorBoundary=M,exports.clearDeviceInfoCache=function(){p=null},exports.collectDeviceInfo=f,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=g,exports.getCachedDeviceInfo=y,exports.getEnvironment=d,exports.getPlatform=u,exports.getSessionId=c,exports.isUsableFilename=v,exports.removeGlobalHandlers=function(){if(x){if("web"===t.Platform.OS&&"undefined"!=typeof window)window.onerror=_,_=null,$&&(window.removeEventListener("beforeunload",$),$=null),S&&(window.removeEventListener("pagehide",S),S=null),k&&(window.removeEventListener("unhandledrejection",k),k=null);else{const t=global.ErrorUtils;t&&W&&(t.setGlobalHandler(W),W=null),I&&(I.remove(),I=null)}x=!1}},exports.resetSession=function(){a=null},exports.setSessionId=function(t){a=t},exports.setupConsoleErrorCapture=function(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const n=s.find(t=>t instanceof Error);if(n)i=n;else{const t=s.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(i=new Error(t),i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(i,{source:"console.error",synthetic:!n})}},exports.setupGlobalHandlers=function(e){x?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.Platform.OS?function(t){"undefined"!=typeof window&&(_=window.onerror,window.onerror=(e,s,i,n,r)=>{_&&_(e,s,i,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&s&&(o.filename=s,o.lineno=i,o.colno=n),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:n}),!1},k=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",k),$=()=>{t.flush({keepalive:!0}).catch(()=>{})},S=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",$),window.addEventListener("pagehide",S))}(e):function(e){try{I=t.AppState.addEventListener("change",t=>{"background"!==t&&"inactive"!==t||e.flush().catch(()=>{})})}catch{}const s=global.ErrorUtils;s?(W=s.getGlobalHandler(),s.setGlobalHandler((t,s)=>{e.captureError(t,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&e.flush().catch(()=>{}),W&&W(t,s)})):e.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),x=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))},exports.withWatchErrorBoundary=function(t,e){const i=t.displayName||t.name||"Component",n=i=>s.jsx(M,{...e,children:s.jsx(t,{...i})});return n.displayName=`withWatchErrorBoundary(${i})`,n};
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.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};
1
+ import{Platform as t,Dimensions as e,AppState as s}from"react-native";import i from"react";import{jsx as n}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function o(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let e=5381;for(let s=0;s<t.length;s++)e=(e<<5)+e^t.charCodeAt(s);return(e>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const e=Math.max(1,this.config.maxBufferSize),s=t?.keepalive?Math.min(e,10):e,i=this.queue.slice(0,s);try{await this.doFlush(i,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(i.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,e){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const s=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:s};e?.keepalive&&(t.keepalive=!0);const i=await fetch(this.config.endpoint,t);if(401===i.status||403===i.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:i.status}),new Error("Invalid API key");if(429===i.status){const t=i.headers.get("Retry-After"),e=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=e,this.backoffUntil=Date.now()+e,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${e}ms`),new Error("Rate limited")}if(!i.ok)throw this.applyBackoff(),new Error(`HTTP ${i.status}`);const n=await i.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${n.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,e){this.disabledReason=t,this.disabledUntil=e===1/0?1/0:Date.now()+Math.max(0,e)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const e={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(e))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let e=null;try{e=await t.getItem(this.storageKey)}catch{return}if(e)try{const s=JSON.parse(e);let i=[];if(Array.isArray(s))i=s.filter(o);else if(r(s)&&1===s.v&&Array.isArray(s.events)){const e=s.apiKeyHash;if(e&&e!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}i=s.events.filter(o)}if(0===i.length)return void await t.removeItem(this.storageKey);this.queue=[...i,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${i.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(){return a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let s="";for(let i=0;i<8;i++)s+=t.charAt(e[i]%36);return s}let e="";for(let s=0;s<8;s++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),a}function l(){a=null}function u(t){a=t}function d(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}function f(){const e=t.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function p(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function y(){const s=d("ExpoDevice"),i=d("ExponentConstants"),n=d("ExpoLocalization"),r=f(),o={};try{const{width:t,height:s,scale:i}=e.get("window");o.screenWidth=Math.round(t),o.screenHeight=Math.round(s),o.screenScale=i}catch{}if(o.osName=t.OS,t.Version&&(o.osVersion=String(t.Version)),s&&"web"!==r)try{s.brand&&(o.brand=s.brand),s.manufacturer&&(o.manufacturer=s.manufacturer),s.modelName&&(o.modelName=s.modelName),s.deviceName&&(o.deviceName=s.deviceName),s.osName&&(o.osName=s.osName),s.osVersion&&(o.osVersion=s.osVersion),s.osBuildId&&(o.osBuildId=s.osBuildId),s.platformApiLevel&&(o.platformApiLevel=s.platformApiLevel),"boolean"==typeof s.isDevice&&(o.isDevice=s.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(o.appVersion=t.version),i.expoVersion&&(o.expoVersion=i.expoVersion),t?.name&&(o.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?o.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(o.bundleId=t.android.package)}catch{}if(n)try{const t=n.getLocales?.()?.[0]?.languageTag;t&&(o.locale=t);const e=n.getCalendars?.()?.[0]?.timeZone;e&&(o.timezone=e)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){o.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(o.browserName=e[1],o.browserVersion=e[2])}}catch{}return o}var w=null;function m(){return w||(w=y()),w}function g(){w=null}var v=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const e={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(e),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function b(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function C(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function D(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}}function E(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function _(t,e){return{type:"custom",message:t,data:e}}var W="1.2.0";function $(t){const e=function(t){if(!t)return t;let e=t;const s=e.indexOf("?");s>=0&&(e=e.slice(0,s));const i=e.indexOf("&");for(i>=0&&(e=e.slice(0,i));e.endsWith("/")&&e.length>1;)e=e.slice(0,-1);return e}(t),s=["/app/","/src/","/components/","/screens/"];for(const t of s){const s=e.indexOf(t);if(s>=0)return k(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return k(e)}function S(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function k(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let e=t.replace(/\/{2,}/g,"/");return e.startsWith("./")&&(e=e.slice(2)),e}var I=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||p(),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new v(this.config.maxBreadcrumbs);try{m()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,e){if(!this.shouldCapture())return;let s;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){s=t.stack}const i=this.ensureStack(t,s),n=this.getErrorKey(i);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(n);const r=this.buildEvent(i,e),o=this.config.beforeSend(r);o?this.transport.send(o):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,e="error"){if(!this.shouldCapture())return;const s=new Error(t);if(s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(s,{level:e,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,e){this.context[t]=e}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){let s={};try{s=m()}catch{}const i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:f(),sessionId:c(),deviceInfo:s,sdkVersion:W,extra:{...this.context,...e,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let s=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(s||(s=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),s){const t=$(s[1]);t&&S(t)&&(i.filename=t),i.lineno=parseInt(s[2],10),i.colno=parseInt(s[3],10)}void 0===i.lineno&&"number"==typeof e?.lineno&&(i.lineno=e.lineno),void 0===i.colno&&"number"==typeof e?.colno&&(i.colno=e.colno)}return i}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,e){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(e&&e.length>100&&/:\d+:\d+/.test(e)){const s=e.split("\n"),i=[];i.push(`${t.name}: ${t.message}`);let n=!1;for(const t of s){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const e=t.trim();e&&(e.includes("@")||e.startsWith("at "))&&(i.push(e),n=!0)}if(n&&i.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=i.join("\n"),Object.assign(e,t),e}}try{throw t}catch(t){const e=t;if(e.stack&&e.stack.length>100&&/:\d+:\d+/.test(e.stack))return e}return t}getErrorKey(t){const e=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${e}`}markErrorAsSeen(t){const e=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,e),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const e=this.recentErrors.get(t);if(null==e)return!1;const s=Date.now()-e<=this.config.dedupWindowMs;return s||this.recentErrors.delete(t),s}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),e=this.config.dedupWindowMs;for(const[s,i]of this.recentErrors.entries())t-i>e&&this.recentErrors.delete(s)}};I.instance=null;var M=I,x={init:t=>M.init(t),getInstance:()=>M.getInstance(),captureError(t,e){M.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){M.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){M.getInstance()?.addBreadcrumb(t)},setContext(t,e){M.getInstance()?.setContext(t,e)},setUser(t){M.getInstance()?.setUser(t)},flush:t=>M.getInstance()?.flush(t)||Promise.resolve()},A=!1,T=null,P=null,j=null,R=null,U=null,B=null;function q(e){A?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.OS?function(t){"undefined"!=typeof window&&(T=window.onerror,window.onerror=(e,s,i,n,r)=>{T&&T(e,s,i,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&s&&(o.filename=s,o.lineno=i,o.colno=n),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:n}),!1},U=e=>{if(e.defaultPrevented)return;const s=e.reason instanceof Error?e.reason:new Error(String(e.reason??"Unhandled Promise rejection"));t.captureError(s,{source:"global.unhandledrejection",isPromiseRejection:!0})},window.addEventListener("unhandledrejection",U),j=()=>{t.flush({keepalive:!0}).catch(()=>{})},R=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",j),window.addEventListener("pagehide",R))}(e):function(t){try{B=s.addEventListener("change",e=>{"background"!==e&&"inactive"!==e||t.flush().catch(()=>{})})}catch{}const e=global.ErrorUtils;e?(P=e.getGlobalHandler(),e.setGlobalHandler((e,s)=>{t.captureError(e,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&t.flush().catch(()=>{}),P&&P(e,s)})):t.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),A=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))}function z(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const n=s.find(t=>t instanceof Error);if(n)i=n;else{const t=s.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(i=new Error(t),i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(i,{source:"console.error",synthetic:!n})}}function F(){if(A){if("web"===t.OS&&"undefined"!=typeof window)window.onerror=T,T=null,j&&(window.removeEventListener("beforeunload",j),j=null),R&&(window.removeEventListener("pagehide",R),R=null),U&&(window.removeEventListener("unhandledrejection",U),U=null);else{const t=global.ErrorUtils;t&&P&&(t.setGlobalHandler(P),P=null),B&&(B.remove(),B=null)}A=!1}}var N=class extends i.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=M.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:s}=this.state,{children:i,fallback:r}=this.props;return t&&e?r?n(r,{error:e,errorInfo:s,resetError:this.resetError}):null:i}};function O(t,e){const s=t.displayName||t.name||"Component",i=s=>n(N,{...e,children:n(t,{...s})});return i.displayName=`withWatchErrorBoundary(${s})`,i}export{v as BreadcrumbManager,W as SDK_VERSION,x as Watch,M as WatchClient,N as WatchErrorBoundary,g as clearDeviceInfoCache,y as collectDeviceInfo,E as createConsoleBreadcrumb,_ as createCustomBreadcrumb,D as createHttpBreadcrumb,b as createNavigationBreadcrumb,C as createUIBreadcrumb,$ as deriveReadableFile,m as getCachedDeviceInfo,p as getEnvironment,f as getPlatform,c as getSessionId,S as isUsableFilename,F as removeGlobalHandlers,l as resetSession,u as setSessionId,z as setupConsoleErrorCapture,q as setupGlobalHandlers,O as withWatchErrorBoundary};
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.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};
1
+ "use strict";var t=require("react"),s=require("react-native"),i=require("react/jsx-runtime");function e(t){return t&&t.t?t:{default:t}}var r=e(t);function n(t){return"object"==typeof t&&null!==t}function h(t){return!!n(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var o=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(h);else if(n(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(h)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function u(){const t=s.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null;function d(){return l||(l=function(){const t=c("ExpoDevice"),i=c("ExponentConstants"),e=c("ExpoLocalization"),r=u(),n={};try{const{width:t,height:i,scale:e}=s.Dimensions.get("window");n.screenWidth=Math.round(t),n.screenHeight=Math.round(i),n.screenScale=e}catch{}if(n.osName=s.Platform.OS,s.Platform.Version&&(n.osVersion=String(s.Platform.Version)),t&&"web"!==r)try{t.brand&&(n.brand=t.brand),t.manufacturer&&(n.manufacturer=t.manufacturer),t.modelName&&(n.modelName=t.modelName),t.deviceName&&(n.deviceName=t.deviceName),t.osName&&(n.osName=t.osName),t.osVersion&&(n.osVersion=t.osVersion),t.osBuildId&&(n.osBuildId=t.osBuildId),t.platformApiLevel&&(n.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(n.isDevice=t.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(n.appVersion=t.version),i.expoVersion&&(n.expoVersion=i.expoVersion),t?.name&&(n.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?n.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(n.bundleId=t.android.package)}catch{}if(e)try{const t=e.getLocales?.()?.[0]?.languageTag;t&&(n.locale=t);const s=e.getCalendars?.()?.[0]?.timeZone;s&&(n.timezone=s)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){n.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(n.browserName=s[1],n.browserVersion=s[2])}}catch{}return n}()),l}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function p(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var y=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new o({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{d()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=d()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:(a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),a),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return p(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return p(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};y.instance=null;var w=y,m=class extends r.default.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=w.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:e}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?i.jsx(n,{error:s,errorInfo:e,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=m,exports.withWatchErrorBoundary=function(t,s){const e=t.displayName||t.name||"Component",r=e=>i.jsx(m,{...s,children:i.jsx(t,{...e})});return r.displayName=`withWatchErrorBoundary(${e})`,r};
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.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};
1
+ import t from"react";import{Platform as s,Dimensions as i}from"react-native";import{jsx as e}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function n(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(n);else if(r(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(n)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},o=null;function a(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function c(){const t=s.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var u=null;function l(){return u||(u=function(){const t=a("ExpoDevice"),e=a("ExponentConstants"),r=a("ExpoLocalization"),n=c(),h={};try{const{width:t,height:s,scale:e}=i.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(s),h.screenScale=e}catch{}if(h.osName=s.OS,s.Version&&(h.osVersion=String(s.Version)),t&&"web"!==n)try{t.brand&&(h.brand=t.brand),t.manufacturer&&(h.manufacturer=t.manufacturer),t.modelName&&(h.modelName=t.modelName),t.deviceName&&(h.deviceName=t.deviceName),t.osName&&(h.osName=t.osName),t.osVersion&&(h.osVersion=t.osVersion),t.osBuildId&&(h.osBuildId=t.osBuildId),t.platformApiLevel&&(h.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(h.isDevice=t.isDevice)}catch{}if(e)try{let t=null;"string"==typeof e.manifest?t=JSON.parse(e.manifest):e.manifest&&"object"==typeof e.manifest&&(t=e.manifest),t?.version&&(h.appVersion=t.version),e.expoVersion&&(h.expoVersion=e.expoVersion),t?.name&&(h.appName=t.name),"ios"===n&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===n&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(r)try{const t=r.getLocales?.()?.[0]?.languageTag;t&&(h.locale=t);const s=r.getCalendars?.()?.[0]?.timeZone;s&&(h.timezone=s)}catch{}if("web"===n&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(h.browserName=s[1],h.browserVersion=s[2])}}catch{}return h}()),u}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function d(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var p=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{l()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=l()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:c(),sessionId:(o||(o=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),o),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return d(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return d(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};p.instance=null;var y=p,m=class extends t.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=y.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?e(n,{error:s,errorInfo:i,resetError:this.resetError}):null:r}};function w(t,s){const i=t.displayName||t.name||"Component",r=i=>e(m,{...s,children:e(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r}export{m as WatchErrorBoundary,w as withWatchErrorBoundary};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catdoes/watch",
3
- "version": "1.1.0",
3
+ "version": "1.2.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",
@@ -22,8 +22,8 @@
22
22
  "README.md"
23
23
  ],
24
24
  "scripts": {
25
- "build": "cross-env NODE_ENV=production tsup",
26
- "build:dev": "tsup",
25
+ "build": "cross-env NODE_ENV=production tsup && node scripts/check-dist.mjs",
26
+ "build:dev": "tsup && node scripts/check-dist.mjs",
27
27
  "dev": "tsup --watch",
28
28
  "clean": "rm -rf dist",
29
29
  "prepublishOnly": "npm run build",
@@ -51,19 +51,9 @@
51
51
  "node": ">=18"
52
52
  },
53
53
  "peerDependencies": {
54
- "@react-native-async-storage/async-storage": ">=1.0.0",
55
- "expo-crypto": ">=13.0.0",
56
54
  "react": ">=18.0.0",
57
55
  "react-native": ">=0.72.0"
58
56
  },
59
- "peerDependenciesMeta": {
60
- "expo-crypto": {
61
- "optional": true
62
- },
63
- "@react-native-async-storage/async-storage": {
64
- "optional": true
65
- }
66
- },
67
57
  "devDependencies": {
68
58
  "@types/react": "^19.2.0",
69
59
  "cross-env": "^7.0.3",