@usero/sdk 0.2.1 → 0.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/react.d.cts CHANGED
@@ -1,3 +1,23 @@
1
+ interface PluginLogger {
2
+ debug: (...args: unknown[]) => void;
3
+ info: (...args: unknown[]) => void;
4
+ warn: (...args: unknown[]) => void;
5
+ error: (...args: unknown[]) => void;
6
+ }
7
+ interface PluginContext {
8
+ clientId: string;
9
+ baseUrl: string;
10
+ getStore: <T>() => T | undefined;
11
+ setStore: <T>(value: T) => void;
12
+ logger: PluginLogger;
13
+ }
14
+ interface UseroPlugin {
15
+ name: string;
16
+ onInit?: (ctx: PluginContext) => void | Promise<void>;
17
+ onFeedbackSubmit?: (ctx: PluginContext, submission: FeedbackSubmission) => Promise<Partial<FeedbackSubmission> | undefined> | Partial<FeedbackSubmission> | undefined;
18
+ onDestroy?: (ctx: PluginContext) => void;
19
+ }
20
+
1
21
  type FeedbackRating = 1 | 2 | 3 | 4;
2
22
  interface FeedbackMetadata {
3
23
  pageUrl: string;
@@ -24,6 +44,7 @@ interface FeedbackSubmission {
24
44
  environment?: string;
25
45
  screenshots?: ScreenshotData[];
26
46
  metadata?: Record<string, unknown>;
47
+ replayEvents?: string;
27
48
  }
28
49
  interface FeedbackData {
29
50
  rating?: FeedbackRating;
@@ -51,6 +72,7 @@ interface FeedbackWidgetProps {
51
72
  environment?: string;
52
73
  baseUrl?: string;
53
74
  metadata?: Record<string, unknown>;
75
+ plugins?: ReadonlyArray<UseroPlugin>;
54
76
  onSubmit?: (feedback: FeedbackData) => void;
55
77
  onError?: (error: Error) => void;
56
78
  onOpen?: () => void;
@@ -65,6 +87,7 @@ interface UseroWidgetHandle {
65
87
  open: () => void;
66
88
  close: () => void;
67
89
  update: (next: Partial<Omit<FeedbackWidgetProps, 'clientId' | 'baseUrl'>>) => void;
90
+ whenReady: () => Promise<void>;
68
91
  }
69
92
 
70
93
  declare function UseroFeedbackWidget(props: FeedbackWidgetProps): null;
package/dist/react.d.ts CHANGED
@@ -1,3 +1,23 @@
1
+ interface PluginLogger {
2
+ debug: (...args: unknown[]) => void;
3
+ info: (...args: unknown[]) => void;
4
+ warn: (...args: unknown[]) => void;
5
+ error: (...args: unknown[]) => void;
6
+ }
7
+ interface PluginContext {
8
+ clientId: string;
9
+ baseUrl: string;
10
+ getStore: <T>() => T | undefined;
11
+ setStore: <T>(value: T) => void;
12
+ logger: PluginLogger;
13
+ }
14
+ interface UseroPlugin {
15
+ name: string;
16
+ onInit?: (ctx: PluginContext) => void | Promise<void>;
17
+ onFeedbackSubmit?: (ctx: PluginContext, submission: FeedbackSubmission) => Promise<Partial<FeedbackSubmission> | undefined> | Partial<FeedbackSubmission> | undefined;
18
+ onDestroy?: (ctx: PluginContext) => void;
19
+ }
20
+
1
21
  type FeedbackRating = 1 | 2 | 3 | 4;
2
22
  interface FeedbackMetadata {
3
23
  pageUrl: string;
@@ -24,6 +44,7 @@ interface FeedbackSubmission {
24
44
  environment?: string;
25
45
  screenshots?: ScreenshotData[];
26
46
  metadata?: Record<string, unknown>;
47
+ replayEvents?: string;
27
48
  }
28
49
  interface FeedbackData {
29
50
  rating?: FeedbackRating;
@@ -51,6 +72,7 @@ interface FeedbackWidgetProps {
51
72
  environment?: string;
52
73
  baseUrl?: string;
53
74
  metadata?: Record<string, unknown>;
75
+ plugins?: ReadonlyArray<UseroPlugin>;
54
76
  onSubmit?: (feedback: FeedbackData) => void;
55
77
  onError?: (error: Error) => void;
56
78
  onOpen?: () => void;
@@ -65,6 +87,7 @@ interface UseroWidgetHandle {
65
87
  open: () => void;
66
88
  close: () => void;
67
89
  update: (next: Partial<Omit<FeedbackWidgetProps, 'clientId' | 'baseUrl'>>) => void;
90
+ whenReady: () => Promise<void>;
68
91
  }
69
92
 
70
93
  declare function UseroFeedbackWidget(props: FeedbackWidgetProps): null;
package/dist/react.js CHANGED
@@ -159,6 +159,25 @@ function getGradientEnd(color) {
159
159
  return `#${[shiftedR, shiftedG, shiftedB].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
160
160
  }
161
161
 
162
+ // src/plugin.ts
163
+ function createPluginLogger(name) {
164
+ const prefix = `[usero:${name}]`;
165
+ return {
166
+ debug: (...args) => {
167
+ if (typeof console !== "undefined") console.debug(prefix, ...args);
168
+ },
169
+ info: (...args) => {
170
+ if (typeof console !== "undefined") console.info(prefix, ...args);
171
+ },
172
+ warn: (...args) => {
173
+ if (typeof console !== "undefined") console.warn(prefix, ...args);
174
+ },
175
+ error: (...args) => {
176
+ if (typeof console !== "undefined") console.error(prefix, ...args);
177
+ }
178
+ };
179
+ }
180
+
162
181
  // src/validation.ts
163
182
  function validateFeedbackSubmission(data) {
164
183
  const errors = [];
@@ -339,10 +358,17 @@ var FEEDBACK_CSS = `
339
358
  box-sizing: border-box;
340
359
  }
341
360
 
361
+ .fb-toolrow {
362
+ display: flex;
363
+ align-items: center;
364
+ justify-content: space-between;
365
+ gap: 12px;
366
+ margin-bottom: 8px;
367
+ }
368
+
342
369
  .fb-charcount {
343
370
  font-size: 12px;
344
371
  margin-left: auto;
345
- margin-bottom: 6px;
346
372
  text-align: right;
347
373
  }
348
374
 
@@ -638,6 +664,21 @@ function escapeHtml(value) {
638
664
  }
639
665
  });
640
666
  }
667
+ function mergePluginPatches(base, patches) {
668
+ let result = base;
669
+ for (const patch of patches) {
670
+ if (!patch || typeof patch !== "object") continue;
671
+ const { metadata: patchMetadata, ...rest } = patch;
672
+ result = { ...result, ...rest };
673
+ if (patchMetadata && typeof patchMetadata === "object") {
674
+ result.metadata = {
675
+ ...result.metadata ?? {},
676
+ ...patchMetadata
677
+ };
678
+ }
679
+ }
680
+ return result;
681
+ }
641
682
  function readStoredEmail() {
642
683
  if (typeof window === "undefined") return "";
643
684
  try {
@@ -662,7 +703,8 @@ function initUseroFeedbackWidget(props) {
662
703
  close: () => {
663
704
  },
664
705
  update: () => {
665
- }
706
+ },
707
+ whenReady: () => Promise.resolve()
666
708
  };
667
709
  }
668
710
  const { clientId, baseUrl } = props;
@@ -677,7 +719,8 @@ function initUseroFeedbackWidget(props) {
677
719
  close: () => {
678
720
  },
679
721
  update: () => {
680
- }
722
+ },
723
+ whenReady: () => Promise.resolve()
681
724
  };
682
725
  }
683
726
  let position = props.position ?? "right";
@@ -694,6 +737,34 @@ function initUseroFeedbackWidget(props) {
694
737
  let onOpen = props.onOpen;
695
738
  let onClose = props.onClose;
696
739
  const apiClient = new FeedbackApiClient(baseUrl);
740
+ const pluginList = props.plugins ?? [];
741
+ const pluginStores = /* @__PURE__ */ new Map();
742
+ const pluginContexts = /* @__PURE__ */ new Map();
743
+ const initPromises = [];
744
+ for (const plugin of pluginList) {
745
+ const ctx = {
746
+ clientId,
747
+ baseUrl: baseUrl ?? DEFAULT_API_URL,
748
+ logger: createPluginLogger(plugin.name),
749
+ getStore: () => pluginStores.get(plugin.name),
750
+ setStore: (value) => {
751
+ pluginStores.set(plugin.name, value);
752
+ }
753
+ };
754
+ pluginContexts.set(plugin.name, ctx);
755
+ if (plugin.onInit) {
756
+ const settled = (async () => {
757
+ try {
758
+ await plugin.onInit?.(ctx);
759
+ } catch (err) {
760
+ ctx.logger.error("onInit threw", err);
761
+ }
762
+ })();
763
+ initPromises.push(settled);
764
+ }
765
+ }
766
+ const readyPromise = initPromises.length === 0 ? Promise.resolve() : Promise.all(initPromises).then(() => {
767
+ });
697
768
  let isOpen = false;
698
769
  let selectedRating = void 0;
699
770
  let comment = "";
@@ -812,8 +883,24 @@ function initUseroFeedbackWidget(props) {
812
883
  setSubmitMessage({ type: "error", text: validation.errors.join(", ") });
813
884
  return;
814
885
  }
886
+ let enrichedSubmission = submission;
887
+ if (pluginList.length > 0) {
888
+ const patchPromises = pluginList.map(async (plugin) => {
889
+ if (!plugin.onFeedbackSubmit) return void 0;
890
+ const ctx = pluginContexts.get(plugin.name);
891
+ if (!ctx) return void 0;
892
+ try {
893
+ return await plugin.onFeedbackSubmit(ctx, submission);
894
+ } catch (err) {
895
+ ctx.logger.error("onFeedbackSubmit threw", err);
896
+ return void 0;
897
+ }
898
+ });
899
+ const patches = await Promise.all(patchPromises);
900
+ enrichedSubmission = mergePluginPatches(submission, patches);
901
+ }
815
902
  try {
816
- const response = await apiClient.submitFeedback(submission);
903
+ const response = await apiClient.submitFeedback(enrichedSubmission);
817
904
  if (response.success) {
818
905
  if (shareEmail && userEmail) writeStoredEmail(userEmail);
819
906
  onSubmit?.(feedbackData);
@@ -870,17 +957,26 @@ function initUseroFeedbackWidget(props) {
870
957
  const cls = ["fb-ec", sel && "fb-ec--sel"].filter(Boolean).join(" ");
871
958
  return `
872
959
  <div class="${cls}" style="background:${bg}">
873
- <button type="button" class="fb-eb" data-rating="${r}" role="radio" aria-checked="${sel}" aria-label="${r}: ${RATING_LABELS[r]}">
960
+ <button type="button" class="fb-eb" data-rating="${r}" role="radio" aria-checked="${sel}" aria-label="${r}: ${RATING_LABELS[r]}" style="color:${theme.text}">
874
961
  <div class="fb-ei"><span role="img" aria-label="${RATING_LABELS[r]}">${EMOJI_MAP[r]}</span></div>
875
- <div class="fb-el">${RATING_LABELS[r]}</div>
962
+ <div class="fb-el" style="color:${theme.text}">${RATING_LABELS[r]}</div>
876
963
  </button>
877
964
  </div>
878
965
  `;
879
966
  }).join("");
880
967
  const messageHtml = submitMessage ? `<div class="fb-msg fb-msg--header ${submitMessage.type === "success" ? "fb-msg--ok" : "fb-msg--err"}">${submitMessage.type === "success" ? "\u2713" : "\u26A0"} ${escapeHtml(submitMessage.text)}</div>` : "";
881
- const screenshotBlockHtml = showScreenshotOption ? (() => {
968
+ const uploadBtnHtml = showScreenshotOption ? (() => {
882
969
  const atMax = screenshots.length >= MAX_SCREENSHOTS;
883
970
  const btnDisabled = isUploadingScreenshot || atMax;
971
+ return `
972
+ <input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
973
+ <button type="button" class="fb-upb ${btnDisabled ? "fb-upb--dis" : ""}" data-role="screenshot-pick" ${btnDisabled ? "disabled" : ""} style="border:1px solid ${theme.border};color:${theme.text};">
974
+ ${isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot"}
975
+ </button>
976
+ `;
977
+ })() : "";
978
+ const uploadExtrasHtml = showScreenshotOption ? (() => {
979
+ const atMax = screenshots.length >= MAX_SCREENSHOTS;
884
980
  const previewsHtml = screenshots.map(
885
981
  (shot, i) => `
886
982
  <div class="fb-sp">
@@ -891,16 +987,7 @@ function initUseroFeedbackWidget(props) {
891
987
  ).join("");
892
988
  const errorHtml = screenshotError ? `<div class="fb-upe">\u26A0 ${escapeHtml(screenshotError)}</div>` : "";
893
989
  const limitHtml = atMax ? `<div class="fb-sl">Max ${MAX_SCREENSHOTS}</div>` : "";
894
- const extrasHtml = screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
895
- return `
896
- <div class="fb-up">
897
- <input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
898
- <button type="button" class="fb-upb ${btnDisabled ? "fb-upb--dis" : ""}" data-role="screenshot-pick" ${btnDisabled ? "disabled" : ""} style="border:1px solid ${theme.border};color:${theme.text};">
899
- ${isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot"}
900
- </button>
901
- ${extrasHtml}
902
- </div>
903
- `;
990
+ return screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
904
991
  })() : "";
905
992
  const emailBlockHtml = showEmailOption ? `
906
993
  <div class="fb-email">
@@ -923,8 +1010,11 @@ function initUseroFeedbackWidget(props) {
923
1010
  <form data-role="form">
924
1011
  <div class="fb-es" role="radiogroup" aria-label="Rate experience">${ratingsHtml}</div>
925
1012
  <textarea class="fb-ta" data-role="comment" placeholder="${escapeHtml(placeholder)}" aria-label="Comments" maxlength="1000" rows="2" style="border:1px solid ${theme.border};color:${theme.text};background-color:${theme.background};">${escapeHtml(comment)}</textarea>
926
- <div class="fb-charcount${lowChars ? " fb-charcount--low" : ""}" data-role="charcount" style="color:${lowChars ? "#dc2626" : theme.text};opacity:${lowChars ? 1 : 0.6};">${remaining} chars remaining</div>
927
- ${screenshotBlockHtml}
1013
+ <div class="fb-toolrow">
1014
+ ${uploadBtnHtml}
1015
+ <div class="fb-charcount${lowChars ? " fb-charcount--low" : ""}" data-role="charcount" style="color:${lowChars ? "#dc2626" : theme.text};opacity:${lowChars ? 1 : 0.6};">${remaining} chars remaining</div>
1016
+ </div>
1017
+ ${uploadExtrasHtml ? `<div class="fb-up">${uploadExtrasHtml}</div>` : ""}
928
1018
  ${emailBlockHtml}
929
1019
  <button class="fb-sub ${submitDisabled ? "fb-sub--dis" : ""}" type="submit" aria-label="Submit" ${submitDisabled ? "disabled" : ""} style="${submitStyle}">
930
1020
  ${isSubmitting ? '<span class="fb-spin"></span>' : ""}
@@ -1055,10 +1145,23 @@ function initUseroFeedbackWidget(props) {
1055
1145
  destroyed = true;
1056
1146
  document.removeEventListener("keydown", onKeyDown);
1057
1147
  detachMqlListener();
1148
+ for (const plugin of pluginList) {
1149
+ if (!plugin.onDestroy) continue;
1150
+ const ctx = pluginContexts.get(plugin.name);
1151
+ if (!ctx) continue;
1152
+ try {
1153
+ plugin.onDestroy(ctx);
1154
+ } catch (err) {
1155
+ ctx.logger.error("onDestroy threw", err);
1156
+ }
1157
+ }
1158
+ pluginStores.clear();
1159
+ pluginContexts.clear();
1058
1160
  host.remove();
1059
1161
  },
1060
1162
  open,
1061
1163
  close,
1164
+ whenReady: () => readyPromise,
1062
1165
  update: (next) => {
1063
1166
  if (destroyed) return;
1064
1167
  let needsRender = false;