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