@konemono/nostr-login 1.7.11
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/.prettierrc.json +13 -0
- package/README.md +167 -0
- package/dist/const/index.d.ts +1 -0
- package/dist/iife-module.d.ts +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.esm.js +18 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/modules/AuthNostrService.d.ts +84 -0
- package/dist/modules/BannerManager.d.ts +20 -0
- package/dist/modules/ModalManager.d.ts +25 -0
- package/dist/modules/Nip46.d.ts +56 -0
- package/dist/modules/Nostr.d.ts +34 -0
- package/dist/modules/NostrExtensionService.d.ts +17 -0
- package/dist/modules/NostrParams.d.ts +8 -0
- package/dist/modules/Popup.d.ts +7 -0
- package/dist/modules/ProcessManager.d.ts +10 -0
- package/dist/modules/Signer.d.ts +9 -0
- package/dist/modules/index.d.ts +8 -0
- package/dist/types.d.ts +72 -0
- package/dist/unpkg.js +17 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/nip44.d.ts +9 -0
- package/index.html +30 -0
- package/package.json +28 -0
- package/rollup.config.js +55 -0
- package/src/const/index.ts +1 -0
- package/src/iife-module.ts +81 -0
- package/src/index.ts +347 -0
- package/src/modules/AuthNostrService.ts +756 -0
- package/src/modules/BannerManager.ts +146 -0
- package/src/modules/ModalManager.ts +635 -0
- package/src/modules/Nip46.ts +441 -0
- package/src/modules/Nostr.ts +107 -0
- package/src/modules/NostrExtensionService.ts +99 -0
- package/src/modules/NostrParams.ts +18 -0
- package/src/modules/Popup.ts +27 -0
- package/src/modules/ProcessManager.ts +67 -0
- package/src/modules/Signer.ts +25 -0
- package/src/modules/index.ts +8 -0
- package/src/types.ts +124 -0
- package/src/utils/index.ts +326 -0
- package/src/utils/nip44.ts +185 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { EventEmitter } from 'tseep';
|
|
2
|
+
import { CALL_TIMEOUT } from '../const';
|
|
3
|
+
|
|
4
|
+
class ProcessManager extends EventEmitter {
|
|
5
|
+
private callCount: number = 0;
|
|
6
|
+
private callTimer: NodeJS.Timeout | undefined;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public onAuthUrl() {
|
|
13
|
+
if (Boolean(this.callTimer)) {
|
|
14
|
+
clearTimeout(this.callTimer);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public onIframeUrl() {
|
|
19
|
+
if (Boolean(this.callTimer)) {
|
|
20
|
+
clearTimeout(this.callTimer);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async wait<T>(cb: () => Promise<T>): Promise<T> {
|
|
25
|
+
// FIXME only allow 1 parallel req
|
|
26
|
+
|
|
27
|
+
if (!this.callTimer) {
|
|
28
|
+
this.callTimer = setTimeout(() => this.emit('onCallTimeout'), CALL_TIMEOUT);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!this.callCount) {
|
|
32
|
+
this.emit('onCallStart');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.callCount++;
|
|
36
|
+
|
|
37
|
+
let error;
|
|
38
|
+
let result;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
result = await cb();
|
|
42
|
+
} catch (e) {
|
|
43
|
+
error = e;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.callCount--;
|
|
47
|
+
|
|
48
|
+
this.emit('onCallEnd');
|
|
49
|
+
|
|
50
|
+
if (this.callTimer) {
|
|
51
|
+
clearTimeout(this.callTimer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.callTimer = undefined;
|
|
55
|
+
|
|
56
|
+
if (error) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// we can't return undefined bcs an exception is
|
|
61
|
+
// thrown above on error
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default ProcessManager;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
|
|
2
|
+
import { Nip44 } from '../utils/nip44';
|
|
3
|
+
import { getPublicKey } from 'nostr-tools';
|
|
4
|
+
|
|
5
|
+
export class PrivateKeySigner extends NDKPrivateKeySigner {
|
|
6
|
+
private nip44: Nip44 = new Nip44();
|
|
7
|
+
private _pubkey: string;
|
|
8
|
+
|
|
9
|
+
constructor(privateKey: string) {
|
|
10
|
+
super(privateKey);
|
|
11
|
+
this._pubkey = getPublicKey(privateKey);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get pubkey() {
|
|
15
|
+
return this._pubkey;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
encryptNip44(recipient: NDKUser, value: string): Promise<string> {
|
|
19
|
+
return Promise.resolve(this.nip44.encrypt(this.privateKey!, recipient.pubkey, value));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
decryptNip44(sender: NDKUser, value: string): Promise<string> {
|
|
23
|
+
return Promise.resolve(this.nip44.decrypt(this.privateKey!, sender.pubkey, value));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as BannerManager } from './BannerManager';
|
|
2
|
+
export { default as AuthNostrService } from './AuthNostrService';
|
|
3
|
+
export { default as ModalManager } from './ModalManager';
|
|
4
|
+
export { default as Nostr } from './Nostr';
|
|
5
|
+
export { default as NostrExtensionService } from './NostrExtensionService';
|
|
6
|
+
export { default as NostrParams } from './NostrParams';
|
|
7
|
+
export { default as Popup } from './Popup';
|
|
8
|
+
export { default as ProcessManager } from './ProcessManager';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Info, AuthMethod, ConnectionString, RecentType, BannerNotify } from 'nostr-login-components';
|
|
2
|
+
|
|
3
|
+
export interface NostrLoginAuthOptions {
|
|
4
|
+
localNsec?: string;
|
|
5
|
+
relays?: string[];
|
|
6
|
+
type: 'login' | 'signup' | 'logout';
|
|
7
|
+
method?: AuthMethod;
|
|
8
|
+
pubkey?: string;
|
|
9
|
+
otpData?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// NOTE: must be a subset of CURRENT_MODULE enum
|
|
14
|
+
export type StartScreens =
|
|
15
|
+
| 'welcome'
|
|
16
|
+
| 'welcome-login'
|
|
17
|
+
| 'welcome-signup'
|
|
18
|
+
| 'signup'
|
|
19
|
+
| 'local-signup'
|
|
20
|
+
| 'login'
|
|
21
|
+
| 'otp'
|
|
22
|
+
| 'connect'
|
|
23
|
+
| 'login-bunker-url'
|
|
24
|
+
| 'login-read-only'
|
|
25
|
+
| 'connection-string'
|
|
26
|
+
| 'switch-account'
|
|
27
|
+
| 'import';
|
|
28
|
+
|
|
29
|
+
export interface NostrLoginOptions {
|
|
30
|
+
// optional
|
|
31
|
+
theme?: string;
|
|
32
|
+
startScreen?: StartScreens;
|
|
33
|
+
bunkers?: string;
|
|
34
|
+
onAuth?: (npub: string, options: NostrLoginAuthOptions) => void;
|
|
35
|
+
perms?: string;
|
|
36
|
+
darkMode?: boolean;
|
|
37
|
+
|
|
38
|
+
// do not show the banner, modals must be `launch`-ed
|
|
39
|
+
noBanner?: boolean;
|
|
40
|
+
|
|
41
|
+
// forward reqs to this bunker origin for testing
|
|
42
|
+
devOverrideBunkerOrigin?: string;
|
|
43
|
+
|
|
44
|
+
// deprecated, use methods=['local']
|
|
45
|
+
// use local signup instead of nostr connect
|
|
46
|
+
localSignup?: boolean;
|
|
47
|
+
|
|
48
|
+
// allowed auth methods
|
|
49
|
+
methods?: AuthMethod[];
|
|
50
|
+
|
|
51
|
+
// otp endpoints
|
|
52
|
+
otpRequestUrl?: string;
|
|
53
|
+
otpReplyUrl?: string;
|
|
54
|
+
|
|
55
|
+
// welcome screen's title/desc
|
|
56
|
+
title?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
|
|
59
|
+
// comma-separated list of relays added
|
|
60
|
+
// to relay list of new profiles created with local signup
|
|
61
|
+
signupRelays?: string;
|
|
62
|
+
|
|
63
|
+
// relay list to override hardcoded `OUTBOX_RELAYS` constant
|
|
64
|
+
outboxRelays?: string[];
|
|
65
|
+
|
|
66
|
+
// dev mode
|
|
67
|
+
dev?: boolean;
|
|
68
|
+
|
|
69
|
+
// use start.njump.me instead of local signup
|
|
70
|
+
signupNstart?: boolean;
|
|
71
|
+
|
|
72
|
+
// list of npubs to auto/suggest-follow on signup
|
|
73
|
+
followNpubs?: string;
|
|
74
|
+
|
|
75
|
+
// when method call auth needed, instead of showing
|
|
76
|
+
// the modal, we start waiting for incoming nip46
|
|
77
|
+
// connection and send the nostrconnect string using
|
|
78
|
+
// nlNeedAuth event
|
|
79
|
+
customNostrConnect?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IBanner {
|
|
83
|
+
userInfo?: Info | null;
|
|
84
|
+
titleBanner?: string;
|
|
85
|
+
isLoading?: boolean;
|
|
86
|
+
listNotifies?: string[];
|
|
87
|
+
accounts?: Info[];
|
|
88
|
+
isOpen?: boolean;
|
|
89
|
+
darkMode?: boolean;
|
|
90
|
+
notify?: BannerNotify;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type TypeBanner = IBanner & HTMLElement;
|
|
94
|
+
|
|
95
|
+
export interface IModal {
|
|
96
|
+
authUrl?: string;
|
|
97
|
+
iframeUrl?: string;
|
|
98
|
+
isLoading?: boolean;
|
|
99
|
+
isOTP?: boolean;
|
|
100
|
+
isLoadingExtension?: boolean;
|
|
101
|
+
localSignup?: boolean;
|
|
102
|
+
signupNjump?: boolean;
|
|
103
|
+
njumpIframe?: string;
|
|
104
|
+
authMethods?: AuthMethod[];
|
|
105
|
+
hasExtension?: boolean;
|
|
106
|
+
hasOTP?: boolean;
|
|
107
|
+
error?: string;
|
|
108
|
+
signupNameIsAvailable?: string | boolean;
|
|
109
|
+
loginIsGood?: string | boolean;
|
|
110
|
+
recents?: RecentType[];
|
|
111
|
+
accounts?: Info[];
|
|
112
|
+
darkMode?: boolean;
|
|
113
|
+
welcomeTitle?: string;
|
|
114
|
+
welcomeDescription?: string;
|
|
115
|
+
connectionString?: string;
|
|
116
|
+
connectionStringServices?: ConnectionString[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type TypeModal = IModal & HTMLElement;
|
|
120
|
+
|
|
121
|
+
export interface Response {
|
|
122
|
+
result?: string;
|
|
123
|
+
error?: string;
|
|
124
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Info, RecentType } from 'nostr-login-components';
|
|
2
|
+
import NDK, { NDKEvent, NDKRelaySet, NDKSigner, NDKUser } from '@nostr-dev-kit/ndk';
|
|
3
|
+
import { generatePrivateKey } from 'nostr-tools';
|
|
4
|
+
import { NostrLoginOptions } from '../types';
|
|
5
|
+
|
|
6
|
+
const LOCAL_STORE_KEY = '__nostrlogin_nip46';
|
|
7
|
+
const LOGGED_IN_ACCOUNTS = '__nostrlogin_accounts';
|
|
8
|
+
const RECENT_ACCOUNTS = '__nostrlogin_recent';
|
|
9
|
+
const OUTBOX_RELAYS = ['wss://purplepag.es', 'wss://relay.nos.social', 'wss://user.kindpag.es', 'wss://relay.damus.io', 'wss://nos.lol'];
|
|
10
|
+
const DEFAULT_SIGNUP_RELAYS = ['wss://relay.damus.io/', 'wss://nos.lol/', 'wss://relay.primal.net/'];
|
|
11
|
+
|
|
12
|
+
export const localStorageSetItem = (key: string, value: string) => {
|
|
13
|
+
localStorage.setItem(key, value);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const localStorageGetItem = (key: string) => {
|
|
17
|
+
const value = window.localStorage.getItem(key);
|
|
18
|
+
|
|
19
|
+
if (value) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(value);
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const localStorageRemoveItem = (key: string) => {
|
|
29
|
+
localStorage.removeItem(key);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const fetchProfile = async (info: Info, profileNdk: NDK) => {
|
|
33
|
+
const user = new NDKUser({ pubkey: info.pubkey });
|
|
34
|
+
|
|
35
|
+
user.ndk = profileNdk;
|
|
36
|
+
|
|
37
|
+
return await user.fetchProfile();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const prepareSignupRelays = (signupRelays?: string) => {
|
|
41
|
+
const relays = (signupRelays || '')
|
|
42
|
+
.split(',')
|
|
43
|
+
.map(r => r.trim())
|
|
44
|
+
.filter(r => r.startsWith('ws'));
|
|
45
|
+
if (!relays.length) relays.push(...DEFAULT_SIGNUP_RELAYS);
|
|
46
|
+
return relays;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const createProfile = async (info: Info, profileNdk: NDK, signer: NDKSigner, signupRelays?: string, outboxRelays?: string[]) => {
|
|
50
|
+
const meta = {
|
|
51
|
+
name: info.name,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const profileEvent = new NDKEvent(profileNdk, {
|
|
55
|
+
kind: 0,
|
|
56
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
57
|
+
pubkey: info.pubkey,
|
|
58
|
+
content: JSON.stringify(meta),
|
|
59
|
+
tags: [],
|
|
60
|
+
});
|
|
61
|
+
if (window.location.hostname) profileEvent.tags.push(['client', window.location.hostname]);
|
|
62
|
+
|
|
63
|
+
const relaysEvent = new NDKEvent(profileNdk, {
|
|
64
|
+
kind: 10002,
|
|
65
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
66
|
+
pubkey: info.pubkey,
|
|
67
|
+
content: '',
|
|
68
|
+
tags: [],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const relays = prepareSignupRelays(signupRelays)
|
|
72
|
+
for (const r of relays) {
|
|
73
|
+
relaysEvent.tags.push(['r', r]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await profileEvent.sign(signer);
|
|
77
|
+
console.log('signed profile', profileEvent);
|
|
78
|
+
await relaysEvent.sign(signer);
|
|
79
|
+
console.log('signed relays', relaysEvent);
|
|
80
|
+
|
|
81
|
+
const outboxRelaysFinal = outboxRelays && outboxRelays.length ? outboxRelays : OUTBOX_RELAYS;
|
|
82
|
+
|
|
83
|
+
await profileEvent.publish(NDKRelaySet.fromRelayUrls(outboxRelaysFinal, profileNdk));
|
|
84
|
+
console.log('published profile', profileEvent);
|
|
85
|
+
await relaysEvent.publish(NDKRelaySet.fromRelayUrls(outboxRelaysFinal, profileNdk));
|
|
86
|
+
console.log('published relays', relaysEvent);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const bunkerUrlToInfo = (bunkerUrl: string, sk = ''): Info => {
|
|
90
|
+
const url = new URL(bunkerUrl);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
pubkey: '',
|
|
94
|
+
signerPubkey: url.hostname || url.pathname.split('//')[1],
|
|
95
|
+
sk: sk || generatePrivateKey(),
|
|
96
|
+
relays: url.searchParams.getAll('relay'),
|
|
97
|
+
token: url.searchParams.get('secret') || '',
|
|
98
|
+
authMethod: 'connect',
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const isBunkerUrl = (value: string) => value.startsWith('bunker://');
|
|
103
|
+
|
|
104
|
+
export const getBunkerUrl = async (value: string, optionsModal: NostrLoginOptions) => {
|
|
105
|
+
if (!value) {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isBunkerUrl(value)) {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (value.includes('@')) {
|
|
114
|
+
const [name, domain] = value.toLocaleLowerCase().split('@');
|
|
115
|
+
const origin = optionsModal.devOverrideBunkerOrigin || `https://${domain}`;
|
|
116
|
+
const bunkerUrl = `${origin}/.well-known/nostr.json?name=_`;
|
|
117
|
+
const userUrl = `${origin}/.well-known/nostr.json?name=${name}`;
|
|
118
|
+
const bunker = await fetch(bunkerUrl);
|
|
119
|
+
const bunkerData = await bunker.json();
|
|
120
|
+
const bunkerPubkey = bunkerData.names['_'];
|
|
121
|
+
const bunkerRelays = bunkerData.nip46[bunkerPubkey];
|
|
122
|
+
const user = await fetch(userUrl);
|
|
123
|
+
const userData = await user.json();
|
|
124
|
+
const userPubkey = userData.names[name];
|
|
125
|
+
// console.log({
|
|
126
|
+
// bunkerData, userData, bunkerPubkey, bunkerRelays, userPubkey,
|
|
127
|
+
// name, domain, origin
|
|
128
|
+
// })
|
|
129
|
+
if (!bunkerRelays.length) {
|
|
130
|
+
throw new Error('Bunker relay not provided');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return `bunker://${userPubkey}?relay=${bunkerRelays[0]}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw new Error('Invalid user name or bunker url');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const checkNip05 = async (nip05: string) => {
|
|
140
|
+
let available = false;
|
|
141
|
+
let error = '';
|
|
142
|
+
let pubkey = '';
|
|
143
|
+
await (async () => {
|
|
144
|
+
if (!nip05 || !nip05.includes('@')) return;
|
|
145
|
+
|
|
146
|
+
const [name, domain] = nip05.toLocaleLowerCase().split('@');
|
|
147
|
+
if (!name) return;
|
|
148
|
+
|
|
149
|
+
const REGEXP = new RegExp(/^[\w-.]+@([\w-]+\.)+[\w-]{2,8}$/g);
|
|
150
|
+
if (!REGEXP.test(nip05)) {
|
|
151
|
+
error = 'Invalid name';
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!domain) {
|
|
156
|
+
error = 'Select service';
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const url = `https://${domain}/.well-known/nostr.json?name=${name.toLowerCase()}`;
|
|
161
|
+
try {
|
|
162
|
+
const r = await fetch(url);
|
|
163
|
+
const d = await r.json();
|
|
164
|
+
if (d.names[name]) {
|
|
165
|
+
pubkey = d.names[name];
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
|
|
170
|
+
available = true;
|
|
171
|
+
})();
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
available,
|
|
175
|
+
taken: pubkey != '',
|
|
176
|
+
error,
|
|
177
|
+
pubkey,
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const upgradeInfo = (info: Info | RecentType) => {
|
|
182
|
+
if ('typeAuthMethod' in info) delete info['typeAuthMethod'];
|
|
183
|
+
|
|
184
|
+
if (!info.authMethod) {
|
|
185
|
+
if ('extension' in info && info['extension']) info.authMethod = 'extension';
|
|
186
|
+
else if ('readOnly' in info && info['readOnly']) info.authMethod = 'readOnly';
|
|
187
|
+
else info.authMethod = 'connect';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (info.nip05 && isBunkerUrl(info.nip05)) {
|
|
191
|
+
info.bunkerUrl = info.nip05;
|
|
192
|
+
info.nip05 = '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (info.authMethod === 'connect' && !info.signerPubkey) {
|
|
196
|
+
info.signerPubkey = info.pubkey;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const localStorageAddAccount = (info: Info) => {
|
|
201
|
+
// make current
|
|
202
|
+
localStorageSetItem(LOCAL_STORE_KEY, JSON.stringify(info));
|
|
203
|
+
|
|
204
|
+
const loggedInAccounts: Info[] = localStorageGetItem(LOGGED_IN_ACCOUNTS) || [];
|
|
205
|
+
const recentAccounts: RecentType[] = localStorageGetItem(RECENT_ACCOUNTS) || [];
|
|
206
|
+
|
|
207
|
+
// upgrade first
|
|
208
|
+
loggedInAccounts.forEach(a => upgradeInfo(a));
|
|
209
|
+
recentAccounts.forEach(a => upgradeInfo(a));
|
|
210
|
+
|
|
211
|
+
// upsert new info into accounts
|
|
212
|
+
const accounts: Info[] = loggedInAccounts;
|
|
213
|
+
const index = loggedInAccounts.findIndex((el: Info) => el.pubkey === info.pubkey && el.authMethod === info.authMethod);
|
|
214
|
+
if (index !== -1) {
|
|
215
|
+
accounts[index] = info;
|
|
216
|
+
} else {
|
|
217
|
+
accounts.push(info);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// remove new info from recent
|
|
221
|
+
const recents = recentAccounts.filter(el => el.pubkey !== info.pubkey || el.authMethod !== info.authMethod);
|
|
222
|
+
|
|
223
|
+
localStorageSetItem(RECENT_ACCOUNTS, JSON.stringify(recents));
|
|
224
|
+
localStorageSetItem(LOGGED_IN_ACCOUNTS, JSON.stringify(accounts));
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const localStorageRemoveCurrentAccount = () => {
|
|
228
|
+
const user: Info = localStorageGetItem(LOCAL_STORE_KEY);
|
|
229
|
+
if (!user) return;
|
|
230
|
+
|
|
231
|
+
// make sure it's valid
|
|
232
|
+
upgradeInfo(user);
|
|
233
|
+
|
|
234
|
+
// remove secret fields
|
|
235
|
+
const recentUser: RecentType = { ...user };
|
|
236
|
+
|
|
237
|
+
// make sure session keys are dropped
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
delete recentUser['sk'];
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
delete recentUser['otpData'];
|
|
242
|
+
|
|
243
|
+
// get accounts and recent
|
|
244
|
+
const loggedInAccounts: Info[] = localStorageGetItem(LOGGED_IN_ACCOUNTS) || [];
|
|
245
|
+
const recentsAccounts: RecentType[] = localStorageGetItem(RECENT_ACCOUNTS) || [];
|
|
246
|
+
|
|
247
|
+
// upgrade first
|
|
248
|
+
loggedInAccounts.forEach(a => upgradeInfo(a));
|
|
249
|
+
recentsAccounts.forEach(a => upgradeInfo(a));
|
|
250
|
+
|
|
251
|
+
const recents: RecentType[] = recentsAccounts;
|
|
252
|
+
if (recentUser.authMethod === 'connect' && recentUser.bunkerUrl && recentUser.bunkerUrl.includes('secret=')) {
|
|
253
|
+
console.log('nostr login bunker conn with a secret not saved to recent');
|
|
254
|
+
} else if (recentUser.authMethod === 'local') {
|
|
255
|
+
console.log('nostr login temporary local keys not save to recent');
|
|
256
|
+
} else {
|
|
257
|
+
// upsert to recent
|
|
258
|
+
const index = recentsAccounts.findIndex((el: RecentType) => el.pubkey === recentUser.pubkey && el.authMethod === recentUser.authMethod);
|
|
259
|
+
if (index !== -1) {
|
|
260
|
+
recents[index] = recentUser;
|
|
261
|
+
} else {
|
|
262
|
+
recents.push(recentUser);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// remove from accounts
|
|
267
|
+
const accounts = loggedInAccounts.filter(el => el.pubkey !== user.pubkey || el.authMethod !== user.authMethod);
|
|
268
|
+
|
|
269
|
+
// update accounts and recent, clear current
|
|
270
|
+
localStorageSetItem(RECENT_ACCOUNTS, JSON.stringify(recents));
|
|
271
|
+
localStorageSetItem(LOGGED_IN_ACCOUNTS, JSON.stringify(accounts));
|
|
272
|
+
localStorageRemoveItem(LOCAL_STORE_KEY);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export const localStorageRemoveRecent = (user: RecentType) => {
|
|
276
|
+
const recentsAccounts: RecentType[] = localStorageGetItem(RECENT_ACCOUNTS) || [];
|
|
277
|
+
recentsAccounts.forEach(a => upgradeInfo(a));
|
|
278
|
+
const recents = recentsAccounts.filter(el => el.pubkey !== user.pubkey || el.authMethod !== user.authMethod);
|
|
279
|
+
localStorageSetItem(RECENT_ACCOUNTS, JSON.stringify(recents));
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const localStorageGetRecents = (): RecentType[] => {
|
|
283
|
+
const recents: RecentType[] = localStorageGetItem(RECENT_ACCOUNTS) || [];
|
|
284
|
+
recents.forEach(r => upgradeInfo(r));
|
|
285
|
+
return recents;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const localStorageGetAccounts = (): Info[] => {
|
|
289
|
+
const accounts: Info[] = localStorageGetItem(LOGGED_IN_ACCOUNTS) || [];
|
|
290
|
+
accounts.forEach(a => upgradeInfo(a));
|
|
291
|
+
return accounts;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export const localStorageGetCurrent = (): Info | null => {
|
|
295
|
+
const info = localStorageGetItem(LOCAL_STORE_KEY);
|
|
296
|
+
if (info) upgradeInfo(info);
|
|
297
|
+
return info;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export const setDarkMode = (dark: boolean) => {
|
|
301
|
+
localStorageSetItem('nl-dark-mode', dark ? 'true' : 'false');
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export const getDarkMode = (opt: NostrLoginOptions) => {
|
|
305
|
+
const getDarkModeLocal = localStorage.getItem('nl-dark-mode');
|
|
306
|
+
|
|
307
|
+
if (getDarkModeLocal) {
|
|
308
|
+
// user already changed it
|
|
309
|
+
return Boolean(JSON.parse(getDarkModeLocal));
|
|
310
|
+
} else if (opt.darkMode !== undefined) {
|
|
311
|
+
// app provided an option
|
|
312
|
+
return opt.darkMode;
|
|
313
|
+
} else {
|
|
314
|
+
// auto-detect
|
|
315
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
316
|
+
return true;
|
|
317
|
+
} else {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const getIcon = async () => {
|
|
324
|
+
// FIXME look at meta tags or manifest
|
|
325
|
+
return document.location.origin + '/favicon.ico';
|
|
326
|
+
};
|