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