@mushi-mushi/core 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -145,6 +145,53 @@ function createApiClient(options) {
145
145
  reporterToken,
146
146
  { body }
147
147
  );
148
+ },
149
+ // ─── Rewards program (P1) ──────────────────────────────────
150
+ async submitActivity(userId, events, opts) {
151
+ return request(
152
+ "POST",
153
+ "/v1/sdk/activity",
154
+ {
155
+ user_id: userId,
156
+ user_traits: opts?.userTraits,
157
+ opted_in: opts?.optedIn,
158
+ reporter_token_hash: opts?.reporterTokenHash,
159
+ // P2: JWT for monetary verification; omitted when null
160
+ ...opts?.hostJwt ? { host_jwt: opts.hostJwt } : {},
161
+ events
162
+ },
163
+ 1,
164
+ // best-effort, 1 retry
165
+ "discovery"
166
+ );
167
+ },
168
+ async getMyPoints(userId) {
169
+ return request(
170
+ "GET",
171
+ `/v1/sdk/me/points?userId=${encodeURIComponent(userId)}`,
172
+ void 0,
173
+ 1,
174
+ "reporter-poll"
175
+ );
176
+ },
177
+ async getMyTier(userId) {
178
+ return request(
179
+ "GET",
180
+ `/v1/sdk/me/tier?userId=${encodeURIComponent(userId)}`,
181
+ void 0,
182
+ 1,
183
+ "reporter-poll"
184
+ );
185
+ },
186
+ async getMyHistory(userId, opts) {
187
+ const qs = new URLSearchParams({ userId, ...opts?.limit ? { limit: String(opts.limit) } : {} });
188
+ return request(
189
+ "GET",
190
+ `/v1/sdk/me/history?${qs}`,
191
+ void 0,
192
+ 1,
193
+ "reporter-poll"
194
+ );
148
195
  }
149
196
  };
150
197
  }
@@ -783,6 +830,8 @@ function captureEnvironment() {
783
830
  const nav = typeof navigator !== "undefined" ? navigator : void 0;
784
831
  const win = typeof window !== "undefined" ? window : void 0;
785
832
  const doc = typeof document !== "undefined" ? document : void 0;
833
+ const scr = typeof screen !== "undefined" ? screen : void 0;
834
+ void kickOffUserAgentData(nav);
786
835
  const connection = nav && "connection" in nav ? nav.connection : void 0;
787
836
  return {
788
837
  userAgent: nav?.userAgent ?? "unknown",
@@ -804,7 +853,19 @@ function captureEnvironment() {
804
853
  deviceMemory: nav?.deviceMemory,
805
854
  hardwareConcurrency: nav?.hardwareConcurrency,
806
855
  route: win?.location?.pathname,
807
- nearestTestid: findNearestTestidFromActive(doc)
856
+ nearestTestid: findNearestTestidFromActive(doc),
857
+ userAgentData: captureUserAgentData(nav),
858
+ screen: captureScreen(scr, win),
859
+ prefersColorScheme: matchScheme(win),
860
+ prefersReducedMotion: matchMedia(win, "(prefers-reduced-motion: reduce)"),
861
+ prefersReducedData: matchMedia(win, "(prefers-reduced-data: reduce)"),
862
+ prefersContrast: matchContrast(win),
863
+ forcedColors: matchMedia(win, "(forced-colors: active)"),
864
+ online: typeof nav?.onLine === "boolean" ? nav.onLine : void 0,
865
+ displayMode: matchDisplayMode(win),
866
+ documentTitle: doc?.title?.slice(0, 200),
867
+ buildId: readBuildIdMeta(doc),
868
+ pageLoadTiming: capturePageLoadTiming(win)
808
869
  };
809
870
  }
810
871
  function findNearestTestidFromActive(doc) {
@@ -819,6 +880,117 @@ function findNearestTestidFromActive(doc) {
819
880
  }
820
881
  return void 0;
821
882
  }
883
+ var cachedHighEntropy = null;
884
+ var highEntropyKickedOff = false;
885
+ function kickOffUserAgentData(nav) {
886
+ if (highEntropyKickedOff) return;
887
+ const ua = nav?.userAgentData;
888
+ if (!ua?.getHighEntropyValues) return;
889
+ highEntropyKickedOff = true;
890
+ ua.getHighEntropyValues([
891
+ "architecture",
892
+ "bitness",
893
+ "model",
894
+ "platformVersion",
895
+ "uaFullVersion",
896
+ "fullVersionList"
897
+ ]).then((v) => {
898
+ cachedHighEntropy = v;
899
+ }).catch(() => {
900
+ });
901
+ }
902
+ function pickBrand(brands) {
903
+ if (!brands?.length) return void 0;
904
+ const real = brands.filter((b) => !/not.?a.?brand/i.test(b.brand));
905
+ if (real.length === 0) return void 0;
906
+ const named = real.find((b) => !/chromium|google chrome/i.test(b.brand));
907
+ return named ?? real[0];
908
+ }
909
+ function captureUserAgentData(nav) {
910
+ const low = nav?.userAgentData;
911
+ if (!low && !cachedHighEntropy) return void 0;
912
+ const fullList = cachedHighEntropy?.fullVersionList;
913
+ const brand = pickBrand(fullList ?? low?.brands);
914
+ const out = {};
915
+ if (brand) {
916
+ out.browser = brand.brand;
917
+ out.browserVersion = brand.version;
918
+ }
919
+ if (low?.platform) out.os = low.platform;
920
+ if (cachedHighEntropy?.platformVersion) out.osVersion = cachedHighEntropy.platformVersion;
921
+ if (typeof low?.mobile === "boolean") out.mobile = low.mobile;
922
+ if (cachedHighEntropy?.model) out.model = cachedHighEntropy.model;
923
+ if (cachedHighEntropy?.architecture) out.architecture = cachedHighEntropy.architecture;
924
+ if (cachedHighEntropy?.bitness) out.bitness = cachedHighEntropy.bitness;
925
+ return Object.keys(out).length === 0 ? void 0 : out;
926
+ }
927
+ function captureScreen(scr, win) {
928
+ if (!scr && !win) return void 0;
929
+ const out = {};
930
+ if (typeof scr?.width === "number") out.width = scr.width;
931
+ if (typeof scr?.height === "number") out.height = scr.height;
932
+ if (typeof win?.devicePixelRatio === "number") out.devicePixelRatio = win.devicePixelRatio;
933
+ if (typeof scr?.colorDepth === "number") out.colorDepth = scr.colorDepth;
934
+ const orientationType = scr?.orientation?.type;
935
+ if (orientationType) out.orientation = orientationType;
936
+ return Object.keys(out).length === 0 ? void 0 : out;
937
+ }
938
+ function matchMedia(win, query) {
939
+ if (!win?.matchMedia) return void 0;
940
+ try {
941
+ return win.matchMedia(query).matches;
942
+ } catch {
943
+ return void 0;
944
+ }
945
+ }
946
+ function matchScheme(win) {
947
+ if (!win?.matchMedia) return void 0;
948
+ if (matchMedia(win, "(prefers-color-scheme: dark)")) return "dark";
949
+ if (matchMedia(win, "(prefers-color-scheme: light)")) return "light";
950
+ return "no-preference";
951
+ }
952
+ function matchContrast(win) {
953
+ if (!win?.matchMedia) return void 0;
954
+ if (matchMedia(win, "(prefers-contrast: more)")) return "more";
955
+ if (matchMedia(win, "(prefers-contrast: less)")) return "less";
956
+ if (matchMedia(win, "(prefers-contrast: custom)")) return "custom";
957
+ return "no-preference";
958
+ }
959
+ function matchDisplayMode(win) {
960
+ if (!win?.matchMedia) return void 0;
961
+ if (matchMedia(win, "(display-mode: fullscreen)")) return "fullscreen";
962
+ if (matchMedia(win, "(display-mode: standalone)")) return "standalone";
963
+ if (matchMedia(win, "(display-mode: minimal-ui)")) return "minimal-ui";
964
+ if (matchMedia(win, "(display-mode: browser)")) return "browser";
965
+ return void 0;
966
+ }
967
+ function readBuildIdMeta(doc) {
968
+ if (!doc) return void 0;
969
+ const el = doc.querySelector?.('meta[name="mushi:build"]');
970
+ const v = el?.content?.trim();
971
+ if (!v) return void 0;
972
+ return v.slice(0, 64);
973
+ }
974
+ function capturePageLoadTiming(win) {
975
+ const perf = win?.performance;
976
+ if (!perf?.getEntriesByType) return void 0;
977
+ let entry;
978
+ try {
979
+ const entries = perf.getEntriesByType("navigation");
980
+ entry = entries[0];
981
+ } catch {
982
+ return void 0;
983
+ }
984
+ if (!entry) return void 0;
985
+ const start = entry.startTime ?? 0;
986
+ const out = {};
987
+ if (entry.domContentLoadedEventEnd > 0)
988
+ out.domContentLoadedMs = Math.round(entry.domContentLoadedEventEnd - start);
989
+ if (entry.loadEventEnd > 0) out.loadCompleteMs = Math.round(entry.loadEventEnd - start);
990
+ if (entry.responseStart > 0) out.timeToFirstByteMs = Math.round(entry.responseStart - start);
991
+ if (typeof entry.type === "string") out.navigationType = entry.type;
992
+ return Object.keys(out).length === 0 ? void 0 : out;
993
+ }
822
994
 
823
995
  // src/reporter-token.ts
824
996
  var STORAGE_KEY = "mushi_reporter_token";
@@ -1026,6 +1198,75 @@ function scrubPii(text, config) {
1026
1198
  return createPiiScrubber(config).scrub(text);
1027
1199
  }
1028
1200
 
1029
- export { DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, REGION_ENDPOINTS, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
1201
+ // src/breadcrumbs.ts
1202
+ var DEFAULT_MAX = 50;
1203
+ var DEFAULT_MAX_MESSAGE = 500;
1204
+ function createBreadcrumbBuffer(options = {}) {
1205
+ const max = Math.max(1, options.max ?? DEFAULT_MAX);
1206
+ const maxMsg = Math.max(50, options.maxMessageLength ?? DEFAULT_MAX_MESSAGE);
1207
+ let entries = [];
1208
+ return {
1209
+ add(input) {
1210
+ const ts = typeof input.timestamp === "number" ? input.timestamp : Date.now();
1211
+ const message = typeof input.message === "string" && input.message.length > maxMsg ? `${input.message.slice(0, maxMsg)}\u2026` : input.message;
1212
+ const crumb = {
1213
+ timestamp: ts,
1214
+ category: input.category,
1215
+ level: input.level ?? "info",
1216
+ message: message ?? "",
1217
+ ...input.data ? { data: input.data } : {}
1218
+ };
1219
+ entries.push(crumb);
1220
+ while (entries.length > max) entries.shift();
1221
+ },
1222
+ getAll() {
1223
+ return entries.slice();
1224
+ },
1225
+ clear() {
1226
+ entries = [];
1227
+ },
1228
+ size() {
1229
+ return entries.length;
1230
+ }
1231
+ };
1232
+ }
1233
+
1234
+ // src/exception-normaliser.ts
1235
+ var STACK_LIMIT = 8 * 1024;
1236
+ var FALLBACK_JSON_LIMIT = 1e3;
1237
+ function normaliseThrown(thrown) {
1238
+ if (thrown instanceof Error) {
1239
+ const name = thrown.name || "Error";
1240
+ const message = thrown.message || String(thrown);
1241
+ const stack = typeof thrown.stack === "string" && thrown.stack.length > 0 ? thrown.stack.slice(0, STACK_LIMIT) : void 0;
1242
+ const cause = thrown.cause;
1243
+ return {
1244
+ name,
1245
+ message,
1246
+ ...stack ? { stack } : {},
1247
+ ...cause !== void 0 ? { cause: cause instanceof Error ? cause.message : cause } : {}
1248
+ };
1249
+ }
1250
+ if (typeof thrown === "string") {
1251
+ return { name: "Error", message: thrown };
1252
+ }
1253
+ if (thrown && typeof thrown === "object") {
1254
+ const obj = thrown;
1255
+ const name = typeof obj.name === "string" ? obj.name : "Error";
1256
+ const message = typeof obj.message === "string" ? obj.message : (() => {
1257
+ try {
1258
+ return JSON.stringify(obj).slice(0, FALLBACK_JSON_LIMIT);
1259
+ } catch {
1260
+ return String(obj);
1261
+ }
1262
+ })();
1263
+ const stack = typeof obj.stack === "string" ? obj.stack.slice(0, STACK_LIMIT) : void 0;
1264
+ return { name, message, ...stack ? { stack } : {} };
1265
+ }
1266
+ if (thrown === void 0) return { name: "Error", message: "unknown" };
1267
+ return { name: "Error", message: String(thrown) };
1268
+ }
1269
+
1270
+ export { DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, REGION_ENDPOINTS, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
1030
1271
  //# sourceMappingURL=index.js.map
1031
1272
  //# sourceMappingURL=index.js.map