on master help sign in
Merge pull request #446 from urbit/lf/login-single-click
9277c41Fang
src/components/LoginButton.jsnew file+27
@@ -0,0 +1,27 @@
1+import React, { useRef } from 'react';
2+import { Button } from 'indigo-react';
3+
4+export default function LoginButton({ url, className, code, ...rest }) {
5+ const formEl = useRef(null);
6+ return (
7+ <form
8+ style={{ display: 'contents' }}
9+ action={url}
10+ method="POST"
11+ target="_blank"
12+ ref={formEl}>
13+ <input name="password" value={code} type="hidden" />
14+ <input name="redirect" value="/" type="hidden" />
15+ <Button
16+ type="submit"
17+ accessory="↗"
18+ solid
19+ success
20+ className={className}
21+ onClick={() => formEl.current.submit()}
22+ {...rest}>
23+ Open OS
24+ </Button>
25+ </form>
26+ );
27+}
src/lib/useLocaHosting.jsnew file+51
@@ -0,0 +1,51 @@
1+import { useState, useEffect, useMemo } from 'react';
2+import { Just } from 'folktale/maybe';
3+import * as ob from 'urbit-ob';
4+
5+import { usePointCursor } from 'store/pointCursor';
6+
7+const DOMAIN = 'arvo.network';
8+
9+const STATE = {
10+ UNKNOWN: 'UNKNOWN',
11+ MISSING: 'MISSING',
12+ RUNNING: 'RUNNING',
13+};
14+
15+export default function useLocalHosting() {
16+ const [status, setStatus] = useState(STATE.UNKNOWN);
17+
18+ const { pointCursor } = usePointCursor();
19+
20+ useEffect(() => {
21+ const updateStatus = async point => {
22+ const patp = ob.patp(point).slice(1);
23+ try {
24+ const { who } = await fetch(`https://${patp}.${DOMAIN}/who.json`).then(
25+ r => r.json()
26+ );
27+ if (who === patp) {
28+ setStatus(STATE.RUNNING);
29+ } else {
30+ setStatus(STATE.MISSING);
31+ }
32+ } catch (e) {
33+ setStatus(STATE.MISSING);
34+ }
35+ };
36+ if (Just.hasInstance(pointCursor)) {
37+ updateStatus(pointCursor.value);
38+ }
39+ }, [pointCursor]);
40+
41+ const url = useMemo(
42+ () =>
43+ pointCursor.value &&
44+ `https://${ob.patp(pointCursor.value).slice(1)}.${DOMAIN}`,
45+ [pointCursor]
46+ );
47+
48+ const running = useMemo(() => status === STATE.RUNNING, [status]);
49+
50+ return { running, url };
51+}
src/views/UrbitOS/Home.js+43−15
@@ -1,4 +1,4 @@
1−import React, { useCallback, useState, useEffect } from 'react';
1+import React, { useCallback, useState, useEffect, useMemo } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, Button, SelectInput, Flex } from 'indigo-react';
3import { Grid, Button, SelectInput, Flex } from 'indigo-react';
4import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
@@ -15,12 +15,14 @@ import { OutButton, ForwardButton } from 'components/Buttons';
15import { CopyButtonWide } from 'components/CopyButton';
15import { CopyButtonWide } from 'components/CopyButton';
16import NetworkingKeys from 'components/NetworkingKeys';
16import NetworkingKeys from 'components/NetworkingKeys';
17import ProgressButton from 'components/ProgressButton';
17import ProgressButton from 'components/ProgressButton';
18+import LoginButton from 'components/LoginButton';
18
19
19import BridgeForm from 'form/BridgeForm';
20import BridgeForm from 'form/BridgeForm';
20import { useLocalRouter } from 'lib/LocalRouter';
21import { useLocalRouter } from 'lib/LocalRouter';
21import { useHosting } from 'store/hosting';
22import { useHosting } from 'store/hosting';
22import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
23import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
23import useLifecycle from 'lib/useLifecycle';
24import useLifecycle from 'lib/useLifecycle';
25+import useLocalHosting from 'lib/useLocaHosting';
24
26
25export default function UrbitOSHome({ manualNetworkSeed }) {
27export default function UrbitOSHome({ manualNetworkSeed }) {
26 const { pointCursor } = usePointCursor();
28 const { pointCursor } = usePointCursor();
@@ -103,6 +105,13 @@ function Hosting({ manualNetworkSeed }) {
103 disabled,
105 disabled,
104 } = ship;
106 } = ship;
105
107
108+ const { running: localRunning, url: localUrl } = useLocalHosting();
109+
110+ let running = useMemo(() => localRunning || ship.running, [
111+ ship,
112+ localRunning,
113+ ]);
114+
106 useLifecycle(() => {
115 useLifecycle(() => {
107 syncStatus();
116 syncStatus();
108 });
117 });
@@ -113,10 +122,29 @@ function Hosting({ manualNetworkSeed }) {
113 const options = [{ text: 'Tlon', value: 'tlon' }];
122 const options = [{ text: 'Tlon', value: 'tlon' }];
114
123
115 const renderMain = useCallback(() => {
124 const renderMain = useCallback(() => {
125+ if (localRunning) {
126+ return (
127+ <Grid.Item
128+ full
129+ as={LoginButton}
130+ solid
131+ success
132+ url={localUrl}
133+ code={code}>
134+ Open OS
135+ </Grid.Item>
136+ );
137+ }
116 if (ship.running) {
138 if (ship.running) {
117 return (
139 return (
118 <>
140 <>
119− <Grid.Item cols={[1, 9]} as={OutButton} solid success href={url}>
141+ <Grid.Item
142+ cols={[1, 9]}
143+ as={LoginButton}
144+ solid
145+ success
146+ url={url}
147+ code={code}>
120 Open OS
148 Open OS
121 </Grid.Item>
149 </Grid.Item>
122 {/* Unsupported for now */}
150 {/* Unsupported for now */}
@@ -151,6 +179,7 @@ function Hosting({ manualNetworkSeed }) {
151 );
179 );
152 }
180 }
153 }, [
181 }, [
182+ localRunning,
154 ship.missing,
183 ship.missing,
155 ship.running,
184 ship.running,
156 ship.pending,
185 ship.pending,
@@ -195,11 +224,8 @@ function Hosting({ manualNetworkSeed }) {
195 <Grid.Item full className="f5" as={Flex}>
224 <Grid.Item full className="f5" as={Flex}>
196 <Flex.Item>Urbit OS </Flex.Item>
225 <Flex.Item>Urbit OS </Flex.Item>
197 <Flex.Item
226 <Flex.Item
198− className={cn(
199− { green3: ship.running, gray4: !ship.running },
227+ className={cn({ green3: running, gray4: !running }, 'ml3')}>
200− 'ml3'
201− )}>
202− {ship.running ? 'Connected' : 'Disconnected'}
228+ {running ? 'Connected' : 'Disconnected'}
203 </Flex.Item>
229 </Flex.Item>
204 </Grid.Item>
230 </Grid.Item>
205 <BridgeForm initialValues={{ provider: 'tlon' }}>
231 <BridgeForm initialValues={{ provider: 'tlon' }}>
@@ -207,14 +233,16 @@ function Hosting({ manualNetworkSeed }) {
207 <>
233 <>
208 {renderDetails()}
234 {renderDetails()}
209 {renderMain()}
235 {renderMain()}
236+ {!localRunning && (
210− <Grid.Item
237+ <Grid.Item
211− full
238+ full
212− as={SelectInput}
239+ as={SelectInput}
213− name="provider"
240+ name="provider"
214− label="Host Provider"
241+ label="Host Provider"
215− options={options}
242+ options={options}
216− disabled
243+ disabled
217− />
244+ />
245+ )}
218 </>
246 </>
219 )}
247 )}
220 </BridgeForm>
248 </BridgeForm>