Skip to content

Commit 983ffa6

Browse files
committed
Add saving token locally
1 parent 1df4b41 commit 983ffa6

File tree

3 files changed

+200
-57
lines changed

3 files changed

+200
-57
lines changed

npm-packages/cli/package-lock.json

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 144 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
import React, {useEffect, useState} from 'react';
2-
import {Box, Text} from 'ink';
3-
// import Spinner from 'ink-spinner';
2+
import {Box, Newline, Text, useApp} from 'ink';
43
import SelectInput from 'ink-select-input';
54
import {Flags} from '../flags.js';
65
import {Result} from 'meow';
7-
import TextInput from 'ink-text-input';
6+
import TextInput, {UncontrolledTextInput} from 'ink-text-input';
87
import Spinner from 'ink-spinner';
8+
import os from 'os';
9+
import path from 'path';
10+
import fs from 'fs';
911

1012
type Item<V> = {
1113
key?: string;
1214
label: string;
1315
value: V;
1416
};
1517

16-
type LoginMethod = 'token' | 'flow';
17-
1818
export default function Login({cli}: {cli: Result<Flags>}) {
19-
const [isCheckingAuth, setIsCheckingAuth] = useState(false);
19+
const [loginMethod, setLoginMethod] = useState<string | undefined>(undefined);
20+
const [isShowingSelect, setIsShowingSelect] = useState(false);
21+
const [isShowingInput, setIsShowingInput] = useState(false);
22+
23+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
2024
const [isAuthenticated, setIsAuthenticated] = useState(false);
21-
const [loginMethod, setLoginMethod] = useState<LoginMethod | undefined>(
22-
undefined,
23-
);
2425
const [token, setToken] = useState<string>('');
2526

26-
const items: Item<LoginMethod>[] = [
27+
const [isTokenInEnv, setIsTokenInEnv] = useState(false);
28+
const [isTokenSaved, setIsTokenSaved] = useState(false);
29+
30+
const [tokenInput, setTokenInput] = useState<string>('');
31+
const [saveTokenInput, setSaveTokenInput] = useState<string>('');
32+
33+
const loginMethodItems: Item<string>[] = [
2734
{
2835
label: 'Login using access token',
2936
value: 'token',
@@ -34,21 +41,30 @@ export default function Login({cli}: {cli: Result<Flags>}) {
3441
},
3542
];
3643

44+
const {exit} = useApp();
45+
3746
// Check login method
3847
useEffect(() => {
39-
const tokenEnv = process.env['PE_ACCESS_TOKEN'];
40-
if (tokenEnv) {
48+
const savedToken = getToken();
49+
if (savedToken) {
4150
setLoginMethod('token');
42-
setToken(tokenEnv);
43-
}
44-
45-
if (cli.flags.token) {
51+
setToken(savedToken);
52+
return;
53+
} else if (cli.flags.token) {
4654
setLoginMethod('token');
4755
} else if (cli.flags.flow) {
4856
setLoginMethod('flow');
4957
}
5058
}, [cli]);
5159

60+
useEffect(() => {
61+
setIsShowingSelect(loginMethod === undefined);
62+
}, [loginMethod]);
63+
64+
useEffect(() => {
65+
setIsShowingInput(!isShowingSelect);
66+
}, [isShowingSelect]);
67+
5268
// Check token validity
5369
useEffect(() => {
5470
async function checkToken(token: string) {
@@ -69,60 +85,134 @@ export default function Login({cli}: {cli: Result<Flags>}) {
6985

7086
// Only check token validity when it is set
7187
if (loginMethod === 'token' && token.length > 0) {
72-
setIsCheckingAuth(true);
7388
checkToken(token).then(isValid => {
7489
setIsAuthenticated(isValid);
7590
setIsCheckingAuth(false);
7691
});
7792
}
7893
}, [token, loginMethod]);
7994

80-
if (loginMethod === undefined) {
81-
// Prompt user to select login method
82-
return (
83-
<>
84-
<Text>Login to the Pulse Editor Platform</Text>
85-
<SelectInput
86-
items={items}
87-
onSelect={item => setLoginMethod(item.value)}
88-
/>
89-
</>
90-
);
91-
} else if (loginMethod === 'token') {
92-
// Prompt user to enter token if not already set
93-
if (token.length === 0) {
94-
return (
95-
<>
96-
<Text>Enter your Pulse Editor access token:</Text>
97-
<TextInput
98-
value={token}
99-
onChange={setToken}
100-
onSubmit={value => setToken(value)}
101-
/>
102-
</>
103-
);
95+
function saveToken(token: string) {
96+
// Save the token to .pulse-editor/config.json in user home directory
97+
const configDir = path.join(os.homedir(), '.pulse-editor');
98+
const configFile = path.join(configDir, 'config.json');
99+
const config = {
100+
accessToken: token,
101+
};
102+
if (!fs.existsSync(configDir)) {
103+
fs.mkdirSync(configDir, {recursive: true});
104104
}
105+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
106+
}
105107

106-
// Check token validity
107-
if (isCheckingAuth) {
108-
return (
109-
<Box>
110-
<Spinner type="dots" />
111-
<Text> Checking authentication...</Text>
112-
</Box>
113-
);
108+
function getToken() {
109+
// First try to get the token from the environment variable
110+
const tokenEnv = process.env['PE_ACCESS_TOKEN'];
111+
if (tokenEnv) {
112+
setIsTokenInEnv(true);
113+
return tokenEnv;
114114
}
115115

116-
if (isAuthenticated) {
117-
return <Text>You are signed in successfully.</Text>;
116+
// If not found, try to get the token from the config file
117+
const configDir = path.join(os.homedir(), '.pulse-editor');
118+
const configFile = path.join(configDir, 'config.json');
119+
if (fs.existsSync(configFile)) {
120+
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
121+
if (config.accessToken) {
122+
return config.accessToken as string;
123+
}
118124
}
119-
return <Text>Authentication error: please enter valid credentials.</Text>;
125+
126+
// If not found, return undefined
127+
return undefined;
120128
}
121129

122130
return (
123131
<>
124-
<Text>(WIP) Open the following URL in your browser:</Text>
125-
<Text>https://pulse-editor.com/login</Text>
132+
{isShowingSelect && (
133+
<>
134+
<Text>Login to the Pulse Editor Platform</Text>
135+
<SelectInput
136+
items={loginMethodItems}
137+
onSelect={item => {
138+
setLoginMethod(item.value);
139+
}}
140+
/>
141+
</>
142+
)}
143+
144+
{isShowingInput &&
145+
loginMethod === 'token' &&
146+
(token.length === 0 ? (
147+
<>
148+
<Text>Enter your Pulse Editor access token:</Text>
149+
<TextInput
150+
mask="*"
151+
value={tokenInput}
152+
onChange={setTokenInput}
153+
onSubmit={value => {
154+
if (value.length === 0) {
155+
return;
156+
}
157+
setToken(value);
158+
}}
159+
/>
160+
</>
161+
) : isCheckingAuth ? (
162+
<Box>
163+
<Spinner type="dots" />
164+
<Text> Checking authentication...</Text>
165+
</Box>
166+
) : isAuthenticated ? (
167+
<>
168+
<Text>✅ You are signed in successfully.</Text>
169+
{!isTokenInEnv && getToken() !== token && (
170+
<>
171+
<Text>
172+
🟢 It is recommended to save your access token as an
173+
environment variable PE_ACCESS_TOKEN.
174+
</Text>
175+
<Box>
176+
<Text>
177+
⚠️ (NOT recommended) Or, do you want to save access token to
178+
user home directory? (y/n){' '}
179+
</Text>
180+
<TextInput
181+
value={saveTokenInput}
182+
onChange={setSaveTokenInput}
183+
onSubmit={value => {
184+
if (value.length === 0) {
185+
return;
186+
}
187+
if (value === 'y') {
188+
saveToken(token);
189+
setIsTokenSaved(true);
190+
setTimeout(() => {
191+
exit();
192+
}, 100);
193+
} else {
194+
exit();
195+
}
196+
}}
197+
/>
198+
</Box>
199+
</>
200+
)}
201+
{isTokenSaved && (
202+
<Text>
203+
Token saved to {path.join(os.homedir(), '.pulse-editor')}
204+
</Text>
205+
)}
206+
</>
207+
) : (
208+
<Text>Authentication error: please enter valid credentials.</Text>
209+
))}
210+
{isShowingInput && loginMethod === 'flow' && (
211+
<>
212+
<Text>(WIP) Open the following URL in your browser:</Text>
213+
<Text>https://pulse-editor.com/login</Text>
214+
</>
215+
)}
126216
</>
127217
);
128218
}

npm-packages/cli/tsconfig.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"extends": "@sindresorhus/tsconfig",
33
"compilerOptions": {
4-
"outDir": "dist"
4+
"outDir": "dist",
5+
"noUnusedLocals": false,
6+
"noUnusedParameters": false,
57
},
6-
"include": ["source"]
7-
}
8+
"include": [
9+
"source"
10+
]
11+
}

0 commit comments

Comments
 (0)