@mushi-mushi/core 1.9.0 → 1.11.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.cts CHANGED
@@ -1065,6 +1065,41 @@ interface MushiSDKInstance {
1065
1065
  * welcome" nudges (first-session, beta-onboarding).
1066
1066
  */
1067
1067
  pulseTrigger(): void;
1068
+ /**
1069
+ * Returns the signed-in reporter's own report history.
1070
+ * Keyed to the persistent `reporterToken` stored in localStorage /
1071
+ * AsyncStorage. Returns an empty array when no token exists yet.
1072
+ */
1073
+ listMyReports(): Promise<MushiReporterReport[]>;
1074
+ /**
1075
+ * Returns the comment thread for a given report. Only returns comments
1076
+ * visible to the reporter (their own comments + team replies).
1077
+ */
1078
+ listMyComments(reportId: string): Promise<MushiReporterComment[]>;
1079
+ /**
1080
+ * Post a follow-up comment on one of the reporter's own reports.
1081
+ * Returns the newly created comment, or null on failure.
1082
+ */
1083
+ replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
1084
+ /**
1085
+ * Submit a structured feedback chip (confirms / not_fixed / …) on a report.
1086
+ * Drives the verify/reopen lifecycle when the report is in a fixed state.
1087
+ */
1088
+ submitFeedbackSignal(reportId: string, signal: string, note?: string): Promise<Record<string, unknown> | null>;
1089
+ /**
1090
+ * Reporter-initiated regression reopen with an optional note.
1091
+ */
1092
+ reopenReport(reportId: string, note?: string): Promise<Record<string, unknown> | null>;
1093
+ /**
1094
+ * Open the reporter inbox ("my reports") view in the widget.
1095
+ */
1096
+ openMyReports(): void;
1097
+ /**
1098
+ * Returns the global contributor hall-of-fame ranked by total points.
1099
+ * Safe to call without an authenticated user; uses public endpoint.
1100
+ * @param limit Maximum entries to return (default 20).
1101
+ */
1102
+ getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
1068
1103
  }
1069
1104
  interface MushiCaptureExceptionOptions {
1070
1105
  /** Override the default `'bug'` category (e.g. `'slow'` for timeouts). */
@@ -1125,8 +1160,12 @@ interface MushiApiClient {
1125
1160
  listReporterComments(reportId: string, reporterToken: string): Promise<MushiApiResponse<{
1126
1161
  comments: MushiReporterComment[];
1127
1162
  }>>;
1128
- replyToReporterReport(reportId: string, reporterToken: string, body: string): Promise<MushiApiResponse<{
1163
+ replyToReporterReport(reportId: string, reporterToken: string, body: string, feedbackSignal?: string): Promise<MushiApiResponse<{
1129
1164
  comment: MushiReporterComment;
1165
+ feedback?: Record<string, unknown>;
1166
+ }>>;
1167
+ reopenReporterReport(reportId: string, reporterToken: string, note?: string): Promise<MushiApiResponse<{
1168
+ outcome: Record<string, unknown>;
1130
1169
  }>>;
1131
1170
  /**
1132
1171
  * Submit a batch of activity events for the current user.
@@ -1164,6 +1203,20 @@ interface MushiApiClient {
1164
1203
  items: unknown[];
1165
1204
  total: number;
1166
1205
  }>>;
1206
+ /** Fetch the project's public leaderboard (top contributors). */
1207
+ getHallOfFame(limit?: number): Promise<MushiApiResponse<{
1208
+ data: Array<{
1209
+ display_name: string;
1210
+ email_hash: string | null;
1211
+ tier_slug: string | null;
1212
+ tier_name: string | null;
1213
+ points_30d: number;
1214
+ total_points: number;
1215
+ }>;
1216
+ meta: {
1217
+ project_name: string;
1218
+ };
1219
+ }>>;
1167
1220
  }
1168
1221
  /**
1169
1222
  * Wire shape of a single discovery event sent by the SDK to
@@ -1217,6 +1270,10 @@ interface MushiReporterReport {
1217
1270
  created_at: string;
1218
1271
  last_admin_reply_at?: string | null;
1219
1272
  last_reporter_reply_at?: string | null;
1273
+ parent_report_id?: string | null;
1274
+ verified_at?: string | null;
1275
+ reopened_at?: string | null;
1276
+ regression_count?: number;
1220
1277
  unread_count?: number;
1221
1278
  }
1222
1279
  interface MushiReporterComment {
@@ -1227,6 +1284,14 @@ interface MushiReporterComment {
1227
1284
  visible_to_reporter?: boolean;
1228
1285
  created_at: string;
1229
1286
  }
1287
+ interface MushiHallOfFameEntry {
1288
+ display_name: string;
1289
+ email_hash: string | null;
1290
+ tier_slug: string | null;
1291
+ tier_name: string | null;
1292
+ points_30d: number;
1293
+ total_points: number;
1294
+ }
1230
1295
 
1231
1296
  interface ApiClientOptions {
1232
1297
  projectId: string;
@@ -1537,4 +1602,4 @@ declare function createLogger(options: LoggerOptions): Logger;
1537
1602
  */
1538
1603
  declare const noopLogger: Logger;
1539
1604
 
1540
- export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, newUuid, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
1605
+ export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiHallOfFameEntry, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, newUuid, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
package/dist/index.d.ts CHANGED
@@ -1065,6 +1065,41 @@ interface MushiSDKInstance {
1065
1065
  * welcome" nudges (first-session, beta-onboarding).
1066
1066
  */
1067
1067
  pulseTrigger(): void;
1068
+ /**
1069
+ * Returns the signed-in reporter's own report history.
1070
+ * Keyed to the persistent `reporterToken` stored in localStorage /
1071
+ * AsyncStorage. Returns an empty array when no token exists yet.
1072
+ */
1073
+ listMyReports(): Promise<MushiReporterReport[]>;
1074
+ /**
1075
+ * Returns the comment thread for a given report. Only returns comments
1076
+ * visible to the reporter (their own comments + team replies).
1077
+ */
1078
+ listMyComments(reportId: string): Promise<MushiReporterComment[]>;
1079
+ /**
1080
+ * Post a follow-up comment on one of the reporter's own reports.
1081
+ * Returns the newly created comment, or null on failure.
1082
+ */
1083
+ replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
1084
+ /**
1085
+ * Submit a structured feedback chip (confirms / not_fixed / …) on a report.
1086
+ * Drives the verify/reopen lifecycle when the report is in a fixed state.
1087
+ */
1088
+ submitFeedbackSignal(reportId: string, signal: string, note?: string): Promise<Record<string, unknown> | null>;
1089
+ /**
1090
+ * Reporter-initiated regression reopen with an optional note.
1091
+ */
1092
+ reopenReport(reportId: string, note?: string): Promise<Record<string, unknown> | null>;
1093
+ /**
1094
+ * Open the reporter inbox ("my reports") view in the widget.
1095
+ */
1096
+ openMyReports(): void;
1097
+ /**
1098
+ * Returns the global contributor hall-of-fame ranked by total points.
1099
+ * Safe to call without an authenticated user; uses public endpoint.
1100
+ * @param limit Maximum entries to return (default 20).
1101
+ */
1102
+ getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
1068
1103
  }
1069
1104
  interface MushiCaptureExceptionOptions {
1070
1105
  /** Override the default `'bug'` category (e.g. `'slow'` for timeouts). */
@@ -1125,8 +1160,12 @@ interface MushiApiClient {
1125
1160
  listReporterComments(reportId: string, reporterToken: string): Promise<MushiApiResponse<{
1126
1161
  comments: MushiReporterComment[];
1127
1162
  }>>;
1128
- replyToReporterReport(reportId: string, reporterToken: string, body: string): Promise<MushiApiResponse<{
1163
+ replyToReporterReport(reportId: string, reporterToken: string, body: string, feedbackSignal?: string): Promise<MushiApiResponse<{
1129
1164
  comment: MushiReporterComment;
1165
+ feedback?: Record<string, unknown>;
1166
+ }>>;
1167
+ reopenReporterReport(reportId: string, reporterToken: string, note?: string): Promise<MushiApiResponse<{
1168
+ outcome: Record<string, unknown>;
1130
1169
  }>>;
1131
1170
  /**
1132
1171
  * Submit a batch of activity events for the current user.
@@ -1164,6 +1203,20 @@ interface MushiApiClient {
1164
1203
  items: unknown[];
1165
1204
  total: number;
1166
1205
  }>>;
1206
+ /** Fetch the project's public leaderboard (top contributors). */
1207
+ getHallOfFame(limit?: number): Promise<MushiApiResponse<{
1208
+ data: Array<{
1209
+ display_name: string;
1210
+ email_hash: string | null;
1211
+ tier_slug: string | null;
1212
+ tier_name: string | null;
1213
+ points_30d: number;
1214
+ total_points: number;
1215
+ }>;
1216
+ meta: {
1217
+ project_name: string;
1218
+ };
1219
+ }>>;
1167
1220
  }
1168
1221
  /**
1169
1222
  * Wire shape of a single discovery event sent by the SDK to
@@ -1217,6 +1270,10 @@ interface MushiReporterReport {
1217
1270
  created_at: string;
1218
1271
  last_admin_reply_at?: string | null;
1219
1272
  last_reporter_reply_at?: string | null;
1273
+ parent_report_id?: string | null;
1274
+ verified_at?: string | null;
1275
+ reopened_at?: string | null;
1276
+ regression_count?: number;
1220
1277
  unread_count?: number;
1221
1278
  }
1222
1279
  interface MushiReporterComment {
@@ -1227,6 +1284,14 @@ interface MushiReporterComment {
1227
1284
  visible_to_reporter?: boolean;
1228
1285
  created_at: string;
1229
1286
  }
1287
+ interface MushiHallOfFameEntry {
1288
+ display_name: string;
1289
+ email_hash: string | null;
1290
+ tier_slug: string | null;
1291
+ tier_name: string | null;
1292
+ points_30d: number;
1293
+ total_points: number;
1294
+ }
1230
1295
 
1231
1296
  interface ApiClientOptions {
1232
1297
  projectId: string;
@@ -1537,4 +1602,4 @@ declare function createLogger(options: LoggerOptions): Logger;
1537
1602
  */
1538
1603
  declare const noopLogger: Logger;
1539
1604
 
1540
- export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, newUuid, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
1605
+ export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiHallOfFameEntry, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, newUuid, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
package/dist/index.js CHANGED
@@ -138,12 +138,20 @@ function createApiClient(options) {
138
138
  reporterToken
139
139
  );
140
140
  },
141
- async replyToReporterReport(reportId, reporterToken, body) {
141
+ async replyToReporterReport(reportId, reporterToken, body, feedbackSignal) {
142
142
  return requestForReporter(
143
143
  "POST",
144
144
  `/v1/reporter/reports/${reportId}/reply`,
145
145
  reporterToken,
146
- { body }
146
+ { body, ...feedbackSignal ? { feedback_signal: feedbackSignal } : {} }
147
+ );
148
+ },
149
+ async reopenReporterReport(reportId, reporterToken, note) {
150
+ return requestForReporter(
151
+ "POST",
152
+ `/v1/reporter/reports/${reportId}/reopen`,
153
+ reporterToken,
154
+ { note: note ?? "" }
147
155
  );
148
156
  },
149
157
  // ─── Rewards program (P1) ──────────────────────────────────
@@ -192,6 +200,15 @@ function createApiClient(options) {
192
200
  1,
193
201
  "reporter-poll"
194
202
  );
203
+ },
204
+ async getHallOfFame(limit = 10) {
205
+ return request(
206
+ "GET",
207
+ `/v1/sdk/hall-of-fame?limit=${limit}`,
208
+ void 0,
209
+ 1,
210
+ "reporter-poll"
211
+ );
195
212
  }
196
213
  };
197
214
  }
@@ -290,8 +307,10 @@ var SPAM_PATTERNS = [
290
307
  // numbers only
291
308
  /^[^a-zA-Z\u00C0-\u024F\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF]{10,}$/,
292
309
  // no real letters
293
- /\b(test|asdf|qwerty|lorem ipsum)\b/i
294
- // common test strings
310
+ // "test" removed — users legitimately write "I was testing…" in real reports.
311
+ // Keep clearly-nonsense patterns that are never real sentences.
312
+ /^(asdf|qwerty|lorem ipsum)[.,!?\s]*$/i
313
+ // standalone keyboard-mash or placeholder
295
314
  ];
296
315
  var GIBBERISH_PATTERN = /^[bcdfghjklmnpqrstvwxz]{6,}/i;
297
316
  function createPreFilter(config = {}) {
@@ -581,9 +600,11 @@ var DB_VERSION = 1;
581
600
  var LS_KEY = "mushi_offline_queue";
582
601
  var BATCH_SIZE = 10;
583
602
  var MAX_BACKOFF_MS = 6e4;
603
+ var AUTO_FLUSH_INTERVAL_MS = 3e4;
584
604
  function createOfflineQueue(config = {}) {
585
605
  const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
586
606
  let syncCleanup = null;
607
+ let flushInterval = null;
587
608
  let backendType = null;
588
609
  async function wrapForStorage(report) {
589
610
  const queuedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -778,6 +799,7 @@ function createOfflineQueue(config = {}) {
778
799
  const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
779
800
  result.error.message
780
801
  );
802
+ const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_429" || result.error?.code === "HTTP_502" || result.error?.code === "HTTP_503" || result.error?.code === "HTTP_504" || typeof result.error?.code === "string" && result.error.code.startsWith("HTTP_5"));
781
803
  if (permanent) {
782
804
  try {
783
805
  if (backend === "indexeddb") await idbDelete(rowId);
@@ -785,6 +807,11 @@ function createOfflineQueue(config = {}) {
785
807
  } catch {
786
808
  lsDelete(rowId);
787
809
  }
810
+ } else if (transient) {
811
+ queueLog.debug("Offline queue: transient failure, will retry", {
812
+ id: rowId,
813
+ code: result.error?.code
814
+ });
788
815
  }
789
816
  failed++;
790
817
  if (i < batch.length - 1) {
@@ -820,14 +847,25 @@ function createOfflineQueue(config = {}) {
820
847
  }
821
848
  function startAutoSync(client) {
822
849
  if (!enabled || !syncOnReconnect || typeof window === "undefined") return;
823
- const handler = () => {
850
+ const tryFlush = () => {
824
851
  if (navigator.onLine) {
825
852
  flush(client).catch(() => {
826
853
  });
827
854
  }
828
855
  };
829
- window.addEventListener("online", handler);
830
- syncCleanup = () => window.removeEventListener("online", handler);
856
+ window.addEventListener("online", tryFlush);
857
+ flushInterval = setInterval(() => {
858
+ void size().then((n) => {
859
+ if (n > 0) tryFlush();
860
+ });
861
+ }, AUTO_FLUSH_INTERVAL_MS);
862
+ syncCleanup = () => {
863
+ window.removeEventListener("online", tryFlush);
864
+ if (flushInterval) {
865
+ clearInterval(flushInterval);
866
+ flushInterval = null;
867
+ }
868
+ };
831
869
  }
832
870
  function stopAutoSync() {
833
871
  syncCleanup?.();