@usero/sdk 1.1.1 → 1.1.3
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/plugins/session-replay.cjs +2 -1
- package/dist/plugins/session-replay.cjs.map +1 -1
- package/dist/plugins/session-replay.d.cts +5 -0
- package/dist/plugins/session-replay.d.ts +5 -0
- package/dist/plugins/session-replay.js +2 -1
- package/dist/plugins/session-replay.js.map +1 -1
- package/dist/plugins/user-test.cjs +18 -3
- package/dist/plugins/user-test.cjs.map +1 -1
- package/dist/plugins/user-test.d.cts +5 -0
- package/dist/plugins/user-test.d.ts +5 -0
- package/dist/plugins/user-test.js +18 -3
- package/dist/plugins/user-test.js.map +1 -1
- package/dist/react.cjs +122 -34
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +5 -0
- package/dist/react.d.ts +5 -0
- package/dist/react.js +122 -34
- package/dist/react.js.map +1 -1
- package/dist/usero.iife.js +35 -35
- package/dist/usero.iife.js.map +1 -1
- package/dist/vanilla.cjs +122 -34
- package/dist/vanilla.cjs.map +1 -1
- package/dist/vanilla.d.cts +5 -0
- package/dist/vanilla.d.ts +5 -0
- package/dist/vanilla.js +122 -34
- package/dist/vanilla.js.map +1 -1
- package/package.json +1 -1
package/dist/vanilla.js
CHANGED
|
@@ -157,7 +157,11 @@ function getGradientEnd(color) {
|
|
|
157
157
|
|
|
158
158
|
// src/identity.ts
|
|
159
159
|
var ANON_STORAGE_KEY = "usero:anonymous-id";
|
|
160
|
+
var SDK_SESSION_STORAGE_KEY = "usero:session-replay:sdk-session-id";
|
|
160
161
|
var cachedAnonymousId = null;
|
|
162
|
+
var cachedSdkSessionId = null;
|
|
163
|
+
var currentUserId = null;
|
|
164
|
+
var replayStartMs = null;
|
|
161
165
|
var lastIdentifyFingerprint = null;
|
|
162
166
|
function generateRandomId() {
|
|
163
167
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -188,6 +192,21 @@ function safeWriteLocalStorage(key, value) {
|
|
|
188
192
|
} catch {
|
|
189
193
|
}
|
|
190
194
|
}
|
|
195
|
+
function safeReadSessionStorage(key) {
|
|
196
|
+
if (typeof window === "undefined") return null;
|
|
197
|
+
try {
|
|
198
|
+
return window.sessionStorage?.getItem(key) ?? null;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function safeWriteSessionStorage(key, value) {
|
|
204
|
+
if (typeof window === "undefined") return;
|
|
205
|
+
try {
|
|
206
|
+
window.sessionStorage?.setItem(key, value);
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
191
210
|
function getOrMintAnonymousId() {
|
|
192
211
|
if (cachedAnonymousId) return cachedAnonymousId;
|
|
193
212
|
const existing = safeReadLocalStorage(ANON_STORAGE_KEY);
|
|
@@ -205,8 +224,30 @@ function rotateAnonymousId() {
|
|
|
205
224
|
cachedAnonymousId = id;
|
|
206
225
|
safeWriteLocalStorage(ANON_STORAGE_KEY, id);
|
|
207
226
|
lastIdentifyFingerprint = null;
|
|
227
|
+
currentUserId = null;
|
|
228
|
+
return id;
|
|
229
|
+
}
|
|
230
|
+
function getOrMintSdkSessionId() {
|
|
231
|
+
if (cachedSdkSessionId) return cachedSdkSessionId;
|
|
232
|
+
const existing = safeReadSessionStorage(SDK_SESSION_STORAGE_KEY);
|
|
233
|
+
if (existing && /^[a-z0-9-]{8,}$/i.test(existing)) {
|
|
234
|
+
cachedSdkSessionId = existing;
|
|
235
|
+
return existing;
|
|
236
|
+
}
|
|
237
|
+
const id = generateRandomId();
|
|
238
|
+
safeWriteSessionStorage(SDK_SESSION_STORAGE_KEY, id);
|
|
239
|
+
cachedSdkSessionId = id;
|
|
208
240
|
return id;
|
|
209
241
|
}
|
|
242
|
+
function getCurrentUserId() {
|
|
243
|
+
return currentUserId;
|
|
244
|
+
}
|
|
245
|
+
function publishReplayStartMs(epochMs) {
|
|
246
|
+
if (replayStartMs === null) replayStartMs = epochMs;
|
|
247
|
+
}
|
|
248
|
+
function getReplayStartMs() {
|
|
249
|
+
return replayStartMs;
|
|
250
|
+
}
|
|
210
251
|
function fingerprintUser(anonymousId, user) {
|
|
211
252
|
const traits = user.traits ?? {};
|
|
212
253
|
const keys = Object.keys(traits).sort();
|
|
@@ -215,6 +256,7 @@ function fingerprintUser(anonymousId, user) {
|
|
|
215
256
|
}
|
|
216
257
|
async function identifyIfChanged(transport, user) {
|
|
217
258
|
const anonymousId = getOrMintAnonymousId();
|
|
259
|
+
currentUserId = user.id;
|
|
218
260
|
const fp = fingerprintUser(anonymousId, user);
|
|
219
261
|
if (fp === lastIdentifyFingerprint) return false;
|
|
220
262
|
const url = `${transport.apiUrl.replace(/\/$/, "")}/api/identify`;
|
|
@@ -903,7 +945,15 @@ function initUseroFeedbackWidget(props) {
|
|
|
903
945
|
} else {
|
|
904
946
|
resolveAndApplyGetUser();
|
|
905
947
|
}
|
|
906
|
-
}
|
|
948
|
+
},
|
|
949
|
+
// Core-owned cross-cutting identity. Every plugin reads the same
|
|
950
|
+
// source of truth in identity.ts, so user-test and session-replay
|
|
951
|
+
// agree on the per-tab sdkSessionId without importing each other.
|
|
952
|
+
getSdkSessionId: () => getOrMintSdkSessionId(),
|
|
953
|
+
getAnonymousId: () => getOrMintAnonymousId(),
|
|
954
|
+
getUserId: () => getCurrentUserId(),
|
|
955
|
+
getReplayStartMs: () => getReplayStartMs(),
|
|
956
|
+
publishReplayStartMs: (epochMs) => publishReplayStartMs(epochMs)
|
|
907
957
|
};
|
|
908
958
|
pluginContexts.set(plugin.name, ctx);
|
|
909
959
|
if (plugin.onInit) {
|
|
@@ -985,21 +1035,22 @@ function initUseroFeedbackWidget(props) {
|
|
|
985
1035
|
screenshotError = null;
|
|
986
1036
|
if (!file.type.startsWith("image/")) {
|
|
987
1037
|
screenshotError = "Image files only";
|
|
988
|
-
|
|
1038
|
+
updateUploadExtras();
|
|
989
1039
|
return;
|
|
990
1040
|
}
|
|
991
1041
|
if (file.size > MAX_SCREENSHOT_BYTES) {
|
|
992
1042
|
screenshotError = "Max 10MB";
|
|
993
|
-
|
|
1043
|
+
updateUploadExtras();
|
|
994
1044
|
return;
|
|
995
1045
|
}
|
|
996
1046
|
if (screenshots.length >= MAX_SCREENSHOTS) {
|
|
997
1047
|
screenshotError = `Max ${MAX_SCREENSHOTS} screenshots`;
|
|
998
|
-
|
|
1048
|
+
updateUploadExtras();
|
|
999
1049
|
return;
|
|
1000
1050
|
}
|
|
1001
1051
|
isUploadingScreenshot = true;
|
|
1002
|
-
|
|
1052
|
+
updateUploadButton();
|
|
1053
|
+
updateUploadExtras();
|
|
1003
1054
|
try {
|
|
1004
1055
|
const uploaded = await apiClient.uploadScreenshot(file, clientId);
|
|
1005
1056
|
screenshots = [...screenshots, uploaded];
|
|
@@ -1007,12 +1058,14 @@ function initUseroFeedbackWidget(props) {
|
|
|
1007
1058
|
screenshotError = err instanceof Error ? err.message : "Upload failed";
|
|
1008
1059
|
} finally {
|
|
1009
1060
|
isUploadingScreenshot = false;
|
|
1010
|
-
|
|
1061
|
+
updateUploadButton();
|
|
1062
|
+
updateUploadExtras();
|
|
1011
1063
|
}
|
|
1012
1064
|
}
|
|
1013
1065
|
function removeScreenshot(index) {
|
|
1014
1066
|
screenshots = screenshots.filter((_, i) => i !== index);
|
|
1015
|
-
|
|
1067
|
+
updateUploadButton();
|
|
1068
|
+
updateUploadExtras();
|
|
1016
1069
|
}
|
|
1017
1070
|
function close() {
|
|
1018
1071
|
if (!isOpen) return;
|
|
@@ -1020,6 +1073,57 @@ function initUseroFeedbackWidget(props) {
|
|
|
1020
1073
|
onClose?.();
|
|
1021
1074
|
render();
|
|
1022
1075
|
}
|
|
1076
|
+
function buildUploadButtonInner() {
|
|
1077
|
+
return isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot";
|
|
1078
|
+
}
|
|
1079
|
+
function buildUploadButtonHtml() {
|
|
1080
|
+
const atMax = screenshots.length >= MAX_SCREENSHOTS;
|
|
1081
|
+
const btnDisabled = isUploadingScreenshot || atMax;
|
|
1082
|
+
return `
|
|
1083
|
+
<input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
|
|
1084
|
+
<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};">
|
|
1085
|
+
${buildUploadButtonInner()}
|
|
1086
|
+
</button>
|
|
1087
|
+
`;
|
|
1088
|
+
}
|
|
1089
|
+
function buildUploadExtrasHtml() {
|
|
1090
|
+
const atMax = screenshots.length >= MAX_SCREENSHOTS;
|
|
1091
|
+
const previewsHtml = screenshots.map(
|
|
1092
|
+
(shot, i) => `
|
|
1093
|
+
<div class="fb-sp">
|
|
1094
|
+
<img src="${escapeHtml(shot.url)}" alt="Screenshot ${i + 1}" class="fb-si" />
|
|
1095
|
+
<button type="button" class="fb-sr" data-role="screenshot-remove" data-index="${i}" aria-label="Remove screenshot">\u2715</button>
|
|
1096
|
+
</div>
|
|
1097
|
+
`
|
|
1098
|
+
).join("");
|
|
1099
|
+
const errorHtml = screenshotError ? `<div class="fb-upe">\u26A0 ${escapeHtml(screenshotError)}</div>` : "";
|
|
1100
|
+
const limitHtml = atMax ? `<div class="fb-sl">Max ${MAX_SCREENSHOTS}</div>` : "";
|
|
1101
|
+
return screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
|
|
1102
|
+
}
|
|
1103
|
+
function updateUploadButton() {
|
|
1104
|
+
if (!showScreenshotOption) return;
|
|
1105
|
+
const pickBtn = panelEl.querySelector(
|
|
1106
|
+
'button[data-role="screenshot-pick"]'
|
|
1107
|
+
);
|
|
1108
|
+
if (!pickBtn) return;
|
|
1109
|
+
const atMax = screenshots.length >= MAX_SCREENSHOTS;
|
|
1110
|
+
const btnDisabled = isUploadingScreenshot || atMax;
|
|
1111
|
+
pickBtn.disabled = btnDisabled;
|
|
1112
|
+
pickBtn.classList.toggle("fb-upb--dis", btnDisabled);
|
|
1113
|
+
pickBtn.innerHTML = buildUploadButtonInner();
|
|
1114
|
+
}
|
|
1115
|
+
function updateUploadExtras() {
|
|
1116
|
+
if (!showScreenshotOption) return;
|
|
1117
|
+
const container = panelEl.querySelector(".fb-up");
|
|
1118
|
+
if (!container) return;
|
|
1119
|
+
container.innerHTML = buildUploadExtrasHtml();
|
|
1120
|
+
container.querySelectorAll('button[data-role="screenshot-remove"]').forEach((btn) => {
|
|
1121
|
+
btn.addEventListener("click", () => {
|
|
1122
|
+
const idx = Number(btn.dataset.index);
|
|
1123
|
+
if (Number.isInteger(idx)) removeScreenshot(idx);
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1023
1127
|
async function submitForm() {
|
|
1024
1128
|
if (isSubmitting) return;
|
|
1025
1129
|
isSubmitting = true;
|
|
@@ -1137,30 +1241,8 @@ function initUseroFeedbackWidget(props) {
|
|
|
1137
1241
|
`;
|
|
1138
1242
|
}).join("");
|
|
1139
1243
|
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>` : "";
|
|
1140
|
-
const uploadBtnHtml = showScreenshotOption ? (
|
|
1141
|
-
|
|
1142
|
-
const btnDisabled = isUploadingScreenshot || atMax;
|
|
1143
|
-
return `
|
|
1144
|
-
<input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
|
|
1145
|
-
<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};">
|
|
1146
|
-
${isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot"}
|
|
1147
|
-
</button>
|
|
1148
|
-
`;
|
|
1149
|
-
})() : "";
|
|
1150
|
-
const uploadExtrasHtml = showScreenshotOption ? (() => {
|
|
1151
|
-
const atMax = screenshots.length >= MAX_SCREENSHOTS;
|
|
1152
|
-
const previewsHtml = screenshots.map(
|
|
1153
|
-
(shot, i) => `
|
|
1154
|
-
<div class="fb-sp">
|
|
1155
|
-
<img src="${escapeHtml(shot.url)}" alt="Screenshot ${i + 1}" class="fb-si" />
|
|
1156
|
-
<button type="button" class="fb-sr" data-role="screenshot-remove" data-index="${i}" aria-label="Remove screenshot">\u2715</button>
|
|
1157
|
-
</div>
|
|
1158
|
-
`
|
|
1159
|
-
).join("");
|
|
1160
|
-
const errorHtml = screenshotError ? `<div class="fb-upe">\u26A0 ${escapeHtml(screenshotError)}</div>` : "";
|
|
1161
|
-
const limitHtml = atMax ? `<div class="fb-sl">Max ${MAX_SCREENSHOTS}</div>` : "";
|
|
1162
|
-
return screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
|
|
1163
|
-
})() : "";
|
|
1244
|
+
const uploadBtnHtml = showScreenshotOption ? buildUploadButtonHtml() : "";
|
|
1245
|
+
const uploadExtrasHtml = showScreenshotOption ? buildUploadExtrasHtml() : "";
|
|
1164
1246
|
const emailBlockHtml = showEmailOption ? `
|
|
1165
1247
|
<div class="fb-email">
|
|
1166
1248
|
<label class="fb-email-lbl" style="color:${theme.text}">
|
|
@@ -1186,7 +1268,7 @@ function initUseroFeedbackWidget(props) {
|
|
|
1186
1268
|
${uploadBtnHtml}
|
|
1187
1269
|
<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>
|
|
1188
1270
|
</div>
|
|
1189
|
-
${
|
|
1271
|
+
${showScreenshotOption ? `<div class="fb-up">${uploadExtrasHtml}</div>` : ""}
|
|
1190
1272
|
${emailBlockHtml}
|
|
1191
1273
|
<button class="fb-sub ${submitDisabled ? "fb-sub--dis" : ""}" type="submit" aria-label="Submit" ${submitDisabled ? "disabled" : ""} style="${submitStyle}">
|
|
1192
1274
|
${isSubmitting ? '<span class="fb-spin"></span>' : ""}
|
|
@@ -1283,10 +1365,16 @@ function initUseroFeedbackWidget(props) {
|
|
|
1283
1365
|
if (isOpen) close();
|
|
1284
1366
|
else open();
|
|
1285
1367
|
});
|
|
1286
|
-
backdropEl.addEventListener("click",
|
|
1368
|
+
backdropEl.addEventListener("click", () => {
|
|
1369
|
+
if (isUploadingScreenshot || isSubmitting) return;
|
|
1370
|
+
close();
|
|
1371
|
+
});
|
|
1287
1372
|
const onKeyDown = (e) => {
|
|
1288
1373
|
if (!isOpen) return;
|
|
1289
|
-
if (e.key === "Escape")
|
|
1374
|
+
if (e.key === "Escape") {
|
|
1375
|
+
if (isUploadingScreenshot || isSubmitting) return;
|
|
1376
|
+
close();
|
|
1377
|
+
}
|
|
1290
1378
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
1291
1379
|
e.preventDefault();
|
|
1292
1380
|
void submitForm();
|