@hotosm/hanko-auth 0.5.1 → 0.5.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.
- package/README.md +59 -53
- package/dist/hanko-auth.esm.js +938 -1039
- package/dist/hanko-auth.iife.js +35 -32
- package/dist/hanko-auth.umd.js +35 -32
- package/package.json +8 -9
- package/src/hanko-auth.styles.ts +6 -6
- package/src/hanko-auth.ts +39 -20
- package/src/hanko-i18n-en.ts +2 -2
- package/src/hanko-i18n-es.ts +2 -2
- package/src/hanko-i18n-fr.ts +2 -2
- package/src/hanko-i18n-pt.ts +2 -2
- package/src/hanko-translations.ts +20 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotosm/hanko-auth",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Web component for HOTOSM SSO authentication with Hanko and OSM integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/hanko-auth.umd.js",
|
|
@@ -17,13 +17,6 @@
|
|
|
17
17
|
"dist",
|
|
18
18
|
"src"
|
|
19
19
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"dev": "vite",
|
|
22
|
-
"build": "vite build",
|
|
23
|
-
"watch": "vite build --watch",
|
|
24
|
-
"preview": "vite preview",
|
|
25
|
-
"prepublishOnly": "pnpm build"
|
|
26
|
-
},
|
|
27
20
|
"keywords": [
|
|
28
21
|
"hotosm",
|
|
29
22
|
"hanko",
|
|
@@ -51,5 +44,11 @@
|
|
|
51
44
|
},
|
|
52
45
|
"devDependencies": {
|
|
53
46
|
"vite": "^5.0.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "vite",
|
|
50
|
+
"build": "vite build",
|
|
51
|
+
"watch": "vite build --watch",
|
|
52
|
+
"preview": "vite preview"
|
|
54
53
|
}
|
|
55
|
-
}
|
|
54
|
+
}
|
package/src/hanko-auth.styles.ts
CHANGED
|
@@ -274,7 +274,7 @@ export const styles = css`
|
|
|
274
274
|
|
|
275
275
|
.login-link {
|
|
276
276
|
color: var(--login-btn-text-color, white);
|
|
277
|
-
font-size: var(--
|
|
277
|
+
font-size: var(--hot-font-size-small);
|
|
278
278
|
border-radius: var(
|
|
279
279
|
--login-btn-border-radius,
|
|
280
280
|
var(--hot-border-radius-medium)
|
|
@@ -303,11 +303,11 @@ export const styles = css`
|
|
|
303
303
|
border: none;
|
|
304
304
|
}
|
|
305
305
|
.login-link.filled.primary {
|
|
306
|
-
background: var(--
|
|
306
|
+
background: var(--hot-color-gray-950);
|
|
307
307
|
color: var(--login-btn-text-color, white);
|
|
308
308
|
}
|
|
309
309
|
.login-link.filled.primary:hover {
|
|
310
|
-
background: var(--
|
|
310
|
+
background: var(--hot-color-primary-800);
|
|
311
311
|
}
|
|
312
312
|
.login-link.filled.neutral {
|
|
313
313
|
background: var(--login-btn-bg-color, var(--hot-color-neutral-600));
|
|
@@ -330,8 +330,8 @@ export const styles = css`
|
|
|
330
330
|
border: 1px solid;
|
|
331
331
|
}
|
|
332
332
|
.login-link.outline.primary {
|
|
333
|
-
border-color: var(--login-btn-bg-color, var(--hot-color-primary-
|
|
334
|
-
color: var(--login-btn-text-color, var(--hot-color-primary-
|
|
333
|
+
border-color: var(--login-btn-bg-color, var(--hot-color-primary-950));
|
|
334
|
+
color: var(--login-btn-text-color, var(--hot-color-primary-950));
|
|
335
335
|
}
|
|
336
336
|
.login-link.outline.primary:hover {
|
|
337
337
|
background: var(--login-btn-hover-bg-color, var(--hot-color-primary-50));
|
|
@@ -357,7 +357,7 @@ export const styles = css`
|
|
|
357
357
|
border: none;
|
|
358
358
|
}
|
|
359
359
|
.login-link.plain.primary {
|
|
360
|
-
color: var(--login-btn-text-color, var(--hot-color-primary-
|
|
360
|
+
color: var(--login-btn-text-color, var(--hot-color-primary-950));
|
|
361
361
|
}
|
|
362
362
|
.login-link.plain.primary:hover {
|
|
363
363
|
background: var(--login-btn-hover-bg-color, var(--hot-color-primary-50));
|
package/src/hanko-auth.ts
CHANGED
|
@@ -195,6 +195,8 @@ export class HankoAuth extends LitElement {
|
|
|
195
195
|
private _lastSessionId: string | null = null;
|
|
196
196
|
private _hanko: any = null;
|
|
197
197
|
private _isPrimary = false; // Is this the primary instance?
|
|
198
|
+
private _sessionCheckFailures = 0;
|
|
199
|
+
private _sessionCheckBackoffTimer: ReturnType<typeof setTimeout> | null = null;
|
|
198
200
|
private _hankoObserver: MutationObserver | null = null;
|
|
199
201
|
|
|
200
202
|
// Hanko signup headline text across all supported languages (used for subtitle injection)
|
|
@@ -368,6 +370,8 @@ export class HankoAuth extends LitElement {
|
|
|
368
370
|
private _handleVisibilityChange = () => {
|
|
369
371
|
// Only primary instance should handle visibility changes to prevent race conditions
|
|
370
372
|
if (!this._isPrimary) return;
|
|
373
|
+
// Don't re-check if a backoff retry is already scheduled
|
|
374
|
+
if (this._sessionCheckBackoffTimer) return;
|
|
371
375
|
|
|
372
376
|
if (!document.hidden && !this.showProfile && !this.user) {
|
|
373
377
|
// Page became visible, we're in header mode, and no user is logged in
|
|
@@ -380,6 +384,8 @@ export class HankoAuth extends LitElement {
|
|
|
380
384
|
private _handleWindowFocus = () => {
|
|
381
385
|
// Only primary instance should handle window focus to prevent race conditions
|
|
382
386
|
if (!this._isPrimary) return;
|
|
387
|
+
// Don't re-check if a backoff retry is already scheduled
|
|
388
|
+
if (this._sessionCheckBackoffTimer) return;
|
|
383
389
|
|
|
384
390
|
if (!this.showProfile && !this.user) {
|
|
385
391
|
// Window focused, we're in header mode, and no user is logged in
|
|
@@ -549,6 +555,16 @@ export class HankoAuth extends LitElement {
|
|
|
549
555
|
}
|
|
550
556
|
}
|
|
551
557
|
|
|
558
|
+
private _scheduleSessionRetry() {
|
|
559
|
+
if (this._sessionCheckBackoffTimer) return; // already scheduled
|
|
560
|
+
const delay = Math.min(1000 * 2 ** this._sessionCheckFailures, 60000);
|
|
561
|
+
this.log(`Session check failed, retrying in ${delay / 1000}s (attempt ${this._sessionCheckFailures})`);
|
|
562
|
+
this._sessionCheckBackoffTimer = setTimeout(() => {
|
|
563
|
+
this._sessionCheckBackoffTimer = null;
|
|
564
|
+
this.checkSession();
|
|
565
|
+
}, delay);
|
|
566
|
+
}
|
|
567
|
+
|
|
552
568
|
private async checkSession() {
|
|
553
569
|
this.log("Checking for existing Hanko session...");
|
|
554
570
|
|
|
@@ -592,6 +608,7 @@ export class HankoAuth extends LitElement {
|
|
|
592
608
|
return;
|
|
593
609
|
}
|
|
594
610
|
|
|
611
|
+
this._sessionCheckFailures = 0; // reset backoff on success
|
|
595
612
|
this.log("Valid Hanko session found via cookie");
|
|
596
613
|
this.log("Session data:", sessionData);
|
|
597
614
|
|
|
@@ -710,12 +727,16 @@ export class HankoAuth extends LitElement {
|
|
|
710
727
|
this.log("No valid session cookie found - user needs to login");
|
|
711
728
|
}
|
|
712
729
|
} catch (validateError) {
|
|
730
|
+
this._sessionCheckFailures++;
|
|
713
731
|
this.log("Session validation failed:", validateError);
|
|
714
|
-
this.
|
|
732
|
+
this._scheduleSessionRetry();
|
|
733
|
+
return;
|
|
715
734
|
}
|
|
716
735
|
} catch (error) {
|
|
736
|
+
this._sessionCheckFailures++;
|
|
717
737
|
this.log("Session check error:", error);
|
|
718
|
-
this.
|
|
738
|
+
this._scheduleSessionRetry();
|
|
739
|
+
return;
|
|
719
740
|
} finally {
|
|
720
741
|
// Broadcast state changes to other instances
|
|
721
742
|
if (this._isPrimary) {
|
|
@@ -921,33 +942,36 @@ export class HankoAuth extends LitElement {
|
|
|
921
942
|
|
|
922
943
|
updated(changedProperties: Map<string, any>) {
|
|
923
944
|
super.updated(changedProperties);
|
|
924
|
-
// Re-attach event listeners when user becomes null (after logout)
|
|
925
|
-
// because a new <hanko-auth> element is created
|
|
926
945
|
if (
|
|
927
|
-
changedProperties.has("user") &&
|
|
928
|
-
|
|
929
|
-
this.showProfile
|
|
946
|
+
(changedProperties.has("user") && this.user === null && this.showProfile) ||
|
|
947
|
+
changedProperties.has("lang")
|
|
930
948
|
) {
|
|
931
|
-
this.log("User logged out, re-attaching event listeners...");
|
|
932
949
|
this._currentHankoAuthElement = null;
|
|
933
950
|
this.setupEventListeners();
|
|
934
951
|
}
|
|
935
952
|
}
|
|
936
953
|
|
|
937
954
|
private setupEventListeners() {
|
|
938
|
-
// Use updateComplete to ensure DOM is ready
|
|
939
955
|
this.updateComplete.then(() => {
|
|
940
956
|
const hankoAuth = this.shadowRoot?.querySelector("hanko-auth");
|
|
941
957
|
|
|
942
|
-
// Skip if already attached to the same element
|
|
943
958
|
if (hankoAuth && hankoAuth === this._currentHankoAuthElement) {
|
|
944
|
-
this.log("Event listeners already attached to this element");
|
|
945
959
|
return;
|
|
946
960
|
}
|
|
947
|
-
|
|
961
|
+
// no exports available for adding css, so injecting it
|
|
948
962
|
if (hankoAuth) {
|
|
949
963
|
this._currentHankoAuthElement = hankoAuth;
|
|
950
|
-
|
|
964
|
+
const hankoShadow = (hankoAuth as HTMLElement & { shadowRoot: ShadowRoot | null }).shadowRoot;
|
|
965
|
+
if (hankoShadow && !hankoShadow.querySelector("#hot-hanko-overrides")) {
|
|
966
|
+
const style = document.createElement("style");
|
|
967
|
+
style.id = "hot-hanko-overrides";
|
|
968
|
+
style.textContent = `
|
|
969
|
+
.hanko_lastUsed { margin-left: 8px; }
|
|
970
|
+
.hanko_form .hanko_li { min-width: 100%; }
|
|
971
|
+
`;
|
|
972
|
+
hankoShadow.appendChild(style);
|
|
973
|
+
}
|
|
974
|
+
|
|
951
975
|
this._setupSignUpSubtitleObserver(hankoAuth);
|
|
952
976
|
|
|
953
977
|
hankoAuth.addEventListener("onSessionCreated", (e: any) => {
|
|
@@ -969,7 +993,7 @@ export class HankoAuth extends LitElement {
|
|
|
969
993
|
}
|
|
970
994
|
});
|
|
971
995
|
}
|
|
972
|
-
|
|
996
|
+
// subtitle handling
|
|
973
997
|
private _setupSignUpSubtitleObserver(hankoAuth: Element) {
|
|
974
998
|
const injectSubtitle = () => {
|
|
975
999
|
const hankoShadow = (hankoAuth as HTMLElement & { shadowRoot: ShadowRoot | null }).shadowRoot;
|
|
@@ -978,13 +1002,11 @@ export class HankoAuth extends LitElement {
|
|
|
978
1002
|
const h1 = hankoShadow.querySelector<HTMLElement>("h1[part='headline1']");
|
|
979
1003
|
const headlineText = h1?.textContent?.trim() ?? "";
|
|
980
1004
|
|
|
981
|
-
// Remove any existing subtitle first
|
|
982
1005
|
const existing = hankoShadow.querySelector(".hot-subtitle");
|
|
983
1006
|
|
|
984
1007
|
const isSignUp = this._signUpHeadlines.has(headlineText);
|
|
985
1008
|
const isLogin = this._loginHeadlines.has(headlineText);
|
|
986
1009
|
|
|
987
|
-
// Only show subtitle on the two initial screens
|
|
988
1010
|
if (!isSignUp && !isLogin) {
|
|
989
1011
|
if (existing) {
|
|
990
1012
|
this._hankoObserver?.disconnect();
|
|
@@ -998,11 +1020,9 @@ export class HankoAuth extends LitElement {
|
|
|
998
1020
|
? this.t("signUpSubtitle")
|
|
999
1021
|
: this.t("loginSubtitle");
|
|
1000
1022
|
|
|
1001
|
-
// Skip if subtitle already has correct text
|
|
1002
1023
|
if (existing && existing.textContent === subtitleText) return;
|
|
1003
1024
|
if (!h1) return;
|
|
1004
1025
|
|
|
1005
|
-
// Disconnect before modifying DOM to avoid re-triggering the observer
|
|
1006
1026
|
this._hankoObserver?.disconnect();
|
|
1007
1027
|
|
|
1008
1028
|
if (existing) existing.remove();
|
|
@@ -1011,11 +1031,10 @@ export class HankoAuth extends LitElement {
|
|
|
1011
1031
|
subtitle.className = "hot-subtitle";
|
|
1012
1032
|
subtitle.textContent = subtitleText;
|
|
1013
1033
|
subtitle.style.cssText =
|
|
1014
|
-
"margin: -4px 0 16px; text-align: center; font-size: var(--hot-font-size-
|
|
1034
|
+
"margin: -4px 0 16px; text-align: center; font-size: var(--hot-font-size-large); color: var(--hot-color-gray-600, #6b7280); font-weight: normal;";
|
|
1015
1035
|
|
|
1016
1036
|
h1.insertAdjacentElement("afterend", subtitle);
|
|
1017
1037
|
|
|
1018
|
-
// Re-observe after injection
|
|
1019
1038
|
this._hankoObserver?.observe(hankoShadow, { childList: true, subtree: true });
|
|
1020
1039
|
};
|
|
1021
1040
|
|
package/src/hanko-i18n-en.ts
CHANGED
|
@@ -9,9 +9,9 @@ export const enOverrides = {
|
|
|
9
9
|
signUp: "Create an account",
|
|
10
10
|
},
|
|
11
11
|
labels: {
|
|
12
|
-
signUp: "
|
|
12
|
+
signUp: "Sign up here",
|
|
13
13
|
alreadyHaveAnAccount: "Already have a HOT account?",
|
|
14
|
-
|
|
14
|
+
dontHaveAnAccount: "Don't have a HOT account?",
|
|
15
15
|
},
|
|
16
16
|
texts: {
|
|
17
17
|
enterPasscode:
|
package/src/hanko-i18n-es.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const es = {
|
|
|
120
120
|
signInPasskey: "Iniciar sesión con llave de acceso",
|
|
121
121
|
registerAuthenticator: "Crear una llave de acceso",
|
|
122
122
|
signIn: "Iniciar sesión",
|
|
123
|
-
signUp: "
|
|
123
|
+
signUp: "Regístrese aquí",
|
|
124
124
|
sendNewPasscode: "Enviar nuevo código",
|
|
125
125
|
passwordRetryAfter: "Reintentar en {passwordRetryAfter}",
|
|
126
126
|
passcodeResendAfter: "Solicitar nuevo código en {passcodeResendAfter}",
|
|
@@ -142,7 +142,7 @@ export const es = {
|
|
|
142
142
|
emailOrUsername: "Correo o nombre de usuario",
|
|
143
143
|
username: "Nombre de usuario",
|
|
144
144
|
optional: "opcional",
|
|
145
|
-
dontHaveAnAccount: "¿No tiene una cuenta?",
|
|
145
|
+
dontHaveAnAccount: "¿No tiene una cuenta HOT?",
|
|
146
146
|
alreadyHaveAnAccount: "¿Ya tiene una cuenta?",
|
|
147
147
|
changeUsername: "Cambiar nombre de usuario",
|
|
148
148
|
setUsername: "Establecer nombre de usuario",
|
package/src/hanko-i18n-fr.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const fr = {
|
|
|
120
120
|
signInPasskey: "Se connecter avec clé d'accès",
|
|
121
121
|
registerAuthenticator: "Créer une clé d'accès",
|
|
122
122
|
signIn: "Se connecter",
|
|
123
|
-
signUp: "
|
|
123
|
+
signUp: "S'inscrire ici",
|
|
124
124
|
sendNewPasscode: "Envoyer un nouveau code",
|
|
125
125
|
passwordRetryAfter: "Réessayer dans {passwordRetryAfter}",
|
|
126
126
|
passcodeResendAfter: "Demander un nouveau code dans {passcodeResendAfter}",
|
|
@@ -142,7 +142,7 @@ export const fr = {
|
|
|
142
142
|
emailOrUsername: "E-mail ou nom d'utilisateur",
|
|
143
143
|
username: "Nom d'utilisateur",
|
|
144
144
|
optional: "optionnel",
|
|
145
|
-
dontHaveAnAccount: "Vous n'avez pas de compte ?",
|
|
145
|
+
dontHaveAnAccount: "Vous n'avez pas de compte HOT ?",
|
|
146
146
|
alreadyHaveAnAccount: "Vous avez déjà un compte ?",
|
|
147
147
|
changeUsername: "Changer le nom d'utilisateur",
|
|
148
148
|
setUsername: "Définir le nom d'utilisateur",
|
package/src/hanko-i18n-pt.ts
CHANGED
|
@@ -119,7 +119,7 @@ export const pt = {
|
|
|
119
119
|
signInPasskey: "Entrar com chave de acesso",
|
|
120
120
|
registerAuthenticator: "Criar uma chave de acesso",
|
|
121
121
|
signIn: "Entrar",
|
|
122
|
-
signUp: "
|
|
122
|
+
signUp: "Cadastre-se aqui",
|
|
123
123
|
sendNewPasscode: "Enviar novo código",
|
|
124
124
|
passwordRetryAfter: "Tentar novamente em {passwordRetryAfter}",
|
|
125
125
|
passcodeResendAfter: "Solicitar novo código em {passcodeResendAfter}",
|
|
@@ -141,7 +141,7 @@ export const pt = {
|
|
|
141
141
|
emailOrUsername: "E-mail ou nome de usuário",
|
|
142
142
|
username: "Nome de usuário",
|
|
143
143
|
optional: "opcional",
|
|
144
|
-
dontHaveAnAccount: "Não tem uma conta?",
|
|
144
|
+
dontHaveAnAccount: "Não tem uma conta HOT?",
|
|
145
145
|
alreadyHaveAnAccount: "Já tem uma conta?",
|
|
146
146
|
changeUsername: "Alterar nome de usuário",
|
|
147
147
|
setUsername: "Definir nome de usuário",
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Hanko's UI overwrites
|
|
3
2
|
import { en } from "@teamhanko/hanko-elements/i18n/en";
|
|
3
|
+
import { fr as hankoFr } from "@teamhanko/hanko-elements/i18n/fr";
|
|
4
4
|
import { enOverrides } from "./hanko-i18n-en";
|
|
5
|
-
import { es } from "./hanko-i18n-es";
|
|
6
|
-
import { fr } from "./hanko-i18n-fr";
|
|
7
|
-
import { pt } from "./hanko-i18n-pt";
|
|
5
|
+
import { es as esOverrides } from "./hanko-i18n-es";
|
|
6
|
+
import { fr as frOverrides } from "./hanko-i18n-fr";
|
|
7
|
+
import { pt as ptOverrides } from "./hanko-i18n-pt";
|
|
8
8
|
|
|
9
9
|
Object.assign(en.headlines, enOverrides.headlines);
|
|
10
10
|
Object.assign(en.labels, enOverrides.labels);
|
|
11
11
|
Object.assign(en.texts, enOverrides.texts);
|
|
12
12
|
|
|
13
|
+
const fr = JSON.parse(JSON.stringify(hankoFr));
|
|
14
|
+
Object.assign(fr.headlines, frOverrides.headlines);
|
|
15
|
+
Object.assign(fr.labels, frOverrides.labels);
|
|
16
|
+
Object.assign(fr.texts, frOverrides.texts);
|
|
17
|
+
|
|
18
|
+
const es = JSON.parse(JSON.stringify(en));
|
|
19
|
+
Object.assign(es.headlines, esOverrides.headlines);
|
|
20
|
+
Object.assign(es.labels, esOverrides.labels);
|
|
21
|
+
Object.assign(es.texts, esOverrides.texts);
|
|
22
|
+
|
|
23
|
+
const pt = JSON.parse(JSON.stringify(en));
|
|
24
|
+
Object.assign(pt.headlines, ptOverrides.headlines);
|
|
25
|
+
Object.assign(pt.labels, ptOverrides.labels);
|
|
26
|
+
Object.assign(pt.texts, ptOverrides.texts);
|
|
27
|
+
|
|
13
28
|
export function getTranslations() {
|
|
14
29
|
return { en, es, fr, pt };
|
|
15
30
|
}
|