on 4c8c607 help sign in
bridge/src/store/lib/useHostingStore.js
244 lines 6.1 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 newSource = useCallback(
64 (url, retry = 0) => {
65 let source = new EventSource(url);
67 source.addEventListener('sys', event => {
68 const sysEvent = JSON.parse(event.data);
69 const time = new Date(sysEvent.object.firstTimestamp).getTime();
70 if (time > startTime) {
71 setSysEvents(previous => [...previous, sysEvent]);
72 }
73 });
75 source.addEventListener('new', event => {
76 if (event.data !== '') {
77 setNewEvents(previous => [...previous, event.data]);
78 }
79 if (event.data === '1-b') {
80 setBootMessage('Compiling Hoon');
81 setBootProgress(0.2);
82 } else if (event.data === '1-c') {
83 setBootProgress(0.25);
84 } else if (event.data === '%arvo-assembly') {
85 setBootMessage('Compiling Urbit OS');
86 setBootProgress(0.3);
87 } else if (event.data.startsWith('[%vane %')) {
88 const vane = getVaneName(event.data.slice(8, 9));
89 setBootMessage(`Compiling vane: ${vane}`);
90 setBootProgress(0.3 + 0.05 * getVaneNumber(event.data.slice(8, 9)));
91 } else if (event.data === 'pier: boot complete') {
92 setBootMessage('Ship starting');
93 setBootProgress(0.85);
94 }
95 });
97 source.addEventListener('run', event => {
98 if (event.data === 'goad: recompiling all apps') {
99 setBootProgress(1.0);
100 setBootMessage('Ship Launched');
101 syncStatus();
102 }
103 if (event.data !== '') {
104 setRunEvents(previous => [...previous, event.data]);
105 }
106 });
108 source.addEventListener(
109 'error',
110 error => {
111 if (source.readyState === EventSource.CLOSED) {
112 if (retry < 5) {
113 newSource(url, retry + 1);
114 return;
115 }
116 setError(error);
117 }
118 },
119 false
120 );
121 },
122 [setError, startTime, syncStatus]
123 );
125 const getEvents = useCallback(() => {
126 try {
127 if (Nothing.hasInstance(pointCursor) || disabled) {
128 return;
129 }
130 const patp = ob.patp(pointCursor.value);
131 setError(undefined);
133 resetEvents();
135 const url = client.getShipsByPatpUrl(patp) + '/events';
136 newSource(url);
137 } catch (error) {
138 setError(error);
139 }
140 }, [setError, disabled, resetEvents, client, pointCursor, newSource]);
142 syncStatus = useCallback(async () => {
143 try {
144 if (Nothing.hasInstance(pointCursor) || disabled) {
145 return;
146 }
147 const patp = ob.patp(pointCursor.value);
148 setError(undefined);
150 resetEvents();
152 const response = await client.getShipsByPatp(patp);
154 if (response.status === 404) {
155 setStatus(STATE.MISSING);
156 } else if (!response.ok) {
157 setError(response.statusText);
158 } else {
159 const ship = await response.json();
161 if (ship.status === 'Pending') {
162 setStatus(STATE.PENDING);
163 getEvents();
164 } else if (ship.status === 'Running') {
165 setStatus(STATE.RUNNING);
166 } else {
167 setError(ship);
168 }
169 }
170 } catch (error) {
171 setError(error);
172 }
173 }, [
174 client,
175 getEvents,
176 pointCursor,
177 resetEvents,
178 setError,
179 setStatus,
180 disabled,
181 ]);
183 const hostedShipUrl = useMemo(
184 () => `http://${ob.patp(pointCursor.getOrElse('')).slice(1)}.${domain}`,
185 [pointCursor, domain]
186 );
188 const create = useCallback(
189 async keyfile => {
190 try {
191 if (Nothing.hasInstance(pointCursor) || disabled) {
192 return;
193 }
194 const patp = ob.patp(pointCursor.value).slice(1);
195 setError(undefined);
197 setStatus(STATE.PENDING);
198 setBootProgress(0.1);
200 const response = await client.postShips({
201 patp,
202 key: keyfile,
203 debug: true,
204 fake: false,
205 });
207 if (!response.ok) {
208 setError(response.statusText);
209 } else {
210 getEvents();
211 }
212 } catch (error) {
213 setError(error);
214 }
215 },
216 [pointCursor, setError, setStatus, client, getEvents, disabled]
217 );
219 return {
220 error,
221 unknown,
222 missing,
223 running,
224 pending,
225 url: hostedShipUrl,
227 sysEvents,
228 newEvents,
229 runEvents,
231 syncStatus,
232 getEvents,
233 create,
235 bootProgress,
236 bootMessage,
238 disabled,
239 // debugging
240 status,
241 };
244export default useHostingStore;