on ded2e62 help sign in
bridge/src/store/lib/useHostingStore.js
234 lines 5.8 kB history raw
1import { useState, useCallback, useMemo } from 'react';
2import { Nothing } from 'folktale/maybe';
3import * as ob from 'urbit-ob';
5import { getVaneName, getVaneNumber } from 'lib/hosting';
6import SolarisClient from 'lib/SolarisClient';
8import { usePointCursor } from 'store/pointCursor';
10const STATE = {
11 UNKNOWN: 'UNKNOWN',
12 MISSING: 'MISSING',
13 RUNNING: 'RUNNING',
14 PENDING: 'PENDING',
15};
17function useHostingStore(url, domain, disabled) {
18 const client = new SolarisClient(url);
20 const { pointCursor } = usePointCursor();
22 const [bootProgress, setBootProgress] = useState(0);
23 const [bootMessage, setBootMessage] = useState('Assembling Urbit');
24 const [startTime] = useState(Date.now());
26 const [status, _setStatus] = useState(STATE.UNKNOWN);
27 const setStatus = useCallback(
28 s => {
29 _setStatus(s);
30 },
31 [_setStatus]
32 );
34 const [sysEvents, setSysEvents] = useState([]);
35 const [newEvents, setNewEvents] = useState([]);
36 const [runEvents, setRunEvents] = useState([]);
38 const [error, _setError] = useState();
39 const setError = useCallback(
40 error => {
41 _setError(error);
42 if (error) {
43 setStatus(STATE.UNKNOWN);
44 console.error(error);
45 }
46 },
47 [_setError, setStatus]
48 );
50 const unknown = status === STATE.UNKNOWN;
51 const missing = status === STATE.MISSING;
52 const running = status === STATE.RUNNING;
53 const pending = status === STATE.PENDING;
55 let syncStatus;
57 const resetEvents = useCallback(() => {
58 setSysEvents([]);
59 setNewEvents([]);
60 setRunEvents([]);
61 }, [setSysEvents, setNewEvents, setRunEvents]);
63 const getEvents = useCallback(() => {
64 try {
65 if (Nothing.hasInstance(pointCursor) || disabled) {
66 return;
67 }
68 const patp = ob.patp(pointCursor.value);
69 setError(undefined);
71 resetEvents();
73 const url = client.getShipsByPatpUrl(patp) + '/events';
74 const source = new EventSource(url);
76 source.addEventListener('sys', event => {
77 const sysEvent = JSON.parse(event.data);
78 const time = new Date(sysEvent.object.firstTimestamp).getTime();
79 if (time > startTime) {
80 setSysEvents(previous => [...previous, sysEvent]);
81 }
82 });
84 source.addEventListener('new', event => {
85 const elapsed = (Date.now() - startTime) / 1000;
86 if (event.data !== '') {
87 setNewEvents(previous => [...previous, event.data]);
88 }
89 if (event.data === '1-b') {
90 setBootMessage('Compiling Hoon');
91 setBootProgress(0.2);
92 } else if (event.data === '1-c') {
93 setBootProgress(0.25);
94 } else if (event.data === '%arvo-assembly') {
95 setBootMessage('Compiling Urbit OS');
96 setBootProgress(0.3);
97 } else if (event.data.startsWith('[%vane %')) {
98 const vane = getVaneName(event.data.slice(8, 9));
99 setBootMessage(`Compiling vane: ${vane}`);
100 setBootProgress(0.3 + 0.05 * getVaneNumber(event.data.slice(8, 9)));
101 } else if (event.data === 'pier: boot complete') {
102 setBootMessage('Ship starting');
103 setBootProgress(0.85);
104 }
105 });
107 source.addEventListener('run', event => {
108 if (event.data === 'goad: recompiling all apps') {
109 setBootProgress(1.0);
110 setBootMessage('Ship Launched');
111 syncStatus();
112 }
113 if (event.data !== '') {
114 setRunEvents(previous => [...previous, event.data]);
115 }
116 });
117 } catch (error) {
118 setError(error);
119 }
120 }, [
121 setError,
122 disabled,
123 resetEvents,
124 client,
125 pointCursor,
126 setBootMessage,
127 startTime,
128 syncStatus,
129 ]);
131 syncStatus = useCallback(async () => {
132 try {
133 if (Nothing.hasInstance(pointCursor) || disabled) {
134 return;
135 }
136 const patp = ob.patp(pointCursor.value);
137 setError(undefined);
139 resetEvents();
141 const response = await client.getShipsByPatp(patp);
143 if (response.status === 404) {
144 setStatus(STATE.MISSING);
145 } else if (!response.ok) {
146 setError(response.statusText);
147 } else {
148 const ship = await response.json();
150 if (ship.status === 'Pending') {
151 setStatus(STATE.PENDING);
152 getEvents();
153 } else if (ship.status === 'Running') {
154 setStatus(STATE.RUNNING);
155 } else {
156 setError(ship);
157 }
158 }
159 } catch (error) {
160 setError(error);
161 }
162 }, [
163 client,
164 getEvents,
165 pointCursor,
166 resetEvents,
167 setError,
168 setStatus,
169 disabled,
170 ]);
172 const hostedShipUrl = useMemo(
173 () => `http://${ob.patp(pointCursor.getOrElse('')).slice(1)}.${domain}`,
174 [pointCursor, domain]
175 );
177 const create = useCallback(
178 async keyfile => {
179 try {
180 if (Nothing.hasInstance(pointCursor) || disabled) {
181 return;
182 }
183 const patp = ob.patp(pointCursor.value).slice(1);
184 setError(undefined);
186 const start = Date.now();
187 setStatus(STATE.PENDING);
188 setBootProgress(0.1);
190 const response = await client.postShips({
191 patp,
192 key: keyfile,
193 debug: true,
194 fake: false,
195 });
197 if (!response.ok) {
198 setError(response.statusText);
199 } else {
200 getEvents();
201 }
202 } catch (error) {
203 setError(error);
204 }
205 },
206 [pointCursor, setError, setStatus, client, getEvents, disabled]
207 );
209 return {
210 error,
211 unknown,
212 missing,
213 running,
214 pending,
215 url: hostedShipUrl,
217 sysEvents,
218 newEvents,
219 runEvents,
221 syncStatus,
222 getEvents,
223 create,
225 bootProgress,
226 bootMessage,
228 disabled,
229 // debugging
230 status,
231 };
234export default useHostingStore;