@feedvalue/core 0.1.9 → 0.1.11
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.cjs +206 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +206 -3
- package/dist/index.d.ts +206 -3
- package/dist/index.js +206 -11
- package/dist/index.js.map +1 -1
- package/dist/umd/index.min.js +2 -2
- package/dist/umd/index.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -301,6 +301,84 @@ var ApiClient = class {
|
|
|
301
301
|
this.log("Feedback submitted", { feedbackId: data.feedback_id });
|
|
302
302
|
return data;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Submit reaction
|
|
306
|
+
*/
|
|
307
|
+
async submitReaction(widgetId, reaction) {
|
|
308
|
+
this.validateWidgetId(widgetId);
|
|
309
|
+
const url = `${this.baseUrl}/api/v1/widgets/${widgetId}/react`;
|
|
310
|
+
if (!this.hasValidToken()) {
|
|
311
|
+
this.log("Token expired, refreshing...");
|
|
312
|
+
await this.fetchConfig(widgetId, true);
|
|
313
|
+
}
|
|
314
|
+
if (!this.submissionToken) {
|
|
315
|
+
throw new Error("No submission token available");
|
|
316
|
+
}
|
|
317
|
+
const headers = {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
"X-Submission-Token": this.submissionToken
|
|
320
|
+
};
|
|
321
|
+
if (this.fingerprint) {
|
|
322
|
+
headers["X-Client-Fingerprint"] = this.fingerprint;
|
|
323
|
+
}
|
|
324
|
+
this.log("Submitting reaction", { widgetId, value: reaction.value });
|
|
325
|
+
const response = await fetch(url, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers,
|
|
328
|
+
body: JSON.stringify({
|
|
329
|
+
value: reaction.value,
|
|
330
|
+
followUp: reaction.followUp,
|
|
331
|
+
metadata: reaction.metadata
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
if (response.status === 429) {
|
|
335
|
+
const resetAt = response.headers.get("X-RateLimit-Reset");
|
|
336
|
+
const retryAfter = resetAt ? Math.ceil(parseInt(resetAt, 10) - Date.now() / 1e3) : 60;
|
|
337
|
+
throw new Error(`Rate limited. Try again in ${retryAfter} seconds.`);
|
|
338
|
+
}
|
|
339
|
+
if (response.status === 403) {
|
|
340
|
+
const errorData = await response.json().catch(() => ({ detail: "Access denied" }));
|
|
341
|
+
if (errorData.detail?.code && errorData.detail?.message) {
|
|
342
|
+
throw new Error(errorData.detail.message);
|
|
343
|
+
}
|
|
344
|
+
const errorMessage = typeof errorData.detail === "string" ? errorData.detail : "";
|
|
345
|
+
if (errorMessage.includes("token") || errorMessage.includes("expired")) {
|
|
346
|
+
this.log("Token rejected, refreshing...");
|
|
347
|
+
this.submissionToken = null;
|
|
348
|
+
await this.fetchConfig(widgetId, true);
|
|
349
|
+
if (this.submissionToken) {
|
|
350
|
+
headers["X-Submission-Token"] = this.submissionToken;
|
|
351
|
+
const retryResponse = await fetch(url, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers,
|
|
354
|
+
body: JSON.stringify({
|
|
355
|
+
value: reaction.value,
|
|
356
|
+
followUp: reaction.followUp,
|
|
357
|
+
metadata: reaction.metadata
|
|
358
|
+
})
|
|
359
|
+
});
|
|
360
|
+
if (retryResponse.ok) {
|
|
361
|
+
return retryResponse.json();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
throw new Error(errorMessage || "Access denied");
|
|
366
|
+
}
|
|
367
|
+
if (response.status === 400) {
|
|
368
|
+
const error = await this.parseError(response);
|
|
369
|
+
throw new Error(error);
|
|
370
|
+
}
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
const error = await this.parseError(response);
|
|
373
|
+
throw new Error(error);
|
|
374
|
+
}
|
|
375
|
+
const data = await response.json();
|
|
376
|
+
if (data.blocked) {
|
|
377
|
+
throw new Error(data.message || "Unable to submit reaction");
|
|
378
|
+
}
|
|
379
|
+
this.log("Reaction submitted", { submissionId: data.submission_id });
|
|
380
|
+
return data;
|
|
381
|
+
}
|
|
304
382
|
/**
|
|
305
383
|
* Parse error from response
|
|
306
384
|
*/
|
|
@@ -480,20 +558,27 @@ var _FeedValue = class _FeedValue {
|
|
|
480
558
|
this.log("Instance destroyed during config fetch, aborting init");
|
|
481
559
|
return;
|
|
482
560
|
}
|
|
561
|
+
const baseConfig = {
|
|
562
|
+
position: configResponse.config.position ?? "bottom-right",
|
|
563
|
+
triggerText: configResponse.config.triggerText ?? "Feedback",
|
|
564
|
+
triggerIcon: configResponse.config.triggerIcon ?? "none",
|
|
565
|
+
formTitle: configResponse.config.formTitle ?? "Share your feedback",
|
|
566
|
+
submitButtonText: configResponse.config.submitButtonText ?? "Submit",
|
|
567
|
+
thankYouMessage: configResponse.config.thankYouMessage ?? "Thank you for your feedback!",
|
|
568
|
+
showBranding: configResponse.config.showBranding ?? true,
|
|
569
|
+
customFields: configResponse.config.customFields,
|
|
570
|
+
// Reaction config (for reaction widgets) - only include if defined
|
|
571
|
+
...configResponse.config.template && { template: configResponse.config.template },
|
|
572
|
+
...configResponse.config.options && { options: configResponse.config.options },
|
|
573
|
+
followUpLabel: configResponse.config.followUpLabel ?? "Tell us more (optional)",
|
|
574
|
+
submitText: configResponse.config.submitText ?? "Send"
|
|
575
|
+
};
|
|
483
576
|
this.widgetConfig = {
|
|
484
577
|
widgetId: configResponse.widget_id,
|
|
485
578
|
widgetKey: configResponse.widget_key,
|
|
486
579
|
appId: "",
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
triggerText: configResponse.config.triggerText ?? "Feedback",
|
|
490
|
-
triggerIcon: configResponse.config.triggerIcon ?? "none",
|
|
491
|
-
formTitle: configResponse.config.formTitle ?? "Share your feedback",
|
|
492
|
-
submitButtonText: configResponse.config.submitButtonText ?? "Submit",
|
|
493
|
-
thankYouMessage: configResponse.config.thankYouMessage ?? "Thank you for your feedback!",
|
|
494
|
-
showBranding: configResponse.config.showBranding ?? true,
|
|
495
|
-
customFields: configResponse.config.customFields
|
|
496
|
-
},
|
|
580
|
+
type: configResponse.type ?? "feedback",
|
|
581
|
+
config: baseConfig,
|
|
497
582
|
styling: {
|
|
498
583
|
primaryColor: configResponse.styling.primaryColor ?? "#3b82f6",
|
|
499
584
|
backgroundColor: configResponse.styling.backgroundColor ?? "#ffffff",
|
|
@@ -711,6 +796,108 @@ var _FeedValue = class _FeedValue {
|
|
|
711
796
|
}
|
|
712
797
|
return result;
|
|
713
798
|
}
|
|
799
|
+
// ===========================================================================
|
|
800
|
+
// Reactions
|
|
801
|
+
// ===========================================================================
|
|
802
|
+
/**
|
|
803
|
+
* Get reaction options from widget config.
|
|
804
|
+
* Returns null if widget is not a reaction type.
|
|
805
|
+
*/
|
|
806
|
+
getReactionOptions() {
|
|
807
|
+
if (!this.widgetConfig || this.widgetConfig.type !== "reaction") {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
const config = this.widgetConfig.config;
|
|
811
|
+
if (config.template) {
|
|
812
|
+
return this.getTemplateOptions(config.template);
|
|
813
|
+
}
|
|
814
|
+
return config.options ?? null;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get predefined options for a reaction template
|
|
818
|
+
*/
|
|
819
|
+
getTemplateOptions(template) {
|
|
820
|
+
const templates = {
|
|
821
|
+
thumbs: [
|
|
822
|
+
{ label: "Helpful", value: "helpful", icon: "thumbs-up", showFollowUp: false },
|
|
823
|
+
{ label: "Not Helpful", value: "not_helpful", icon: "thumbs-down", showFollowUp: true }
|
|
824
|
+
],
|
|
825
|
+
helpful: [
|
|
826
|
+
{ label: "Yes", value: "yes", icon: "check", showFollowUp: false },
|
|
827
|
+
{ label: "No", value: "no", icon: "x", showFollowUp: true }
|
|
828
|
+
],
|
|
829
|
+
emoji: [
|
|
830
|
+
{ label: "Angry", value: "angry", icon: "\u{1F620}", showFollowUp: true },
|
|
831
|
+
{ label: "Disappointed", value: "disappointed", icon: "\u{1F61E}", showFollowUp: true },
|
|
832
|
+
{ label: "Neutral", value: "neutral", icon: "\u{1F610}", showFollowUp: false },
|
|
833
|
+
{ label: "Satisfied", value: "satisfied", icon: "\u{1F60A}", showFollowUp: false },
|
|
834
|
+
{ label: "Excited", value: "excited", icon: "\u{1F60D}", showFollowUp: false }
|
|
835
|
+
],
|
|
836
|
+
rating: [
|
|
837
|
+
{ label: "1", value: "1", icon: "\u2B50", showFollowUp: true },
|
|
838
|
+
{ label: "2", value: "2", icon: "\u2B50\u2B50", showFollowUp: true },
|
|
839
|
+
{ label: "3", value: "3", icon: "\u2B50\u2B50\u2B50", showFollowUp: false },
|
|
840
|
+
{ label: "4", value: "4", icon: "\u2B50\u2B50\u2B50\u2B50", showFollowUp: false },
|
|
841
|
+
{ label: "5", value: "5", icon: "\u2B50\u2B50\u2B50\u2B50\u2B50", showFollowUp: false }
|
|
842
|
+
]
|
|
843
|
+
};
|
|
844
|
+
return templates[template] ?? [];
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Submit a reaction.
|
|
848
|
+
* @param value - Selected reaction option value
|
|
849
|
+
* @param options - Optional follow-up text
|
|
850
|
+
*/
|
|
851
|
+
async react(value, options) {
|
|
852
|
+
if (!this.state.isReady) {
|
|
853
|
+
throw new Error("Widget not ready");
|
|
854
|
+
}
|
|
855
|
+
if (!this.widgetConfig || this.widgetConfig.type !== "reaction") {
|
|
856
|
+
throw new Error("This is not a reaction widget");
|
|
857
|
+
}
|
|
858
|
+
const reactionOptions = this.getReactionOptions();
|
|
859
|
+
if (!reactionOptions) {
|
|
860
|
+
throw new Error("No reaction options configured");
|
|
861
|
+
}
|
|
862
|
+
const selectedOption = reactionOptions.find((opt) => opt.value === value);
|
|
863
|
+
if (!selectedOption) {
|
|
864
|
+
const validValues = reactionOptions.map((opt) => opt.value).join(", ");
|
|
865
|
+
throw new Error(`Invalid reaction value. Must be one of: ${validValues}`);
|
|
866
|
+
}
|
|
867
|
+
this.emitter.emit("react", { value, hasFollowUp: selectedOption.showFollowUp });
|
|
868
|
+
this.updateState({ isSubmitting: true });
|
|
869
|
+
try {
|
|
870
|
+
const reactionData = {
|
|
871
|
+
value,
|
|
872
|
+
metadata: {
|
|
873
|
+
page_url: typeof window !== "undefined" ? window.location.href : ""
|
|
874
|
+
},
|
|
875
|
+
...options?.followUp && { followUp: options.followUp }
|
|
876
|
+
};
|
|
877
|
+
await this.apiClient.submitReaction(this.widgetId, reactionData);
|
|
878
|
+
const emitData = options?.followUp ? { value, followUp: options.followUp } : { value };
|
|
879
|
+
this.emitter.emit("reactSubmit", emitData);
|
|
880
|
+
this.log("Reaction submitted", { value });
|
|
881
|
+
} catch (error) {
|
|
882
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
883
|
+
this.emitter.emit("reactError", err);
|
|
884
|
+
throw err;
|
|
885
|
+
} finally {
|
|
886
|
+
this.updateState({ isSubmitting: false });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Check if widget is a reaction type
|
|
891
|
+
*/
|
|
892
|
+
isReaction() {
|
|
893
|
+
return this.widgetConfig?.type === "reaction";
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Get widget type ('feedback' or 'reaction')
|
|
897
|
+
*/
|
|
898
|
+
getWidgetType() {
|
|
899
|
+
return this.widgetConfig?.type ?? "feedback";
|
|
900
|
+
}
|
|
714
901
|
/**
|
|
715
902
|
* Validate feedback data before submission
|
|
716
903
|
* @throws Error if validation fails
|
|
@@ -1295,6 +1482,14 @@ __publicField(_FeedValue, "TRIGGER_ICONS", {
|
|
|
1295
1482
|
});
|
|
1296
1483
|
var FeedValue = _FeedValue;
|
|
1297
1484
|
|
|
1298
|
-
|
|
1485
|
+
// src/types.ts
|
|
1486
|
+
var NEGATIVE_OPTIONS_MAP = {
|
|
1487
|
+
thumbs: ["not_helpful"],
|
|
1488
|
+
helpful: ["no"],
|
|
1489
|
+
emoji: ["angry", "disappointed"],
|
|
1490
|
+
rating: ["1", "2"]
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
export { ApiClient, DEFAULT_API_BASE_URL, FeedValue, NEGATIVE_OPTIONS_MAP, TypedEventEmitter, clearFingerprint, generateFingerprint };
|
|
1299
1494
|
//# sourceMappingURL=index.js.map
|
|
1300
1495
|
//# sourceMappingURL=index.js.map
|