@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 +6 -5
- package/dist/index.d.mts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.js +1 -1
- package/dist/react.mjs +1 -1
- package/package.json +3 -13
package/README.md
CHANGED
|
@@ -21,13 +21,12 @@ npm install @catdoes/watch
|
|
|
21
21
|
yarn add @catdoes/watch
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### Optional Integrations
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
The SDK has no hard dependencies beyond React / React Native:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|