summaryrefslogtreecommitdiff
path: root/ui/lib/apiServer.js
blob: fa9cbff23832f5d9f5704fbd58c0f4638c595604 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import axios from 'axios';

import * as r from './retry.js';

export const apiServer = axios.create({
  baseURL: '/api/',
});

export async function boot() {
  return apiServer.get('/boot')
    .catch(responseError);
}

export async function setup(name, password) {
  return await apiServer.post('/setup', { name, password })
    .catch(responseError);
}

export async function logIn(name, password) {
  return await apiServer.post('/auth/login', { name, password })
    .catch(responseError);
}

export async function logOut() {
  return await apiServer.post('/auth/logout', {})
    .catch(responseError);
}

export async function changePassword(password, to) {
  return await apiServer.post('/password', { password, to })
    .catch(responseError);
}

export async function createConversation(name) {
  return await apiServer.post('/conversations', { name })
    .catch(responseError);
}

export async function sendToConversation(conversationId, body) {
  return await apiServer.post(`/conversations/${conversationId}`, { body })
    .catch(responseError);
}

export async function deleteMessage(messageId) {
  return await apiServer.delete(`/messages/${messageId}`, {})
    .catch(responseError);
}

export async function createInvite() {
  return await apiServer.post(`/invite`, {})
    .catch(responseError);
}

export async function getInvite(inviteId) {
  return await apiServer.get(`/invite/${inviteId}`)
    .catch(responseError);
}

export async function acceptInvite(inviteId, name, password) {
  return apiServer.post(`/invite/${inviteId}`, { name, password })
    .catch(responseError);
}

export async function createPushSubscription(data) {
  return await apiServer.post("/push", { data })
    .catch(responseError);
}

export async function deletePushSubscription(data) {
  return await apiServer.delete("/push", { data })
    .catch(responseError);
}

export async function notifyMe() {
  // TODO: this is the big "missing on the server" endpoint we need to make a
  // minimal notifications setup.
  return await apiServer.post('/notifyMe')
    .catch(responseError);
}

export function subscribeToEvents(resumePoint) {
  const eventsUrl = apiServer.getUri({
    url: '/events',
    params: {
      resume_point: resumePoint,
    },
  });
  return new EventSource(eventsUrl);
}

export class LoggedOut extends Error {}

export class SetupRequired extends Error {}

export async function retry(op) {
  return await r.retry(op, isRetryable, r.timedDelay(5000));
}

function responseError(err) {
  if (err.response) {
    switch (err.response.status) {
      case 401:
        throw new LoggedOut();
      case 503:
        throw new SetupRequired();
    }
  }
  throw err;
}

function isRetryable(err) {
  // See <https://axios-http.com/docs/handling_errors> for a breakdown of this logic.

  // Any error with a response is non-retryable. The server responded; we get to act on that
  // response. We don't do anything special for 5xx-series responses yet, but they might one day be
  // retryable too.
  if (err.response) {
    return false;
  }

  // Any error with no response and a request is probably from the network side of things, and is
  // retryable.
  if (err.request) {
    return true;
  }

  // Anything with neither is unexpected enough that we should not try it.
  return false;
}