@catdoes/watch 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -506,6 +506,6 @@ declare function isUsableFilename(filename: string): boolean;
506
506
  *
507
507
  * Keep this value updated when making SDK changes.
508
508
  */
509
- declare const SDK_VERSION = "1.2.0";
509
+ declare const SDK_VERSION = "1.3.0";
510
510
 
511
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
@@ -506,6 +506,6 @@ declare function isUsableFilename(filename: string): boolean;
506
506
  *
507
507
  * Keep this value updated when making SDK changes.
508
508
  */
509
- declare const SDK_VERSION = "1.2.0";
509
+ declare const SDK_VERSION = "1.3.0";
510
510
 
511
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
- "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};
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 n=i(e),r=3e5;function o(t){return"object"==typeof t&&null!==t}function h(t){return!!o(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.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):0;throw this.applyBackoff(),Number.isFinite(e)&&e>this.backoffMs&&(this.backoffMs=e,this.backoffUntil=Date.now()+e,this.requestPersist()),this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${this.backoffMs}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.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures")),this.requestPersist()}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,r)}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{const e=Date.now(),s=this.backoffUntil>e?this.backoffUntil:void 0,i="failures"===this.disabledReason&&Number.isFinite(this.disabledUntil)&&this.disabledUntil>e?this.disabledUntil:void 0;if(0===this.queue.length&&!s&&!i)return void await t.removeItem(this.storageKey);const n={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};s&&(n.backoffUntil=s),this.consecutiveFailures>0&&(n.consecutiveFailures=this.consecutiveFailures),i&&(n.disabledUntil=i),await t.setItem(this.storageKey,JSON.stringify(n))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}restoreThrottleState(t){const e=Date.now(),s=Number(t.backoffUntil);Number.isFinite(s)&&s>e&&(this.backoffUntil=Math.min(s,e+r),this.backoffMs=this.backoffUntil-e);const i=Number(t.consecutiveFailures);Number.isFinite(i)&&i>0&&(this.consecutiveFailures=Math.min(Math.floor(i),this.maxConsecutiveFailures));const n=Number(t.disabledUntil);Number.isFinite(n)&&n>e&&this.disable("failures",Math.min(n-e,this.failuresCooldownMs))}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(h);else if(o(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(h),this.restoreThrottleState(s)}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{}}}},c=null;function l(){return c||(c=`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}()}`),c}function u(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}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=u("ExpoDevice"),s=u("ExponentConstants"),i=u("ExpoLocalization"),n=d(),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 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.3.0";function b(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 v(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 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||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,storage:t.storage??null},this.transport=new a({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 m(this.config.maxBreadcrumbs);try{w()}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=w()}catch{}const 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=b(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 E=C,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,M=null,A=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=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:n,fallback:r}=this.props;return t&&e?r?s.jsx(r,{error:e,errorInfo:i,resetError:this.resetError}):null:n}};exports.BreadcrumbManager=m,exports.SDK_VERSION=g,exports.Watch=x,exports.WatchClient=E,exports.WatchErrorBoundary=A,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=b,exports.getCachedDeviceInfo=w,exports.getEnvironment=f,exports.getPlatform=d,exports.getSessionId=l,exports.isUsableFilename=v,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),M&&(M.remove(),M=null)}_=!1}},exports.resetSession=function(){c=null},exports.setSessionId=function(t){c=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){_?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,n,r)=>{W&&W(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},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{M=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",n=i=>s.jsx(A,{...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 s}from"react-native";import i from"react";import{jsx as n}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function o(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let e=5381;for(let s=0;s<t.length;s++)e=(e<<5)+e^t.charCodeAt(s);return(e>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const e=Math.max(1,this.config.maxBufferSize),s=t?.keepalive?Math.min(e,10):e,i=this.queue.slice(0,s);try{await this.doFlush(i,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(i.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,e){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const s=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:s};e?.keepalive&&(t.keepalive=!0);const i=await fetch(this.config.endpoint,t);if(401===i.status||403===i.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:i.status}),new Error("Invalid API key");if(429===i.status){const t=i.headers.get("Retry-After"),e=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=e,this.backoffUntil=Date.now()+e,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${e}ms`),new Error("Rate limited")}if(!i.ok)throw this.applyBackoff(),new Error(`HTTP ${i.status}`);const n=await i.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${n.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,e){this.disabledReason=t,this.disabledUntil=e===1/0?1/0:Date.now()+Math.max(0,e)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const e={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(e))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let e=null;try{e=await t.getItem(this.storageKey)}catch{return}if(e)try{const s=JSON.parse(e);let i=[];if(Array.isArray(s))i=s.filter(o);else if(r(s)&&1===s.v&&Array.isArray(s.events)){const e=s.apiKeyHash;if(e&&e!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}i=s.events.filter(o)}if(0===i.length)return void await t.removeItem(this.storageKey);this.queue=[...i,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${i.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(){return a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(8);crypto.getRandomValues(e);let s="";for(let i=0;i<8;i++)s+=t.charAt(e[i]%36);return s}let e="";for(let s=0;s<8;s++)e+=t.charAt(Math.floor(36*Math.random()));return e}()}`),a}function l(){a=null}function u(t){a=t}function d(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}function f(){const e=t.OS;return"ios"===e?"ios":"android"===e?"android":"web"}function p(){return"undefined"!=typeof __DEV__&&__DEV__?"development":"production"}function y(){const s=d("ExpoDevice"),i=d("ExponentConstants"),n=d("ExpoLocalization"),r=f(),o={};try{const{width:t,height:s,scale:i}=e.get("window");o.screenWidth=Math.round(t),o.screenHeight=Math.round(s),o.screenScale=i}catch{}if(o.osName=t.OS,t.Version&&(o.osVersion=String(t.Version)),s&&"web"!==r)try{s.brand&&(o.brand=s.brand),s.manufacturer&&(o.manufacturer=s.manufacturer),s.modelName&&(o.modelName=s.modelName),s.deviceName&&(o.deviceName=s.deviceName),s.osName&&(o.osName=s.osName),s.osVersion&&(o.osVersion=s.osVersion),s.osBuildId&&(o.osBuildId=s.osBuildId),s.platformApiLevel&&(o.platformApiLevel=s.platformApiLevel),"boolean"==typeof s.isDevice&&(o.isDevice=s.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(o.appVersion=t.version),i.expoVersion&&(o.expoVersion=i.expoVersion),t?.name&&(o.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?o.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(o.bundleId=t.android.package)}catch{}if(n)try{const t=n.getLocales?.()?.[0]?.languageTag;t&&(o.locale=t);const e=n.getCalendars?.()?.[0]?.timeZone;e&&(o.timezone=e)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){o.userAgent=t.slice(0,500);const e=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);e&&(o.browserName=e[1],o.browserVersion=e[2])}}catch{}return o}var w=null;function m(){return w||(w=y()),w}function g(){w=null}var v=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const e={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(e),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function b(t,e){return{type:"navigation",message:`Navigated from ${t} to ${e}`,data:{from:t,to:e}}}function C(t,e){return{type:"ui",message:e?`${t} on ${e}`:t,data:{action:t,target:e}}}function D(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}}function E(t,e){return{type:"console",message:`[${t}] ${e}`.slice(0,200),data:{level:t}}}function _(t,e){return{type:"custom",message:t,data:e}}var W="1.2.0";function $(t){const e=function(t){if(!t)return t;let e=t;const s=e.indexOf("?");s>=0&&(e=e.slice(0,s));const i=e.indexOf("&");for(i>=0&&(e=e.slice(0,i));e.endsWith("/")&&e.length>1;)e=e.slice(0,-1);return e}(t),s=["/app/","/src/","/components/","/screens/"];for(const t of s){const s=e.indexOf(t);if(s>=0)return k(e.slice(s+1))}try{const t=new URL(e).pathname.replace(/^\//,"");if(t)return t}catch{}return k(e)}function S(t){return!(!t||t.includes("node_modules")||/\.bundle(\/|$|:)/.test(t)||/bundle(\.js|\.map)$/.test(t)||t.startsWith("[native code]")||t.startsWith("native "))}function k(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let e=t.replace(/\/{2,}/g,"/");return e.startsWith("./")&&(e=e.slice(2)),e}var I=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||p(),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new v(this.config.maxBreadcrumbs);try{m()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,e){if(!this.shouldCapture())return;let s;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){s=t.stack}const i=this.ensureStack(t,s),n=this.getErrorKey(i);if(this.hasSeenErrorRecently(n))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",i.message));this.markErrorAsSeen(n);const r=this.buildEvent(i,e),o=this.config.beforeSend(r);o?this.transport.send(o):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,e="error"){if(!this.shouldCapture())return;const s=new Error(t);if(s.stack){const t=s.stack.split("\n");s.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(s,{level:e,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,e){this.context[t]=e}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,e){let s={};try{s=m()}catch{}const i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:f(),sessionId:c(),deviceInfo:s,sdkVersion:W,extra:{...this.context,...e,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let s=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(s||(s=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),s){const t=$(s[1]);t&&S(t)&&(i.filename=t),i.lineno=parseInt(s[2],10),i.colno=parseInt(s[3],10)}void 0===i.lineno&&"number"==typeof e?.lineno&&(i.lineno=e.lineno),void 0===i.colno&&"number"==typeof e?.colno&&(i.colno=e.colno)}return i}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,e){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(e&&e.length>100&&/:\d+:\d+/.test(e)){const s=e.split("\n"),i=[];i.push(`${t.name}: ${t.message}`);let n=!1;for(const t of s){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const e=t.trim();e&&(e.includes("@")||e.startsWith("at "))&&(i.push(e),n=!0)}if(n&&i.length>1){const e=new Error(t.message);return e.name=t.name,e.stack=i.join("\n"),Object.assign(e,t),e}}try{throw t}catch(t){const e=t;if(e.stack&&e.stack.length>100&&/:\d+:\d+/.test(e.stack))return e}return t}getErrorKey(t){const e=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${e}`}markErrorAsSeen(t){const e=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,e),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const e=this.recentErrors.get(t);if(null==e)return!1;const s=Date.now()-e<=this.config.dedupWindowMs;return s||this.recentErrors.delete(t),s}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),e=this.config.dedupWindowMs;for(const[s,i]of this.recentErrors.entries())t-i>e&&this.recentErrors.delete(s)}};I.instance=null;var M=I,x={init:t=>M.init(t),getInstance:()=>M.getInstance(),captureError(t,e){M.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){M.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){M.getInstance()?.addBreadcrumb(t)},setContext(t,e){M.getInstance()?.setContext(t,e)},setUser(t){M.getInstance()?.setUser(t)},flush:t=>M.getInstance()?.flush(t)||Promise.resolve()},A=!1,T=null,P=null,j=null,R=null,U=null,B=null;function q(e){A?e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers already installed"):("web"===t.OS?function(t){"undefined"!=typeof window&&(T=window.onerror,window.onerror=(e,s,i,n,r)=>{T&&T(e,s,i,n,r);const o=r||new Error("string"==typeof e?e:"Unknown error");return!r&&s&&(o.filename=s,o.lineno=i,o.colno=n),t.captureError(o,{source:"global.onerror",filename:s,lineno:i,colno:n}),!1},U=e=>{if(e.defaultPrevented)return;const s=e.reason instanceof Error?e.reason:new Error(String(e.reason??"Unhandled Promise rejection"));t.captureError(s,{source:"global.unhandledrejection",isPromiseRejection:!0})},window.addEventListener("unhandledrejection",U),j=()=>{t.flush({keepalive:!0}).catch(()=>{})},R=()=>{t.flush({keepalive:!0}).catch(()=>{})},window.addEventListener("beforeunload",j),window.addEventListener("pagehide",R))}(e):function(t){try{B=s.addEventListener("change",e=>{"background"!==e&&"inactive"!==e||t.flush().catch(()=>{})})}catch{}const e=global.ErrorUtils;e?(P=e.getGlobalHandler(),e.setGlobalHandler((e,s)=>{t.captureError(e,{source:"ErrorUtils.globalHandler",isFatal:s}),s&&t.flush().catch(()=>{}),P&&P(e,s)})):t.getConfig().debug&&console.debug("[CatDoes Watch] ErrorUtils not available")}(e),A=!0,e.getConfig().debug&&console.debug("[CatDoes Watch] Global handlers installed"))}function z(t){const e=console.error;console.error=(...s)=>{let i;e.apply(console,s);const n=s.find(t=>t instanceof Error);if(n)i=n;else{const t=s.map(t=>{if("string"==typeof t)return t;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");if(i=new Error(t),i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(3)].join("\n")}}t.captureError(i,{source:"console.error",synthetic:!n})}}function F(){if(A){if("web"===t.OS&&"undefined"!=typeof window)window.onerror=T,T=null,j&&(window.removeEventListener("beforeunload",j),j=null),R&&(window.removeEventListener("pagehide",R),R=null),U&&(window.removeEventListener("unhandledrejection",U),U=null);else{const t=global.ErrorUtils;t&&P&&(t.setGlobalHandler(P),P=null),B&&(B.remove(),B=null)}A=!1}}var N=class extends i.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,e){this.setState({errorInfo:e});const{captureErrors:s=!0,onError:i}=this.props;if(s){const s=M.getInstance();s&&s.captureError(t,{componentStack:e.componentStack,source:"WatchErrorBoundary"})}i&&i(t,e)}render(){const{hasError:t,error:e,errorInfo:s}=this.state,{children:i,fallback:r}=this.props;return t&&e?r?n(r,{error:e,errorInfo:s,resetError:this.resetError}):null:i}};function O(t,e){const s=t.displayName||t.name||"Component",i=s=>n(N,{...e,children:n(t,{...s})});return i.displayName=`withWatchErrorBoundary(${s})`,i}export{v as BreadcrumbManager,W as SDK_VERSION,x as Watch,M as WatchClient,N as WatchErrorBoundary,g as clearDeviceInfoCache,y as collectDeviceInfo,E as createConsoleBreadcrumb,_ as createCustomBreadcrumb,D as createHttpBreadcrumb,b as createNavigationBreadcrumb,C as createUIBreadcrumb,$ as deriveReadableFile,m as getCachedDeviceInfo,p as getEnvironment,f as getPlatform,c as getSessionId,S as isUsableFilename,F as removeGlobalHandlers,l as resetSession,u as setSessionId,z as setupConsoleErrorCapture,q as setupGlobalHandlers,O as withWatchErrorBoundary};
1
+ import{Platform as t,Dimensions as e,AppState as s}from"react-native";import i from"react";import{jsx as n}from"react/jsx-runtime";var r=3e5;function o(t){return"object"==typeof t&&null!==t}function h(t){return!!o(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.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):0;throw this.applyBackoff(),Number.isFinite(e)&&e>this.backoffMs&&(this.backoffMs=e,this.backoffUntil=Date.now()+e,this.requestPersist()),this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${this.backoffMs}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.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures")),this.requestPersist()}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,r)}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{const e=Date.now(),s=this.backoffUntil>e?this.backoffUntil:void 0,i="failures"===this.disabledReason&&Number.isFinite(this.disabledUntil)&&this.disabledUntil>e?this.disabledUntil:void 0;if(0===this.queue.length&&!s&&!i)return void await t.removeItem(this.storageKey);const n={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};s&&(n.backoffUntil=s),this.consecutiveFailures>0&&(n.consecutiveFailures=this.consecutiveFailures),i&&(n.disabledUntil=i),await t.setItem(this.storageKey,JSON.stringify(n))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}restoreThrottleState(t){const e=Date.now(),s=Number(t.backoffUntil);Number.isFinite(s)&&s>e&&(this.backoffUntil=Math.min(s,e+r),this.backoffMs=this.backoffUntil-e);const i=Number(t.consecutiveFailures);Number.isFinite(i)&&i>0&&(this.consecutiveFailures=Math.min(Math.floor(i),this.maxConsecutiveFailures));const n=Number(t.disabledUntil);Number.isFinite(n)&&n>e&&this.disable("failures",Math.min(n-e,this.failuresCooldownMs))}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(h);else if(o(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(h),this.restoreThrottleState(s)}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{}}}},c=null;function l(){return c||(c=`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}()}`),c}function u(){c=null}function d(t){c=t}function f(t){try{const e=globalThis.expo;return e?.modules?.[t]??null}catch{return null}}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 s=f("ExpoDevice"),i=f("ExponentConstants"),n=f("ExpoLocalization"),r=p(),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 m=null;function g(){return m||(m=w()),m}function b(){m=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 D(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 E(t,e,s){return{type:"http",message:`${t} ${e}${s?` [${s}]`:""}`,data:{method:t,url:e,statusCode:s}}}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.3.0";function S(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 I(e.slice(s+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 M=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,storage:t.storage??null},this.transport=new a({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{g()}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=g()}catch{}const i={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:p(),sessionId:l(),deviceInfo:s,sdkVersion:$,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(s[1]);t&&k(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)}};M.instance=null;var x=M,A={init:t=>x.init(t),getInstance:()=>x.getInstance(),captureError(t,e){x.getInstance()?.captureError(t,e)},captureMessage(t,e="error"){x.getInstance()?.captureMessage(t,e)},addBreadcrumb(t){x.getInstance()?.addBreadcrumb(t)},setContext(t,e){x.getInstance()?.setContext(t,e)},setUser(t){x.getInstance()?.setUser(t)},flush:t=>x.getInstance()?.flush(t)||Promise.resolve()},N=!1,T=null,P=null,j=null,R=null,U=null,B=null;function q(e){N?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),N=!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(N){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)}N=!1}}var O=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=x.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 L(t,e){const s=t.displayName||t.name||"Component",i=s=>n(O,{...e,children:n(t,{...s})});return i.displayName=`withWatchErrorBoundary(${s})`,i}export{v as BreadcrumbManager,$ as SDK_VERSION,A as Watch,x as WatchClient,O as WatchErrorBoundary,b as clearDeviceInfoCache,w as collectDeviceInfo,_ as createConsoleBreadcrumb,W as createCustomBreadcrumb,E as createHttpBreadcrumb,D as createNavigationBreadcrumb,C as createUIBreadcrumb,S as deriveReadableFile,g as getCachedDeviceInfo,y as getEnvironment,p as getPlatform,l as getSessionId,k as isUsableFilename,F as removeGlobalHandlers,u as resetSession,d as setSessionId,z as setupConsoleErrorCapture,q as setupGlobalHandlers,L as withWatchErrorBoundary};
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- "use strict";var t=require("react"),s=require("react-native"),i=require("react/jsx-runtime");function e(t){return t&&t.t?t:{default:t}}var r=e(t);function n(t){return"object"==typeof t&&null!==t}function h(t){return!!n(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var o=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(h);else if(n(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(h)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},a=null;function c(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function u(){const t=s.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null;function d(){return l||(l=function(){const t=c("ExpoDevice"),i=c("ExponentConstants"),e=c("ExpoLocalization"),r=u(),n={};try{const{width:t,height:i,scale:e}=s.Dimensions.get("window");n.screenWidth=Math.round(t),n.screenHeight=Math.round(i),n.screenScale=e}catch{}if(n.osName=s.Platform.OS,s.Platform.Version&&(n.osVersion=String(s.Platform.Version)),t&&"web"!==r)try{t.brand&&(n.brand=t.brand),t.manufacturer&&(n.manufacturer=t.manufacturer),t.modelName&&(n.modelName=t.modelName),t.deviceName&&(n.deviceName=t.deviceName),t.osName&&(n.osName=t.osName),t.osVersion&&(n.osVersion=t.osVersion),t.osBuildId&&(n.osBuildId=t.osBuildId),t.platformApiLevel&&(n.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(n.isDevice=t.isDevice)}catch{}if(i)try{let t=null;"string"==typeof i.manifest?t=JSON.parse(i.manifest):i.manifest&&"object"==typeof i.manifest&&(t=i.manifest),t?.version&&(n.appVersion=t.version),i.expoVersion&&(n.expoVersion=i.expoVersion),t?.name&&(n.appName=t.name),"ios"===r&&t?.ios?.bundleIdentifier?n.bundleId=t.ios.bundleIdentifier:"android"===r&&t?.android?.package&&(n.bundleId=t.android.package)}catch{}if(e)try{const t=e.getLocales?.()?.[0]?.languageTag;t&&(n.locale=t);const s=e.getCalendars?.()?.[0]?.timeZone;s&&(n.timezone=s)}catch{}if("web"===r&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){n.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(n.browserName=s[1],n.browserVersion=s[2])}}catch{}return n}()),l}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function p(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var y=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new o({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{d()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=d()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:u(),sessionId:(a||(a=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),a),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return p(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return p(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};y.instance=null;var w=y,m=class extends r.default.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=w.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:e}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?i.jsx(n,{error:s,errorInfo:e,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=m,exports.withWatchErrorBoundary=function(t,s){const e=t.displayName||t.name||"Component",r=e=>i.jsx(m,{...s,children:i.jsx(t,{...e})});return r.displayName=`withWatchErrorBoundary(${e})`,r};
1
+ "use strict";var 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),h=3e5;function n(t){return"object"==typeof t&&null!==t}function o(t){return!!n(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.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):0;throw this.applyBackoff(),Number.isFinite(s)&&s>this.backoffMs&&(this.backoffMs=s,this.backoffUntil=Date.now()+s,this.requestPersist()),this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${this.backoffMs}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.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures")),this.requestPersist()}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,h)}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{const s=Date.now(),i=this.backoffUntil>s?this.backoffUntil:void 0,e="failures"===this.disabledReason&&Number.isFinite(this.disabledUntil)&&this.disabledUntil>s?this.disabledUntil:void 0;if(0===this.queue.length&&!i&&!e)return void await t.removeItem(this.storageKey);const r={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};i&&(r.backoffUntil=i),this.consecutiveFailures>0&&(r.consecutiveFailures=this.consecutiveFailures),e&&(r.disabledUntil=e),await t.setItem(this.storageKey,JSON.stringify(r))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}restoreThrottleState(t){const s=Date.now(),i=Number(t.backoffUntil);Number.isFinite(i)&&i>s&&(this.backoffUntil=Math.min(i,s+h),this.backoffMs=this.backoffUntil-s);const e=Number(t.consecutiveFailures);Number.isFinite(e)&&e>0&&(this.consecutiveFailures=Math.min(Math.floor(e),this.maxConsecutiveFailures));const r=Number(t.disabledUntil);Number.isFinite(r)&&r>s&&this.disable("failures",Math.min(r-s,this.failuresCooldownMs))}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(o);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(o),this.restoreThrottleState(i)}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{}}}},c=null;function u(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function l(){const t=s.Platform.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var d=null;function f(){return d||(d=function(){const t=u("ExpoDevice"),i=u("ExponentConstants"),e=u("ExpoLocalization"),r=l(),h={};try{const{width:t,height:i,scale:e}=s.Dimensions.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(i),h.screenScale=e}catch{}if(h.osName=s.Platform.OS,s.Platform.Version&&(h.osVersion=String(s.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(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&&(h.appVersion=t.version),i.expoVersion&&(h.expoVersion=i.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(e)try{const t=e.getLocales?.()?.[0]?.languageTag;t&&(h.locale=t);const s=e.getCalendars?.()?.[0]?.timeZone;s&&(h.timezone=s)}catch{}if("web"===r&&"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}()),d}var p=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 y(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 m=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 a({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 p(this.config.maxBreadcrumbs);try{f()}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 h=this.buildEvent(e,s),n=this.config.beforeSend(h);n?this.transport.send(n):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=f()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:l(),sessionId:(c||(c=`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}()}`),c),deviceInfo:i,sdkVersion:"1.3.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 y(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return y(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)}};m.instance=null;var w=m,v=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:h}=this.props;return t&&s?h?i.jsx(h,{error:s,errorInfo:e,resetError:this.resetError}):null:r}};exports.WatchErrorBoundary=v,exports.withWatchErrorBoundary=function(t,s){const e=t.displayName||t.name||"Component",r=e=>i.jsx(v,{...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 s,Dimensions as i}from"react-native";import{jsx as e}from"react/jsx-runtime";function r(t){return"object"==typeof t&&null!==t}function n(t){return!!r(t)&&"string"==typeof t.message&&"string"==typeof t.timestamp}var h=class{constructor(t){if(this.queue=[],this.flushTimer=null,this.backoffMs=0,this.backoffUntil=0,this.flushPromise=null,this.consecutiveFailures=0,this.disabledUntil=0,this.disabledReason=null,this.persistRequestedVersion=0,this.persistCompletedVersion=0,this.persistInFlight=null,this.config=t,this.storage=t.storage??null,this.maxConsecutiveFailures=t.maxConsecutiveFailures??50,this.failuresCooldownMs=t.failuresCooldownMs??9e5,this.disableOnAuthError=t.disableOnAuthError??!0,!t.apiKey)return this.config.debug&&console.warn("[CatDoes Watch] No API key; transport disabled."),this.disabledReason="auth",this.disabledUntil=1/0,this.apiKeyHash="",this.storageKey="",void(this.hydratePromise=Promise.resolve());this.apiKeyHash=function(t){let s=5381;for(let i=0;i<t.length;i++)s=(s<<5)+s^t.charCodeAt(i);return(s>>>0).toString(36)}(t.apiKey),this.storageKey=`@catdoes_watch_queue_v1_${this.apiKeyHash}`,this.hydratePromise=this.hydrateQueue().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to hydrate queue:",t)}).finally(()=>{this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)})}send(t){if(this.config.apiKey)if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), dropping event ${t}`)}}else this.queue.push(t),this.trimQueue(),this.requestPersist(),this.config.debug&&console.debug("[CatDoes Watch] Event queued:",t.message),this.queue.length>=this.config.maxBufferSize?this.flush():this.scheduleFlush()}scheduleFlush(t=this.config.flushInterval){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},Math.max(0,t)))}async flush(t){return this.flushPromise?(await this.flushPromise,this.queue.length>0&&!this.isDisabled()&&Date.now()>=this.backoffUntil?this.flush(t):void 0):(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null),0!==this.queue.length?(this.flushPromise=this.doFlushWithLock(t).finally(()=>{this.flushPromise=null}),this.flushPromise):void 0)}async doFlushWithLock(t){if(0===this.queue.length)return;if(this.isDisabled()){if(this.config.debug){const t=this.disabledUntil===1/0?"permanently":`for ${Math.max(0,this.disabledUntil-Date.now())}ms`;console.warn(`[CatDoes Watch] Transport disabled (${this.disabledReason}), skipping flush ${t}`)}if(this.disabledUntil!==1/0){const t=Math.max(0,this.disabledUntil-Date.now());this.scheduleFlush(t)}return}if(Date.now()<this.backoffUntil){if(this.config.debug){const t=this.backoffUntil-Date.now();console.debug(`[CatDoes Watch] In backoff, waiting ${t}ms`)}return void this.scheduleFlush(this.backoffUntil-Date.now())}const s=Math.max(1,this.config.maxBufferSize),i=t?.keepalive?Math.min(s,10):s,e=this.queue.slice(0,i);try{await this.doFlush(e,t),this.backoffMs=0,this.consecutiveFailures=0,this.queue=this.queue.slice(e.length),this.requestPersist(),this.queue.length>0&&this.scheduleFlush(0)}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Flush failed, events retained for retry:",t),this.isDisabled()||this.scheduleFlush()}}async doFlush(t,s){if(0===t.length)return;this.config.debug&&console.debug(`[CatDoes Watch] Sending ${t.length} event(s)`);const i=JSON.stringify({events:t});try{const t={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:i};s?.keepalive&&(t.keepalive=!0);const e=await fetch(this.config.endpoint,t);if(401===e.status||403===e.status)throw this.config.debug&&console.warn("[CatDoes Watch] Invalid API key. Clearing queue and disabling."),this.queue=[],this.requestPersist(),this.disableOnAuthError&&this.disable("auth",1/0),this.config.onTransportError?.("auth",{status:e.status}),new Error("Invalid API key");if(429===e.status){const t=e.headers.get("Retry-After"),s=t?1e3*parseInt(t,10):this.calculateBackoff();throw this.backoffMs=s,this.backoffUntil=Date.now()+s,this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${s}ms`),new Error("Rate limited")}if(!e.ok)throw this.applyBackoff(),new Error(`HTTP ${e.status}`);const r=await e.json();this.config.debug&&console.debug(`[CatDoes Watch] Sent successfully, accepted: ${r.accepted}`)}catch(t){throw t instanceof Error&&"Rate limited"===t.message||this.applyBackoff(),t}}applyBackoff(){this.consecutiveFailures++,this.backoffMs=this.calculateBackoff(),this.backoffUntil=Date.now()+this.backoffMs,this.config.debug&&console.debug(`[CatDoes Watch] Failure #${this.consecutiveFailures}, backing off ${this.backoffMs}ms`),this.consecutiveFailures>=this.maxConsecutiveFailures&&(this.config.debug&&console.warn("[CatDoes Watch] Max consecutive failures reached. Clearing queue."),this.queue=[],this.requestPersist(),this.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures"))}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,6e4)}isDisabled(){return null!==this.disabledReason&&Date.now()<this.disabledUntil}disable(t,s){this.disabledReason=t,this.disabledUntil=s===1/0?1/0:Date.now()+Math.max(0,s)}get queueSize(){return this.queue.length}clear(){this.queue=[],this.requestPersist(),this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}shutdown(){this.clear()}trimQueue(){this.queue.length<=200||(this.queue=this.queue.slice(-200))}requestPersist(){this.storage&&(this.persistRequestedVersion++,this.persistInFlight||(this.persistInFlight=this.persistUntilStable().catch(t=>{this.config.debug&&console.debug("[CatDoes Watch] Failed to persist queue:",t)}).finally(()=>{this.persistInFlight=null})))}async persistUntilStable(){if(this.storage)for(await this.hydratePromise.catch(()=>{});this.persistCompletedVersion<this.persistRequestedVersion;){const t=this.persistRequestedVersion;await this.persistSnapshot(),this.persistCompletedVersion=t}}async persistSnapshot(){const t=this.storage;if(t)try{if(0===this.queue.length)return void await t.removeItem(this.storageKey);const s={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};await t.setItem(this.storageKey,JSON.stringify(s))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}async hydrateQueue(){const t=this.storage;if(!t)return;let s=null;try{s=await t.getItem(this.storageKey)}catch{return}if(s)try{const i=JSON.parse(s);let e=[];if(Array.isArray(i))e=i.filter(n);else if(r(i)&&1===i.v&&Array.isArray(i.events)){const s=i.apiKeyHash;if(s&&s!==this.apiKeyHash){try{await t.removeItem(this.storageKey)}catch{}return}e=i.events.filter(n)}if(0===e.length)return void await t.removeItem(this.storageKey);this.queue=[...e,...this.queue],this.trimQueue(),this.config.debug&&console.debug(`[CatDoes Watch] Hydrated ${e.length} queued event(s)`)}catch{try{await t.removeItem(this.storageKey)}catch{}}}},o=null;function a(t){try{const s=globalThis.expo;return s?.modules?.[t]??null}catch{return null}}function c(){const t=s.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var u=null;function l(){return u||(u=function(){const t=a("ExpoDevice"),e=a("ExponentConstants"),r=a("ExpoLocalization"),n=c(),h={};try{const{width:t,height:s,scale:e}=i.get("window");h.screenWidth=Math.round(t),h.screenHeight=Math.round(s),h.screenScale=e}catch{}if(h.osName=s.OS,s.Version&&(h.osVersion=String(s.Version)),t&&"web"!==n)try{t.brand&&(h.brand=t.brand),t.manufacturer&&(h.manufacturer=t.manufacturer),t.modelName&&(h.modelName=t.modelName),t.deviceName&&(h.deviceName=t.deviceName),t.osName&&(h.osName=t.osName),t.osVersion&&(h.osVersion=t.osVersion),t.osBuildId&&(h.osBuildId=t.osBuildId),t.platformApiLevel&&(h.platformApiLevel=t.platformApiLevel),"boolean"==typeof t.isDevice&&(h.isDevice=t.isDevice)}catch{}if(e)try{let t=null;"string"==typeof e.manifest?t=JSON.parse(e.manifest):e.manifest&&"object"==typeof e.manifest&&(t=e.manifest),t?.version&&(h.appVersion=t.version),e.expoVersion&&(h.expoVersion=e.expoVersion),t?.name&&(h.appName=t.name),"ios"===n&&t?.ios?.bundleIdentifier?h.bundleId=t.ios.bundleIdentifier:"android"===n&&t?.android?.package&&(h.bundleId=t.android.package)}catch{}if(r)try{const t=r.getLocales?.()?.[0]?.languageTag;t&&(h.locale=t);const s=r.getCalendars?.()?.[0]?.timeZone;s&&(h.timezone=s)}catch{}if("web"===n&&"undefined"!=typeof window)try{const t=window.navigator?.userAgent;if(t){h.userAgent=t.slice(0,500);const s=t.match(/(Chrome|Firefox|Safari|Edge|Opera)\/(\d+(\.\d+)?)/);s&&(h.browserName=s[1],h.browserVersion=s[2])}}catch{}return h}()),u}var f=class{constructor(t=20){this.breadcrumbs=[],this.maxBreadcrumbs=t}add(t){const s={...t,timestamp:(new Date).toISOString()};this.breadcrumbs.push(s),this.breadcrumbs.length>this.maxBreadcrumbs&&(this.breadcrumbs=this.breadcrumbs.slice(-this.maxBreadcrumbs))}getAll(){return[...this.breadcrumbs]}clear(){this.breadcrumbs=[]}get count(){return this.breadcrumbs.length}setMaxBreadcrumbs(t){this.maxBreadcrumbs=t,this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}};function d(t){if(!t)return t;if(/^[a-z]+:\/\//i.test(t))return t;let s=t.replace(/\/{2,}/g,"/");return s.startsWith("./")&&(s=s.slice(2)),s}var p=class _WatchClient{constructor(t){this.context={},this.user=null,this.isInitialized=!1,this.recentErrors=new Map,this.recentErrorsCleanupTimer=null,this.config={apiKey:t.apiKey,endpoint:t.endpoint||"https://app.catdoes.com/api/watch/ingest",environment:t.environment||("undefined"!=typeof __DEV__&&__DEV__?"development":"production"),captureConsoleErrors:t.captureConsoleErrors||!1,maxBreadcrumbs:t.maxBreadcrumbs||20,maxBufferSize:t.maxBufferSize||10,flushInterval:t.flushInterval||5e3,beforeSend:t.beforeSend||(t=>t),debug:t.debug??!1,dedupWindowMs:t.dedupWindowMs??5e3,dedupMaxEntries:t.dedupMaxEntries??500,storage:t.storage??null},this.transport=new h({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxBufferSize:this.config.maxBufferSize,flushInterval:this.config.flushInterval,debug:this.config.debug,storage:this.config.storage}),this.breadcrumbs=new f(this.config.maxBreadcrumbs);try{l()}catch{}t.initialContext&&(this.context={...t.initialContext}),this.isInitialized=!0,this.config.debug&&console.debug("[CatDoes Watch] Initialized with config:",{endpoint:this.config.endpoint,environment:this.config.environment,apiKey:this.config.apiKey.slice(0,12)+"..."})}static init(t){return t.apiKey||"undefined"!=typeof __DEV__&&__DEV__&&console.warn("[CatDoes Watch] No API key provided. Errors will not be reported."),_WatchClient.instance?(t.debug||_WatchClient.instance.config.debug)&&console.debug("[CatDoes Watch] Already initialized. Returning existing instance."):_WatchClient.instance=new _WatchClient(t),_WatchClient.instance}static getInstance(){return _WatchClient.instance}captureError(t,s){if(!this.shouldCapture())return;let i;try{throw new Error("__CALL_SITE_CAPTURE__")}catch(t){i=t.stack}const e=this.ensureStack(t,i),r=this.getErrorKey(e);if(this.hasSeenErrorRecently(r))return void(this.config.debug&&console.debug("[CatDoes Watch] Skipping duplicate error:",e.message));this.markErrorAsSeen(r);const n=this.buildEvent(e,s),h=this.config.beforeSend(n);h?this.transport.send(h):this.config.debug&&console.debug("[CatDoes Watch] Event dropped by beforeSend hook")}captureMessage(t,s="error"){if(!this.shouldCapture())return;const i=new Error(t);if(i.stack){const t=i.stack.split("\n");i.stack=[t[0],...t.slice(2)].join("\n")}this.captureError(i,{level:s,synthetic:!0})}addBreadcrumb(t){this.breadcrumbs.add(t)}setContext(t,s){this.context[t]=s}clearContext(t){delete this.context[t]}setUser(t){this.user=t}async flush(t){await this.transport.flush(t)}getConfig(){return this.config}get initialized(){return this.isInitialized}buildEvent(t,s){let i={};try{i=l()}catch{}const e={message:t.message||"Unknown error",stack:t.stack,timestamp:(new Date).toISOString(),environment:this.config.environment,platform:c(),sessionId:(o||(o=`sess_${Date.now()}_${function(){const t="abcdefghijklmnopqrstuvwxyz0123456789";if("undefined"!=typeof crypto&&crypto.getRandomValues){const s=new Uint8Array(8);crypto.getRandomValues(s);let i="";for(let e=0;e<8;e++)i+=t.charAt(s[e]%36);return i}let s="";for(let i=0;i<8;i++)s+=t.charAt(Math.floor(36*Math.random()));return s}()}`),o),deviceInfo:i,sdkVersion:"1.2.0",extra:{...this.context,...s,...this.user?{user:this.user}:{}},breadcrumbs:this.breadcrumbs.getAll()};if("development"!==this.config.environment&&t.stack){let i=t.stack.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?/);if(i||(i=t.stack.match(/^[^@]+@(.+?):(\d+):(\d+)$/m)),i){const t=function(t){const s=function(t){if(!t)return t;let s=t;const i=s.indexOf("?");i>=0&&(s=s.slice(0,i));const e=s.indexOf("&");for(e>=0&&(s=s.slice(0,e));s.endsWith("/")&&s.length>1;)s=s.slice(0,-1);return s}(t),i=["/app/","/src/","/components/","/screens/"];for(const t of i){const i=s.indexOf(t);if(i>=0)return d(s.slice(i+1))}try{const t=new URL(s).pathname.replace(/^\//,"");if(t)return t}catch{}return d(s)}(i[1]);!t||!(r=t)||r.includes("node_modules")||/\.bundle(\/|$|:)/.test(r)||/bundle(\.js|\.map)$/.test(r)||r.startsWith("[native code]")||r.startsWith("native ")||(e.filename=t),e.lineno=parseInt(i[2],10),e.colno=parseInt(i[3],10)}void 0===e.lineno&&"number"==typeof s?.lineno&&(e.lineno=s.lineno),void 0===e.colno&&"number"==typeof s?.colno&&(e.colno=s.colno)}var r;return e}shouldCapture(){return!!this.isInitialized&&!!this.config.apiKey}ensureStack(t,s){if(t.stack&&t.stack.length>100&&/:\d+:\d+/.test(t.stack))return t;if(s&&s.length>100&&/:\d+:\d+/.test(s)){const i=s.split("\n"),e=[];e.push(`${t.name}: ${t.message}`);let r=!1;for(const t of i){if(t.includes("__CALL_SITE_CAPTURE__")||t.includes("ensureStack")||t.includes("captureError")||t.includes("WatchClient"))continue;const s=t.trim();s&&(s.includes("@")||s.startsWith("at "))&&(e.push(s),r=!0)}if(r&&e.length>1){const s=new Error(t.message);return s.name=t.name,s.stack=e.join("\n"),Object.assign(s,t),s}}try{throw t}catch(t){const s=t;if(s.stack&&s.stack.length>100&&/:\d+:\d+/.test(s.stack))return s}return t}getErrorKey(t){const s=t.stack?.split("\n")[1]?.trim()||"";return`${t.message}::${s}`}markErrorAsSeen(t){const s=Date.now();if(this.recentErrors.has(t)&&this.recentErrors.delete(t),this.recentErrors.set(t,s),this.recentErrors.size>this.config.dedupMaxEntries){const t=this.recentErrors.keys().next().value;t&&this.recentErrors.delete(t)}this.scheduleRecentErrorsCleanup()}hasSeenErrorRecently(t){const s=this.recentErrors.get(t);if(null==s)return!1;const i=Date.now()-s<=this.config.dedupWindowMs;return i||this.recentErrors.delete(t),i}scheduleRecentErrorsCleanup(){this.recentErrorsCleanupTimer||(this.recentErrorsCleanupTimer=setTimeout(()=>{this.pruneRecentErrors(),this.recentErrorsCleanupTimer=null,this.recentErrors.size>0&&this.scheduleRecentErrorsCleanup()},this.config.dedupWindowMs))}pruneRecentErrors(){const t=Date.now(),s=this.config.dedupWindowMs;for(const[i,e]of this.recentErrors.entries())t-e>s&&this.recentErrors.delete(i)}};p.instance=null;var y=p,m=class extends t.Component{constructor(t){super(t),this.resetError=()=>{this.setState({hasError:!1,error:null,errorInfo:null})},this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,s){this.setState({errorInfo:s});const{captureErrors:i=!0,onError:e}=this.props;if(i){const i=y.getInstance();i&&i.captureError(t,{componentStack:s.componentStack,source:"WatchErrorBoundary"})}e&&e(t,s)}render(){const{hasError:t,error:s,errorInfo:i}=this.state,{children:r,fallback:n}=this.props;return t&&s?n?e(n,{error:s,errorInfo:i,resetError:this.resetError}):null:r}};function w(t,s){const i=t.displayName||t.name||"Component",r=i=>e(m,{...s,children:e(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r}export{m as WatchErrorBoundary,w as withWatchErrorBoundary};
1
+ import t from"react";import{Platform as s,Dimensions as i}from"react-native";import{jsx as e}from"react/jsx-runtime";var r=3e5;function h(t){return"object"==typeof t&&null!==t}function n(t){return!!h(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):0;throw this.applyBackoff(),Number.isFinite(s)&&s>this.backoffMs&&(this.backoffMs=s,this.backoffUntil=Date.now()+s,this.requestPersist()),this.config.debug&&console.debug(`[CatDoes Watch] Rate limited, backing off ${this.backoffMs}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.disable("failures",this.failuresCooldownMs),this.config.onTransportError?.("failures")),this.requestPersist()}calculateBackoff(){const t=1e3*Math.pow(2,this.consecutiveFailures);return Math.min(t,r)}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{const s=Date.now(),i=this.backoffUntil>s?this.backoffUntil:void 0,e="failures"===this.disabledReason&&Number.isFinite(this.disabledUntil)&&this.disabledUntil>s?this.disabledUntil:void 0;if(0===this.queue.length&&!i&&!e)return void await t.removeItem(this.storageKey);const r={v:1,savedAt:(new Date).toISOString(),apiKeyHash:this.apiKeyHash,events:this.queue};i&&(r.backoffUntil=i),this.consecutiveFailures>0&&(r.consecutiveFailures=this.consecutiveFailures),e&&(r.disabledUntil=e),await t.setItem(this.storageKey,JSON.stringify(r))}catch(t){this.config.debug&&console.debug("[CatDoes Watch] Queue persistence failed:",t)}}restoreThrottleState(t){const s=Date.now(),i=Number(t.backoffUntil);Number.isFinite(i)&&i>s&&(this.backoffUntil=Math.min(i,s+r),this.backoffMs=this.backoffUntil-s);const e=Number(t.consecutiveFailures);Number.isFinite(e)&&e>0&&(this.consecutiveFailures=Math.min(Math.floor(e),this.maxConsecutiveFailures));const h=Number(t.disabledUntil);Number.isFinite(h)&&h>s&&this.disable("failures",Math.min(h-s,this.failuresCooldownMs))}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(h(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),this.restoreThrottleState(i)}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.OS;return"ios"===t?"ios":"android"===t?"android":"web"}var l=null;function f(){return l||(l=function(){const t=c("ExpoDevice"),e=c("ExponentConstants"),r=c("ExpoLocalization"),h=u(),n={};try{const{width:t,height:s,scale:e}=i.get("window");n.screenWidth=Math.round(t),n.screenHeight=Math.round(s),n.screenScale=e}catch{}if(n.osName=s.OS,s.Version&&(n.osVersion=String(s.Version)),t&&"web"!==h)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(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&&(n.appVersion=t.version),e.expoVersion&&(n.expoVersion=e.expoVersion),t?.name&&(n.appName=t.name),"ios"===h&&t?.ios?.bundleIdentifier?n.bundleId=t.ios.bundleIdentifier:"android"===h&&t?.android?.package&&(n.bundleId=t.android.package)}catch{}if(r)try{const t=r.getLocales?.()?.[0]?.languageTag;t&&(n.locale=t);const s=r.getCalendars?.()?.[0]?.timeZone;s&&(n.timezone=s)}catch{}if("web"===h&&"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 d=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 d(this.config.maxBreadcrumbs);try{f()}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 h=this.buildEvent(e,s),n=this.config.beforeSend(h);n?this.transport.send(n):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=f()}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.3.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 m=y,w=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=m.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:h}=this.props;return t&&s?h?e(h,{error:s,errorInfo:i,resetError:this.resetError}):null:r}};function v(t,s){const i=t.displayName||t.name||"Component",r=i=>e(w,{...s,children:e(t,{...i})});return r.displayName=`withWatchErrorBoundary(${i})`,r}export{w as WatchErrorBoundary,v as withWatchErrorBoundary};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catdoes/watch",
3
- "version": "1.2.0",
3
+ "version": "1.3.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",