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