@ibgib/web-gib 0.0.59 → 0.0.60

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.
Files changed (53) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -1
  4. package/dist/identity/custodian-delegate-helper.mjs +20 -3
  5. package/dist/identity/custodian-delegate-helper.mjs.map +1 -1
  6. package/dist/identity/identity-constants.mjs +2 -2
  7. package/dist/identity/identity-constants.mjs.map +1 -1
  8. package/dist/identity/identity-types.d.mts +4 -4
  9. package/dist/identity/identity-types.d.mts.map +1 -1
  10. package/dist/ui/component/identity/add-device/add-device.css +93 -17
  11. package/dist/ui/component/identity/add-device/add-device.d.mts +24 -7
  12. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  13. package/dist/ui/component/identity/add-device/add-device.html +97 -23
  14. package/dist/ui/component/identity/add-device/add-device.mjs +490 -186
  15. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  16. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  17. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +7 -4
  18. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  19. package/dist/ui/component/identity/keystone-details/keystone-details.css +1 -1
  20. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +8 -8
  21. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  22. package/dist/ui/component/identity/keystone-details/keystone-details.html +29 -20
  23. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +51 -51
  24. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  25. package/dist/ui/component/identity/remove-device/remove-device.d.mts +1 -1
  26. package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
  27. package/dist/ui/component/identity/remove-device/remove-device.html +3 -3
  28. package/dist/ui/component/identity/remove-device/remove-device.mjs +7 -7
  29. package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
  30. package/dist/util/device-helper.d.mts +30 -0
  31. package/dist/util/device-helper.d.mts.map +1 -0
  32. package/dist/util/device-helper.mjs +90 -0
  33. package/dist/util/device-helper.mjs.map +1 -0
  34. package/dist/util/device-helper.respec.d.mts +2 -0
  35. package/dist/util/device-helper.respec.d.mts.map +1 -0
  36. package/dist/util/device-helper.respec.mjs +83 -0
  37. package/dist/util/device-helper.respec.mjs.map +1 -0
  38. package/package.json +3 -3
  39. package/src/AUTO-GENERATED-version.mts +1 -1
  40. package/src/identity/custodian-delegate-helper.mts +28 -3
  41. package/src/identity/identity-constants.mts +2 -2
  42. package/src/identity/identity-types.mts +4 -4
  43. package/src/ui/component/identity/add-device/add-device.css +93 -17
  44. package/src/ui/component/identity/add-device/add-device.html +97 -23
  45. package/src/ui/component/identity/add-device/add-device.mts +577 -194
  46. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +7 -4
  47. package/src/ui/component/identity/keystone-details/keystone-details.css +1 -1
  48. package/src/ui/component/identity/keystone-details/keystone-details.html +29 -20
  49. package/src/ui/component/identity/keystone-details/keystone-details.mts +57 -57
  50. package/src/ui/component/identity/remove-device/remove-device.html +3 -3
  51. package/src/ui/component/identity/remove-device/remove-device.mts +8 -8
  52. package/src/util/device-helper.mts +104 -0
  53. package/src/util/device-helper.respec.mts +90 -0
@@ -10,7 +10,7 @@ import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mj
10
10
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
11
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
12
12
  import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
13
- import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
13
+ import { toDto, getTjpAddr, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
14
  import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
15
15
  import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
16
16
  import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
@@ -921,9 +921,12 @@ export class KeystoneCreatorComponentInstance
921
921
  // 0. Persist all frames of primary identity graph into local browser space
922
922
  if (primaryGraph) {
923
923
  const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
924
- for (const frame of graphIbGibs) {
925
- await metaspace.put({ ibGibs: [frame], space });
926
- await metaspace.registerNewIbGib({ ibGib: frame, space });
924
+ await metaspace.put({ ibGibs: graphIbGibs, space });
925
+ const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
926
+ for (const tjpKey of Object.keys(timelinesMap)) {
927
+ for (const frame of timelinesMap[tjpKey]) {
928
+ await metaspace.registerNewIbGib({ ibGib: frame, space });
929
+ }
927
930
  }
928
931
  } else if (primaryKeystone) {
929
932
  await metaspace.put({ ibGibs: [primaryKeystone], space });
@@ -578,7 +578,7 @@
578
578
  opacity: 0.4;
579
579
  }
580
580
 
581
- /* Form Layout for Passphrase Rotation */
581
+ /* Form Layout for Password Rotation */
582
582
  .form-row {
583
583
  display: flex;
584
584
  gap: 1rem;
@@ -60,7 +60,8 @@
60
60
  <div id="delegates-card" class="card">
61
61
  <h3>Registered Delegates</h3>
62
62
  <div id="delegates-container" class="delegates-container">
63
- <p class="description">Active sub-keystones authorized to perform sync and operations on behalf of this Domain Identity.</p>
63
+ <p class="description">Active sub-keystones authorized to perform operations on behalf of this Domain
64
+ Identity.</p>
64
65
  <table class="delegates-table">
65
66
  <thead>
66
67
  <tr>
@@ -80,7 +81,8 @@
80
81
  <!-- SSO Identity Linking Card -->
81
82
  <div id="sso-link-card" class="card hidden">
82
83
  <h3>Identity Providers</h3>
83
- <p class="description">Securely link or unlink external OAuth2 providers to delegate sync access to this identity.</p>
84
+ <p class="description">Securely link or unlink external OAuth2 providers to delegate sync access to this
85
+ identity.</p>
84
86
  <div class="sso-toggle-group">
85
87
  <!-- Google Card Toggle -->
86
88
  <div id="btn-link-google" class="sso-toggle-card google" role="button" tabindex="0">
@@ -88,10 +90,14 @@
88
90
  <div class="toggle-content">
89
91
  <!-- Custom Google SVG Icon -->
90
92
  <svg class="sso-icon" viewBox="0 0 24 24">
91
- <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
92
- <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
93
- <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z"/>
94
- <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z"/>
93
+ <path fill="#4285F4"
94
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
95
+ <path fill="#34A853"
96
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
97
+ <path fill="#FBBC05"
98
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z" />
99
+ <path fill="#EA4335"
100
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z" />
95
101
  </svg>
96
102
  <span class="provider-label">Google</span>
97
103
  <span class="toggle-state-text">Unlinked</span>
@@ -105,7 +111,8 @@
105
111
  <div class="toggle-content">
106
112
  <!-- Custom GitHub SVG Icon -->
107
113
  <svg class="sso-icon" viewBox="0 0 24 24" fill="currentColor">
108
- <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
114
+ <path
115
+ d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
109
116
  </svg>
110
117
  <span class="provider-label">GitHub</span>
111
118
  <span class="toggle-state-text">Unlinked</span>
@@ -116,28 +123,30 @@
116
123
  <div id="sso-status-msg" class="status-msg hidden"></div>
117
124
  </div>
118
125
 
119
- <!-- Change Passphrase Card -->
120
- <div id="change-passphrase-card" class="card">
121
- <h3>Change Master Passphrase</h3>
122
- <p class="description">Rotate your local master passphrase. This will cryptographically evolve your primary identity and all registered user-owned delegate keystones to the new passphrase.</p>
126
+ <!-- Change Password Card -->
127
+ <div id="change-password-card" class="card">
128
+ <h3>Change Password</h3>
129
+ <p class="description">Rotate your local password that drives this identity. This will cryptographically
130
+ evolve your primary
131
+ identity and all registered user-owned delegate keystones to the new password.</p>
123
132
  <div class="form-row">
124
133
  <div class="form-group">
125
- <label for="input-current-passphrase">Current Passphrase</label>
126
- <input type="password" id="input-current-passphrase" placeholder="••••••••" class="form-input">
134
+ <label for="input-current-password">Current Password</label>
135
+ <input type="password" id="input-current-password" placeholder="••••••••" class="form-input">
127
136
  </div>
128
137
  <div class="form-group">
129
- <label for="input-new-passphrase">New Passphrase</label>
130
- <input type="password" id="input-new-passphrase" placeholder="••••••••" class="form-input">
138
+ <label for="input-new-password">New Password</label>
139
+ <input type="password" id="input-new-password" placeholder="••••••••" class="form-input">
131
140
  </div>
132
141
  <div class="form-group">
133
- <label for="input-confirm-passphrase">Confirm New Passphrase</label>
134
- <input type="password" id="input-confirm-passphrase" placeholder="••••••••" class="form-input">
142
+ <label for="input-confirm-password">Confirm New Password</label>
143
+ <input type="password" id="input-confirm-password" placeholder="••••••••" class="form-input">
135
144
  </div>
136
145
  </div>
137
146
  <div class="action-row">
138
- <button id="btn-change-passphrase" class="action-btn">Update Passphrase</button>
147
+ <button id="btn-change-password" class="action-btn">Update Password</button>
139
148
  </div>
140
- <div id="change-passphrase-status" class="status-msg hidden"></div>
149
+ <div id="change-password-status" class="status-msg hidden"></div>
141
150
  </div>
142
151
 
143
152
  <!-- Raw Keystone Timeline Explorer -->
@@ -151,4 +160,4 @@
151
160
  </div>
152
161
  </div>
153
162
  </div>
154
- </div>
163
+ </div>
@@ -94,12 +94,12 @@ interface KeystoneDetailsElements extends ElementsBase {
94
94
  btnLinkGoogleEl: HTMLDivElement;
95
95
  btnLinkGithubEl: HTMLDivElement;
96
96
  ssoStatusMsgEl: HTMLDivElement;
97
- inputCurrentPassphraseEl: HTMLInputElement;
98
- inputNewPassphraseEl: HTMLInputElement;
99
- inputConfirmPassphraseEl: HTMLInputElement;
100
- btnChangePassphraseEl: HTMLButtonElement;
101
- changePassphraseStatusEl: HTMLDivElement;
102
- changePassphraseCardEl: HTMLDivElement;
97
+ inputCurrentPasswordEl: HTMLInputElement;
98
+ inputNewPasswordEl: HTMLInputElement;
99
+ inputConfirmPasswordEl: HTMLInputElement;
100
+ btnChangePasswordEl: HTMLButtonElement;
101
+ changePasswordStatusEl: HTMLDivElement;
102
+ changePasswordCardEl: HTMLDivElement;
103
103
  }
104
104
 
105
105
  export class KeystoneDetailsComponentInstance
@@ -212,12 +212,12 @@ export class KeystoneDetailsComponentInstance
212
212
  this.elements.btnLinkGoogleEl = shadow.getElementById('btn-link-google') as HTMLDivElement;
213
213
  this.elements.btnLinkGithubEl = shadow.getElementById('btn-link-github') as HTMLDivElement;
214
214
  this.elements.ssoStatusMsgEl = shadow.getElementById('sso-status-msg') as HTMLDivElement;
215
- this.elements.inputCurrentPassphraseEl = shadow.getElementById('input-current-passphrase') as HTMLInputElement;
216
- this.elements.inputNewPassphraseEl = shadow.getElementById('input-new-passphrase') as HTMLInputElement;
217
- this.elements.inputConfirmPassphraseEl = shadow.getElementById('input-confirm-passphrase') as HTMLInputElement;
218
- this.elements.btnChangePassphraseEl = shadow.getElementById('btn-change-passphrase') as HTMLButtonElement;
219
- this.elements.changePassphraseStatusEl = shadow.getElementById('change-passphrase-status') as HTMLDivElement;
220
- this.elements.changePassphraseCardEl = shadow.getElementById('change-passphrase-card') as HTMLDivElement;
215
+ this.elements.inputCurrentPasswordEl = shadow.getElementById('input-current-password') as HTMLInputElement;
216
+ this.elements.inputNewPasswordEl = shadow.getElementById('input-new-password') as HTMLInputElement;
217
+ this.elements.inputConfirmPasswordEl = shadow.getElementById('input-confirm-password') as HTMLInputElement;
218
+ this.elements.btnChangePasswordEl = shadow.getElementById('btn-change-password') as HTMLButtonElement;
219
+ this.elements.changePasswordStatusEl = shadow.getElementById('change-password-status') as HTMLDivElement;
220
+ this.elements.changePasswordCardEl = shadow.getElementById('change-password-card') as HTMLDivElement;
221
221
 
222
222
  // ElementBase requires contentEl
223
223
  this.elements.contentEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
@@ -260,7 +260,7 @@ export class KeystoneDetailsComponentInstance
260
260
  this.elements!.btnUpdateIdentityEl.addEventListener('click', () => this.handleUpdateIdentity());
261
261
  this.elements!.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOClick('google'));
262
262
  this.elements!.btnLinkGithubEl.addEventListener('click', () => this.handleSSOClick('github'));
263
- this.elements!.btnChangePassphraseEl.addEventListener('click', () => this.handleChangePassphrase());
263
+ this.elements!.btnChangePasswordEl.addEventListener('click', () => this.handleChangePassword());
264
264
  }
265
265
 
266
266
  private toggleAccordion() {
@@ -591,17 +591,17 @@ export class KeystoneDetailsComponentInstance
591
591
  // Render Challenge Pools
592
592
  this.renderChallengePools(keystone.data?.challengePools || []);
593
593
 
594
- // Render Delegates, SSO link, and Passphrase Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
594
+ // Render Delegates, SSO link, and Password Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
595
595
  const details = await this.getKeystoneDetails(this.ibGibAddr);
596
596
  if (details.isPrimary) {
597
597
  this.elements.delegatesCardEl.classList.remove('hidden');
598
598
  await this.renderDelegates(keystone.data?.delegates);
599
599
  this.elements.ssoLinkCardEl.classList.remove('hidden');
600
- this.elements.changePassphraseCardEl.classList.remove('hidden');
600
+ this.elements.changePasswordCardEl.classList.remove('hidden');
601
601
  } else {
602
602
  this.elements.delegatesCardEl.classList.add('hidden');
603
603
  this.elements.ssoLinkCardEl.classList.add('hidden');
604
- this.elements.changePassphraseCardEl.classList.add('hidden');
604
+ this.elements.changePasswordCardEl.classList.add('hidden');
605
605
  }
606
606
 
607
607
  this.updateActiveButtonState();
@@ -924,7 +924,7 @@ export class KeystoneDetailsComponentInstance
924
924
  let masterSecret: string;
925
925
  try {
926
926
  masterSecret = await promptForSecret({
927
- msg: `Enter Master Passphrase to revoke delegate '${deviceName}':`,
927
+ msg: `Enter Master Password to revoke delegate '${deviceName}':`,
928
928
  confirm: false
929
929
  });
930
930
  } catch (err) {
@@ -1103,8 +1103,8 @@ export class KeystoneDetailsComponentInstance
1103
1103
  while (tries < maxTries && !isCorrect) {
1104
1104
  try {
1105
1105
  const promptMsg = tries === 0
1106
- ? "Link successful! Enter your local Master Secret (Passphrase) to sign and register the custodian pool"
1107
- : `Incorrect passphrase. Please try again (${maxTries - tries} tries remaining):`;
1106
+ ? "Link successful! Enter your local Master Secret (Password) to sign and register the custodian pool"
1107
+ : `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
1108
1108
  masterSecret = await promptForSecret({
1109
1109
  msg: promptMsg,
1110
1110
  confirm: false
@@ -1133,7 +1133,7 @@ export class KeystoneDetailsComponentInstance
1133
1133
  }
1134
1134
 
1135
1135
  if (!isCorrect) {
1136
- this.setSSOStatus("Link cancelled or failed (incorrect passphrase).", "error");
1136
+ this.setSSOStatus("Link cancelled or failed (incorrect password).", "error");
1137
1137
  return;
1138
1138
  }
1139
1139
 
@@ -1200,7 +1200,7 @@ export class KeystoneDetailsComponentInstance
1200
1200
  this.setSSOStatus(`Unlinking ${providerId}...`, "info");
1201
1201
 
1202
1202
  try {
1203
- // 1. Prompt user for passphrase
1203
+ // 1. Prompt user for password
1204
1204
  const keystoneService = new KeystoneService_V1();
1205
1205
  let masterSecret: string | undefined;
1206
1206
  let isCorrect = false;
@@ -1210,8 +1210,8 @@ export class KeystoneDetailsComponentInstance
1210
1210
  while (tries < maxTries && !isCorrect) {
1211
1211
  try {
1212
1212
  const promptMsg = tries === 0
1213
- ? `Are you sure you want to unlink your ${providerId} account? If so, enter your local Master Secret (Passphrase) to authorize. Otherwise, click Cancel.`
1214
- : `Incorrect passphrase. Please try again (${maxTries - tries} tries remaining):`;
1213
+ ? `Are you sure you want to unlink your ${providerId} account? If so, enter your local Master Secret (Password) to authorize. Otherwise, click Cancel.`
1214
+ : `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
1215
1215
  masterSecret = await promptForSecret({
1216
1216
  msg: promptMsg,
1217
1217
  confirm: false
@@ -1238,7 +1238,7 @@ export class KeystoneDetailsComponentInstance
1238
1238
  }
1239
1239
 
1240
1240
  if (!isCorrect) {
1241
- this.setSSOStatus("Unlink cancelled or failed (incorrect passphrase).", "error");
1241
+ this.setSSOStatus("Unlink cancelled or failed (incorrect password).", "error");
1242
1242
  return;
1243
1243
  }
1244
1244
 
@@ -1292,49 +1292,49 @@ export class KeystoneDetailsComponentInstance
1292
1292
  }
1293
1293
  }
1294
1294
 
1295
- private setChangePassphraseStatus(msg: string, type: 'info' | 'success' | 'error') {
1296
- const el = this.elements!.changePassphraseStatusEl;
1295
+ private setChangePasswordStatus(msg: string, type: 'info' | 'success' | 'error') {
1296
+ const el = this.elements!.changePasswordStatusEl;
1297
1297
  el.textContent = msg;
1298
1298
  el.className = `status-msg ${type}`;
1299
1299
  el.classList.remove('hidden');
1300
1300
  }
1301
1301
 
1302
- protected async handleChangePassphrase(): Promise<void> {
1303
- const lc = `${this.lc}[${this.handleChangePassphrase.name}]`;
1304
- this.setChangePassphraseStatus("Validating entries...", "info");
1302
+ protected async handleChangePassword(): Promise<void> {
1303
+ const lc = `${this.lc}[${this.handleChangePassword.name}]`;
1304
+ this.setChangePasswordStatus("Validating entries...", "info");
1305
1305
 
1306
1306
  try {
1307
- const currentPassphrase = this.elements!.inputCurrentPassphraseEl.value;
1308
- const newPassphrase = this.elements!.inputNewPassphraseEl.value;
1309
- const confirmPassphrase = this.elements!.inputConfirmPassphraseEl.value;
1307
+ const currentPassword = this.elements!.inputCurrentPasswordEl.value;
1308
+ const newPassword = this.elements!.inputNewPasswordEl.value;
1309
+ const confirmPassword = this.elements!.inputConfirmPasswordEl.value;
1310
1310
 
1311
- if (!currentPassphrase) {
1312
- this.setChangePassphraseStatus("Current passphrase is required.", "error");
1311
+ if (!currentPassword) {
1312
+ this.setChangePasswordStatus("Current password is required.", "error");
1313
1313
  return;
1314
1314
  }
1315
- if (!newPassphrase) {
1316
- this.setChangePassphraseStatus("New passphrase is required.", "error");
1315
+ if (!newPassword) {
1316
+ this.setChangePasswordStatus("New password is required.", "error");
1317
1317
  return;
1318
1318
  }
1319
- if (newPassphrase !== confirmPassphrase) {
1320
- this.setChangePassphraseStatus("New passphrase and confirmation do not match.", "error");
1319
+ if (newPassword !== confirmPassword) {
1320
+ this.setChangePasswordStatus("New password and confirmation do not match.", "error");
1321
1321
  return;
1322
1322
  }
1323
1323
 
1324
1324
  const keystoneService = new KeystoneService_V1();
1325
1325
 
1326
- // 1. Verify current passphrase against parent manage pool
1326
+ // 1. Verify current password against parent manage pool
1327
1327
  const isMatch = await keystoneService.verifySigningSecret({
1328
1328
  keystoneIbGib: this.ibGib!,
1329
- signingSecret: currentPassphrase,
1329
+ signingSecret: currentPassword,
1330
1330
  poolId: 'manage'
1331
1331
  });
1332
1332
  if (!isMatch) {
1333
- this.setChangePassphraseStatus("Incorrect current passphrase.", "error");
1333
+ this.setChangePasswordStatus("Incorrect current password.", "error");
1334
1334
  return;
1335
1335
  }
1336
1336
 
1337
- this.setChangePassphraseStatus("Evolving primary identity keystone...", "info");
1337
+ this.setChangePasswordStatus("Evolving primary identity keystone...", "info");
1338
1338
 
1339
1339
  const metaspace = await getGlobalMetaspace_waitIfNeeded();
1340
1340
  if (!metaspace) { throw new Error(`(UNEXPECTED) metaspace falsy? (E: 95e3c8374b0f088f339f5c5a8700f826)`); }
@@ -1346,15 +1346,15 @@ export class KeystoneDetailsComponentInstance
1346
1346
  const { newKeystone: evolvedParent, newDelegates: evolvedDelegates } =
1347
1347
  await keystoneService.changePassword({
1348
1348
  keystoneIbGib: this.ibGib!,
1349
- newMasterSecret: newPassphrase,
1350
- signingSecret: currentPassphrase,
1349
+ newMasterSecret: newPassword,
1350
+ signingSecret: currentPassword,
1351
1351
  signingPoolId: 'manage',
1352
1352
  metaspace,
1353
1353
  space
1354
1354
  });
1355
1355
  debugger; // look at evolvedDelegates
1356
1356
 
1357
- this.setChangePassphraseStatus("Resolving and rotating delegate keystones...", "info");
1357
+ this.setChangePasswordStatus("Resolving and rotating delegate keystones...", "info");
1358
1358
 
1359
1359
  // // 3. Evolve registered delegate keystones (preserving custodian/foreign delegate secrets)
1360
1360
  // const delegates = this.ibGib!.data?.delegates || {};
@@ -1368,7 +1368,7 @@ export class KeystoneDetailsComponentInstance
1368
1368
  // space,
1369
1369
  // });
1370
1370
 
1371
- // // Identify user-owned challenge pools in the delegate that match the old passphrase
1371
+ // // Identify user-owned challenge pools in the delegate that match the old password
1372
1372
  // const poolsToRotate: string[] = [];
1373
1373
  // for (const pool of delegateKeystone.data?.challengePools ?? []) {
1374
1374
  // if (!pool.isForeign) {
@@ -1383,7 +1383,7 @@ export class KeystoneDetailsComponentInstance
1383
1383
  // */
1384
1384
  // const isPoolMatch = await keystoneService.verifySigningSecret({
1385
1385
  // keystoneIbGib: delegateKeystone,
1386
- // signingSecret: currentPassphrase,
1386
+ // signingSecret: currentPassword,
1387
1387
  // poolId: pool.id
1388
1388
  // });
1389
1389
  // if (isPoolMatch) { poolsToRotate.push(pool.id); }
@@ -1394,8 +1394,8 @@ export class KeystoneDetailsComponentInstance
1394
1394
  // console.log(`${lc} Rotating pools in delegate ${delegateInfo.delegateTjpAddr}: ${poolsToRotate.join(', ')}`);
1395
1395
  // const {newKeystone: evolvedDelegate} = await keystoneService.changePassword({
1396
1396
  // keystoneIbGib: delegateKeystone,
1397
- // newMasterSecret: newPassphrase,
1398
- // signingSecret: currentPassphrase,
1397
+ // newMasterSecret: newPassword,
1398
+ // signingSecret: currentPassword,
1399
1399
  // signingPoolId: 'manage',
1400
1400
  // poolIds: poolsToRotate,
1401
1401
  // metaspace,
@@ -1406,7 +1406,7 @@ export class KeystoneDetailsComponentInstance
1406
1406
  // }
1407
1407
 
1408
1408
  // 4. Sync evolved parent and all evolved delegate keystones atomically
1409
- this.setChangePassphraseStatus("Syncing evolved keystones to server...", "info");
1409
+ this.setChangePasswordStatus("Syncing evolved keystones to server...", "info");
1410
1410
  const parentTjpAddr = getIbGibAddr({ ibGib: this.ibGib! }); // is this.ibGib the tjp always? or is it the latest in the keystone line? Does it matter?
1411
1411
 
1412
1412
  const syncRes = await getApiBridge().putEvolveKeystone({
@@ -1419,7 +1419,7 @@ export class KeystoneDetailsComponentInstance
1419
1419
  }
1420
1420
 
1421
1421
  // 5. Update index locally
1422
- this.setChangePassphraseStatus("Updating local indexes...", "info");
1422
+ this.setChangePasswordStatus("Updating local indexes...", "info");
1423
1423
  const newKeystoneIbGibs = [toDto({ ibGib: evolvedParent })];
1424
1424
  if (evolvedDelegates) {
1425
1425
  evolvedDelegates.forEach(d => {
@@ -1440,16 +1440,16 @@ export class KeystoneDetailsComponentInstance
1440
1440
  });
1441
1441
 
1442
1442
  // Clear inputs
1443
- this.elements!.inputCurrentPassphraseEl.value = "";
1444
- this.elements!.inputNewPassphraseEl.value = "";
1445
- this.elements!.inputConfirmPassphraseEl.value = "";
1443
+ this.elements!.inputCurrentPasswordEl.value = "";
1444
+ this.elements!.inputNewPasswordEl.value = "";
1445
+ this.elements!.inputConfirmPasswordEl.value = "";
1446
1446
 
1447
- this.setChangePassphraseStatus("Passphrase updated successfully!", "success");
1447
+ this.setChangePasswordStatus("Password updated successfully!", "success");
1448
1448
  await this.loadIbGib({ getLatest: true });
1449
1449
 
1450
1450
  } catch (error) {
1451
- console.error(`${lc} Passphrase rotation failed: ${extractErrorMsg(error)}`);
1452
- this.setChangePassphraseStatus(`Update failed: ${extractErrorMsg(error)}`, "error");
1451
+ console.error(`${lc} Password rotation failed: ${extractErrorMsg(error)}`);
1452
+ this.setChangePasswordStatus(`Update failed: ${extractErrorMsg(error)}`, "error");
1453
1453
  }
1454
1454
  }
1455
1455
  }
@@ -11,8 +11,8 @@
11
11
  </div>
12
12
 
13
13
  <div class="input-group">
14
- <label for="input-passphrase">Master Passphrase</label>
15
- <input type="password" id="input-passphrase" placeholder="Enter your master passphrase..." class="secret-input">
14
+ <label for="input-password">Primary Password</label>
15
+ <input type="password" id="input-password" placeholder="Enter your master password..." class="secret-input">
16
16
  </div>
17
17
 
18
18
  <div class="action-row">
@@ -28,4 +28,4 @@
28
28
  <p class="warning">🔒 The selected device delegate has been unlinked from your primary identity.</p>
29
29
  </div>
30
30
  </div>
31
- </div>
31
+ </div>
@@ -58,7 +58,7 @@ interface RemoveDeviceElements extends ElementsBase {
58
58
  containerEl: HTMLDivElement;
59
59
  titleEl: HTMLHeadingElement;
60
60
  selectDelegateEl: HTMLSelectElement;
61
- inputPassphrase: HTMLInputElement;
61
+ inputPassword: HTMLInputElement;
62
62
  btnRemoveDeviceEl: HTMLButtonElement;
63
63
  statusArea: HTMLDivElement;
64
64
  statusText: HTMLParagraphElement;
@@ -89,7 +89,7 @@ export class RemoveDeviceComponentInstance
89
89
  this.elements.containerEl = this.shadowRoot!.getElementById('container') as HTMLDivElement;
90
90
  this.elements.titleEl = this.shadowRoot!.getElementById('title') as HTMLHeadingElement;
91
91
  this.elements.selectDelegateEl = this.shadowRoot!.getElementById('select-delegate') as HTMLSelectElement;
92
- this.elements.inputPassphrase = this.shadowRoot!.getElementById('input-passphrase') as HTMLInputElement;
92
+ this.elements.inputPassword = this.shadowRoot!.getElementById('input-password') as HTMLInputElement;
93
93
  this.elements.btnRemoveDeviceEl = this.shadowRoot!.getElementById('btn-remove-device') as HTMLButtonElement;
94
94
  this.elements.statusArea = this.shadowRoot!.getElementById('status-area') as HTMLDivElement;
95
95
  this.elements.statusText = this.shadowRoot!.getElementById('status-text') as HTMLParagraphElement;
@@ -108,8 +108,8 @@ export class RemoveDeviceComponentInstance
108
108
 
109
109
  private initHandlers() {
110
110
  this.elements!.btnRemoveDeviceEl.addEventListener('click', () => this.handleRemoveDevice());
111
- this.elements!.inputPassphrase.addEventListener('input', () => {
112
- this.elements!.inputPassphrase.classList.remove('invalid');
111
+ this.elements!.inputPassword.addEventListener('input', () => {
112
+ this.elements!.inputPassword.classList.remove('invalid');
113
113
  });
114
114
  }
115
115
 
@@ -187,11 +187,11 @@ export class RemoveDeviceComponentInstance
187
187
  return;
188
188
  }
189
189
 
190
- const masterSecret = this.elements!.inputPassphrase.value.trim();
191
- this.elements!.inputPassphrase.value = masterSecret;
190
+ const masterSecret = this.elements!.inputPassword.value.trim();
191
+ this.elements!.inputPassword.value = masterSecret;
192
192
  if (!masterSecret) {
193
- this.elements!.inputPassphrase.classList.add('invalid');
194
- this.setStatus("Please enter your master passphrase.", "error");
193
+ this.elements!.inputPassword.classList.add('invalid');
194
+ this.setStatus("Please enter your master password.", "error");
195
195
  return;
196
196
  }
197
197
 
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @module util/device-helper
3
+ *
4
+ * Pure utility functions for generating clean, friendly, auto-generated device names/tags.
5
+ */
6
+
7
+ export interface DeviceTagOptions {
8
+ userAgent?: string;
9
+ userAgentData?: {
10
+ mobile?: boolean;
11
+ platform?: string;
12
+ brands?: Array<{ brand: string; version: string }>;
13
+ };
14
+ maxTouchPoints?: number;
15
+ screenWidth?: number;
16
+ randomSuffix?: string;
17
+ }
18
+
19
+ /**
20
+ * Generates a friendly, URL-safe, kebab-cased default device tag.
21
+ * (e.g., `chrome-win-a1b2`, `mobile-safari-ios-3c4d`, `tablet-android-e5f6`).
22
+ *
23
+ * Pure function: Uses global browser objects (`navigator`, `window`) only as fallbacks
24
+ * when explicit options are omitted, enabling deterministic unit testing.
25
+ *
26
+ * @param opts Optional mock options for deterministic unit testing.
27
+ */
28
+ export function generateDefaultDeviceTag(opts?: DeviceTagOptions): string {
29
+ const ua = (opts?.userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : '') ?? '').toLowerCase();
30
+ const uaData = opts?.userAgentData ?? (typeof navigator !== 'undefined' ? (navigator as any).userAgentData : undefined);
31
+ const touchPoints = opts?.maxTouchPoints ?? (typeof navigator !== 'undefined' ? navigator.maxTouchPoints : 0) ?? 0;
32
+ const width = opts?.screenWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024) ?? 1024;
33
+
34
+ // 1. Detect Form Factor (Mobile, Tablet, Desktop)
35
+ let formFactor = '';
36
+ const isMobileUaData = uaData?.mobile;
37
+ const isMobileUa = /mobile|iphone|ipod|android.*mobile|windows phone/i.test(ua);
38
+ const isTabletUa = /ipad|tablet|(android(?!.*mobile))/i.test(ua) || (touchPoints > 1 && /macintosh/i.test(ua) && width <= 1366);
39
+
40
+ if (isTabletUa) {
41
+ formFactor = 'tablet';
42
+ } else if (isMobileUaData || isMobileUa) {
43
+ formFactor = 'mobile';
44
+ }
45
+
46
+ // 2. Detect OS / Platform
47
+ let platform = '';
48
+ const uaPlatform = (uaData?.platform || '').toLowerCase();
49
+
50
+ if (uaPlatform.includes('win') || ua.includes('windows')) {
51
+ platform = 'win';
52
+ } else if (uaPlatform.includes('ios') || ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) {
53
+ platform = 'ios';
54
+ } else if (uaPlatform.includes('mac') || ua.includes('macintosh') || ua.includes('mac os')) {
55
+ platform = 'mac';
56
+ } else if (uaPlatform.includes('android') || ua.includes('android')) {
57
+ platform = 'android';
58
+ } else if (uaPlatform.includes('linux') || ua.includes('linux')) {
59
+ platform = 'linux';
60
+ }
61
+
62
+ // 3. Detect Browser
63
+ let browser = '';
64
+ if (ua.includes('edg/') || ua.includes('edge/')) {
65
+ browser = 'edge';
66
+ } else if (ua.includes('chrome/') || ua.includes('crios/')) {
67
+ browser = 'chrome';
68
+ } else if (ua.includes('firefox/') || ua.includes('fxios/')) {
69
+ browser = 'firefox';
70
+ } else if (ua.includes('safari/') && !ua.includes('chrome/') && !ua.includes('crios/')) {
71
+ browser = 'safari';
72
+ }
73
+
74
+ // 4. Generate or use provided 4-character random suffix
75
+ let suffix = opts?.randomSuffix;
76
+ if (!suffix) {
77
+ const chars = '0123456789abcdef';
78
+ suffix = '';
79
+ for (let i = 0; i < 4; i++) {
80
+ suffix += chars.charAt(Math.floor(Math.random() * chars.length));
81
+ }
82
+ }
83
+ suffix = suffix.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 8);
84
+
85
+ // 5. Assemble Tokens
86
+ const tokens: string[] = [];
87
+ if (formFactor) {
88
+ tokens.push(formFactor);
89
+ }
90
+ if (browser) {
91
+ tokens.push(browser);
92
+ }
93
+ if (platform) {
94
+ tokens.push(platform);
95
+ }
96
+
97
+ if (tokens.length === 0) {
98
+ tokens.push('device');
99
+ }
100
+
101
+ tokens.push(suffix);
102
+
103
+ return tokens.join('-');
104
+ }