@flow-industries/id 0.23.2 → 0.23.4
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.
|
@@ -34,7 +34,7 @@ export function createProfileButton(options) {
|
|
|
34
34
|
display: "flex",
|
|
35
35
|
alignItems: "center",
|
|
36
36
|
gap: "8px",
|
|
37
|
-
border: "
|
|
37
|
+
border: "0",
|
|
38
38
|
borderRadius: "999px",
|
|
39
39
|
padding: "10px 16px",
|
|
40
40
|
background: "Canvas",
|
|
@@ -74,17 +74,26 @@ export function createProfileButton(options) {
|
|
|
74
74
|
fallback.rel = "noopener noreferrer";
|
|
75
75
|
fallback.textContent = "Open Flow ID";
|
|
76
76
|
fallback.hidden = true;
|
|
77
|
-
let
|
|
77
|
+
let lastUser = null;
|
|
78
|
+
let imageUrl = null;
|
|
79
|
+
avatar.textContent = "F";
|
|
78
80
|
let profileRequest = null;
|
|
79
|
-
function update() {
|
|
81
|
+
function update(refresh = false) {
|
|
80
82
|
const user = flow.getState().user;
|
|
81
83
|
name.textContent = user?.username ?? "Flow ID";
|
|
82
84
|
button.setAttribute("aria-label", `Open ${user?.username ?? "your"} Flow ID account in a new tab`);
|
|
83
|
-
|
|
85
|
+
const identityChanged = user?.id !== lastUser?.id ||
|
|
86
|
+
user?.username !== lastUser?.username ||
|
|
87
|
+
user?.isGuest !== lastUser?.isGuest;
|
|
88
|
+
if (identityChanged) {
|
|
89
|
+
profileRequest?.abort();
|
|
90
|
+
profileRequest = null;
|
|
91
|
+
imageUrl = null;
|
|
92
|
+
avatar.textContent = user?.username.slice(0, 1).toUpperCase() ?? "F";
|
|
93
|
+
lastUser = user;
|
|
94
|
+
}
|
|
95
|
+
else if (!refresh || profileRequest)
|
|
84
96
|
return;
|
|
85
|
-
lastUsername = user?.username;
|
|
86
|
-
profileRequest?.abort();
|
|
87
|
-
avatar.textContent = user?.username.slice(0, 1).toUpperCase() ?? "F";
|
|
88
97
|
if (!user || user.isGuest)
|
|
89
98
|
return;
|
|
90
99
|
const controller = new AbortController();
|
|
@@ -98,7 +107,14 @@ export function createProfileButton(options) {
|
|
|
98
107
|
return;
|
|
99
108
|
// SAFETY: Flow ID owns the public profile endpoint and its optional image field.
|
|
100
109
|
const profile = (await response.json());
|
|
101
|
-
if (controller.signal.aborted
|
|
110
|
+
if (controller.signal.aborted)
|
|
111
|
+
return;
|
|
112
|
+
if (profile.image === null) {
|
|
113
|
+
imageUrl = null;
|
|
114
|
+
avatar.textContent = user.username.slice(0, 1).toUpperCase();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!profile.image || profile.image === imageUrl)
|
|
102
118
|
return;
|
|
103
119
|
const image = document.createElement("img");
|
|
104
120
|
image.src = profile.image;
|
|
@@ -108,9 +124,17 @@ export function createProfileButton(options) {
|
|
|
108
124
|
height: "100%",
|
|
109
125
|
objectFit: "cover",
|
|
110
126
|
});
|
|
127
|
+
await image.decode();
|
|
128
|
+
if (controller.signal.aborted)
|
|
129
|
+
return;
|
|
130
|
+
imageUrl = profile.image;
|
|
111
131
|
avatar.replaceChildren(image);
|
|
112
132
|
})
|
|
113
|
-
.catch(() => { })
|
|
133
|
+
.catch(() => { })
|
|
134
|
+
.finally(() => {
|
|
135
|
+
if (profileRequest === controller)
|
|
136
|
+
profileRequest = null;
|
|
137
|
+
});
|
|
114
138
|
}
|
|
115
139
|
button.addEventListener("click", () => {
|
|
116
140
|
const opened = flow.openAccount();
|
|
@@ -120,8 +144,7 @@ export function createProfileButton(options) {
|
|
|
120
144
|
fallback.hidden = opened;
|
|
121
145
|
});
|
|
122
146
|
function refresh() {
|
|
123
|
-
|
|
124
|
-
update();
|
|
147
|
+
update(true);
|
|
125
148
|
}
|
|
126
149
|
window.addEventListener("focus", refresh);
|
|
127
150
|
function setTheme(theme) {
|
|
@@ -130,7 +153,7 @@ export function createProfileButton(options) {
|
|
|
130
153
|
setTheme(options.theme ?? "light dark");
|
|
131
154
|
container.append(button, notice, fallback);
|
|
132
155
|
update();
|
|
133
|
-
const unsubscribe = flow.subscribe(update);
|
|
156
|
+
const unsubscribe = flow.subscribe(() => update());
|
|
134
157
|
return {
|
|
135
158
|
setTheme,
|
|
136
159
|
destroy() {
|
|
@@ -90,6 +90,10 @@ export type DialogState = {
|
|
|
90
90
|
customLabels?: DialogCustomLabels;
|
|
91
91
|
};
|
|
92
92
|
export type SignUpProps = {
|
|
93
|
+
verifiedEmail?: {
|
|
94
|
+
email: string;
|
|
95
|
+
nonce: string;
|
|
96
|
+
};
|
|
93
97
|
rpId: string;
|
|
94
98
|
accessKeyHash?: string | undefined;
|
|
95
99
|
guestToken?: string | undefined;
|
|
@@ -10,7 +10,7 @@ export type AuthMode = "sign-up" | "sign-in";
|
|
|
10
10
|
/** Client-only funnel steps reported via the `/api/events` beacon. */
|
|
11
11
|
export type FunnelStep = "mode_selected" | "email_entered" | "ceremony_started" | "ceremony_failed" | "done_shown";
|
|
12
12
|
export type AuthEventName = "auth.username.checked" | "auth.signup.succeeded" | "auth.signup.failed" | "auth.otp.sent" | "auth.otp.verified" | "auth.otp.failed" | "auth.email.verify.sent" | "auth.email.verify.succeeded" | "auth.email.verify.failed" | "auth.email.changed" | "auth.avatar.upload.succeeded" | "auth.avatar.upload.failed" | "auth.bio.update.succeeded" | "auth.bio.update.failed" | "auth.profile.view.failed" | "auth.profile.view.uncapped" | "auth.challenge.issued" | "auth.signin.succeeded" | "auth.signin.failed" | "auth.passkey.added" | "auth.passkey.add_failed" | "auth.restore.succeeded" | "auth.restore.failed" | "auth.restore.no_session" | "auth.refresh.succeeded" | "auth.refresh.failed" | "auth.refresh.reuse" | "auth.refresh.grace" | "auth.jwt.verified" | "auth.jwt.rejected" | "auth.signout" | "auth.session.revoked" | "auth.session.revoked_all" | "auth.audience.rejected" | "auth.audience.minted" | "auth.guest.created" | "auth.guest.restored" | "auth.guest.upgraded" | "auth.guest.failed" | "auth.role.granted" | "auth.role.revoked" | "auth.account.deleted" | "auth.account.exported" | "room.restriction.issued" | "room.restriction.lifted" | "auth.funnel.mode_selected" | "auth.funnel.email_entered" | "auth.funnel.ceremony_started" | "auth.funnel.ceremony_failed" | "auth.funnel.done_shown" | "room_event.activated" | "room_event.completed" | "room_event.reminder_dispatched" | "room_event.reminder_failed";
|
|
13
|
-
export type AuthErrorCode = "username_taken" | "credential_taken" | "email_taken" | "email_not_verified" | "no_email" | "image_upload_forbidden" | "image_upload_rate_limited" | "invalid_image_type" | "image_too_large" | "image_upload_failed" | "bio_forbidden" | "bio_invalid" | "bio_too_long" | "profile_rate_limited" | "profile_ip_missing" | "otp_invalid" | "otp_expired" | "otp_attempts_exceeded" | "otp_resend_cooldown" | "otp_resend_limit" | "otp_global_limit" | "otp_send_failed" | "challenge_expired" | "unknown_credential" | "invalid_assertion_type" | "invalid_assertion_origin" | "user_verification_required" | "invalid_signature" | "user_not_found" | "missing_audience" | "audience_not_allowed" | "audience_mismatch" | "no_session" | "no_passkey" | "refresh_token_invalid" | "refresh_token_expired" | "refresh_epoch_stale" | "refresh_user_epoch_stale" | "refresh_reuse_detected" | "guest_rate_limited" | "guest_global_limit" | "guest_username_exhausted" | "guest_session" | "passkey_exists" | "already_upgraded" | "malformed_token" | "unknown_key" | "verification_failed" | "webauthn_not_allowed" | "webauthn_security" | "webauthn_invalid_state" | "webauthn_not_supported" | "webauthn_constraint" | "webauthn_aborted" | "webauthn_unknown" | "internal_error";
|
|
13
|
+
export type AuthErrorCode = "username_taken" | "credential_taken" | "email_taken" | "email_not_verified" | "no_email" | "image_upload_forbidden" | "image_upload_rate_limited" | "invalid_image_type" | "image_too_large" | "image_upload_failed" | "bio_forbidden" | "bio_invalid" | "bio_too_long" | "profile_rate_limited" | "profile_ip_missing" | "otp_invalid" | "otp_expired" | "otp_attempts_exceeded" | "otp_resend_cooldown" | "otp_resend_limit" | "otp_global_limit" | "otp_send_failed" | "challenge_expired" | "unknown_credential" | "invalid_assertion_type" | "invalid_assertion_origin" | "user_verification_required" | "invalid_signature" | "user_not_found" | "missing_audience" | "audience_not_allowed" | "audience_mismatch" | "no_session" | "no_passkey" | "refresh_token_invalid" | "refresh_token_expired" | "refresh_epoch_stale" | "refresh_user_epoch_stale" | "refresh_reuse_detected" | "refresh_token_revoked" | "guest_rate_limited" | "guest_global_limit" | "guest_username_exhausted" | "guest_session" | "passkey_exists" | "already_upgraded" | "malformed_token" | "unknown_key" | "verification_failed" | "webauthn_not_allowed" | "webauthn_security" | "webauthn_invalid_state" | "webauthn_not_supported" | "webauthn_constraint" | "webauthn_aborted" | "webauthn_unknown" | "internal_error";
|
|
14
14
|
/** One flat record per event = one row in the `auth_events` stream. */
|
|
15
15
|
export interface AuthEventRecord {
|
|
16
16
|
service: "auth";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flow-industries/id",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
],
|
|
141
141
|
"dependencies": {
|
|
142
142
|
"@aws-sdk/client-s3": "^3.1073.0",
|
|
143
|
-
"@flow-industries/ui": "^0.
|
|
143
|
+
"@flow-industries/ui": "^0.23.3",
|
|
144
144
|
"@hono/otel": "^1.1.2",
|
|
145
145
|
"@openobserve/browser-logs": "^0.3.1",
|
|
146
146
|
"@openobserve/browser-rum": "^0.3.1",
|