not-manage 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,384 @@
1
+ const { saveTokenSet } = require("./store");
2
+
3
+ function createError(message, responseText) {
4
+ const suffix = responseText ? ` ${responseText}` : "";
5
+ return new Error(`${message}.${suffix}`.trim());
6
+ }
7
+
8
+ async function postForm(url, formFields, headers = {}) {
9
+ const response = await fetch(url, {
10
+ method: "POST",
11
+ headers: {
12
+ "content-type": "application/x-www-form-urlencoded",
13
+ ...headers,
14
+ },
15
+ body: new URLSearchParams(formFields).toString(),
16
+ });
17
+
18
+ const text = await response.text();
19
+ let payload = null;
20
+
21
+ if (text) {
22
+ try {
23
+ payload = JSON.parse(text);
24
+ } catch (_error) {
25
+ payload = text;
26
+ }
27
+ }
28
+
29
+ if (!response.ok) {
30
+ throw createError(
31
+ `HTTP ${response.status} from ${url}`,
32
+ typeof payload === "string" ? payload : JSON.stringify(payload)
33
+ );
34
+ }
35
+
36
+ return payload;
37
+ }
38
+
39
+ async function getJson(url, headers = {}) {
40
+ const response = await fetch(url, {
41
+ headers,
42
+ });
43
+
44
+ const text = await response.text();
45
+ let payload = null;
46
+
47
+ if (text) {
48
+ try {
49
+ payload = JSON.parse(text);
50
+ } catch (_error) {
51
+ payload = text;
52
+ }
53
+ }
54
+
55
+ if (!response.ok) {
56
+ throw createError(
57
+ `HTTP ${response.status} from ${url}`,
58
+ typeof payload === "string" ? payload : JSON.stringify(payload)
59
+ );
60
+ }
61
+
62
+ return payload;
63
+ }
64
+
65
+ async function postJson(url, body, headers = {}) {
66
+ const response = await fetch(url, {
67
+ method: "POST",
68
+ headers: {
69
+ accept: "application/json",
70
+ "content-type": "application/json",
71
+ ...headers,
72
+ },
73
+ body: JSON.stringify(body),
74
+ });
75
+
76
+ const text = await response.text();
77
+ let payload = null;
78
+
79
+ if (text) {
80
+ try {
81
+ payload = JSON.parse(text);
82
+ } catch (_error) {
83
+ payload = text;
84
+ }
85
+ }
86
+
87
+ if (!response.ok) {
88
+ throw createError(
89
+ `HTTP ${response.status} from ${url}`,
90
+ typeof payload === "string" ? payload : JSON.stringify(payload)
91
+ );
92
+ }
93
+
94
+ return payload;
95
+ }
96
+
97
+ function authBaseUrl(config) {
98
+ return `https://${config.host}`;
99
+ }
100
+
101
+ function apiBaseUrl(config) {
102
+ return `${authBaseUrl(config)}/api/v4`;
103
+ }
104
+
105
+ function parseTrustedApiUrl(config, url, expectedPathPrefix = "/api/v4/") {
106
+ let parsed;
107
+ try {
108
+ parsed = new URL(url);
109
+ } catch (_error) {
110
+ throw new Error(`Received an invalid URL from Clio: ${url}`);
111
+ }
112
+
113
+ if (parsed.protocol !== "https:") {
114
+ throw new Error(`Refusing to call a non-HTTPS URL returned by Clio: ${url}`);
115
+ }
116
+
117
+ if (parsed.hostname !== config.host) {
118
+ throw new Error(
119
+ `Refusing to send Clio credentials to an unexpected host: ${parsed.hostname}`
120
+ );
121
+ }
122
+
123
+ if (parsed.username || parsed.password) {
124
+ throw new Error("Refusing to use a Clio URL that contains embedded credentials.");
125
+ }
126
+
127
+ if (expectedPathPrefix && !parsed.pathname.startsWith(expectedPathPrefix)) {
128
+ throw new Error(
129
+ `Refusing to call an unexpected Clio API path: ${parsed.pathname}`
130
+ );
131
+ }
132
+
133
+ return parsed.toString();
134
+ }
135
+
136
+ function buildUrlWithQuery(baseUrl, query = {}) {
137
+ const url = new URL(baseUrl);
138
+
139
+ Object.entries(query).forEach(([key, value]) => {
140
+ if (value === undefined || value === null || value === "") {
141
+ return;
142
+ }
143
+
144
+ if (Array.isArray(value)) {
145
+ value.forEach((item) => {
146
+ if (item !== undefined && item !== null && item !== "") {
147
+ url.searchParams.append(key, String(item));
148
+ }
149
+ });
150
+ return;
151
+ }
152
+
153
+ url.searchParams.set(key, String(value));
154
+ });
155
+
156
+ return url.toString();
157
+ }
158
+
159
+ function tokenUrl(config) {
160
+ return `${authBaseUrl(config)}/oauth/token`;
161
+ }
162
+
163
+ function authorizeUrl(config, state) {
164
+ const url = new URL(`${authBaseUrl(config)}/oauth/authorize`);
165
+ url.searchParams.set("response_type", "code");
166
+ url.searchParams.set("client_id", config.clientId);
167
+ url.searchParams.set("redirect_uri", config.redirectUri);
168
+ url.searchParams.set("state", state);
169
+ return url.toString();
170
+ }
171
+
172
+ async function exchangeAuthorizationCode(config, code) {
173
+ return postForm(tokenUrl(config), {
174
+ grant_type: "authorization_code",
175
+ code,
176
+ client_id: config.clientId,
177
+ client_secret: config.clientSecret,
178
+ redirect_uri: config.redirectUri,
179
+ });
180
+ }
181
+
182
+ async function refreshAccessToken(config, tokenSet) {
183
+ if (!tokenSet.refreshToken) {
184
+ throw new Error("Missing refresh token. Run `not-manage auth login`.");
185
+ }
186
+
187
+ const refreshed = await postForm(tokenUrl(config), {
188
+ grant_type: "refresh_token",
189
+ refresh_token: tokenSet.refreshToken,
190
+ client_id: config.clientId,
191
+ client_secret: config.clientSecret,
192
+ });
193
+
194
+ return saveTokenSet(refreshed, tokenSet);
195
+ }
196
+
197
+ async function getValidAccessToken(config, tokenSet) {
198
+ if (!tokenSet || !tokenSet.accessToken) {
199
+ throw new Error("You are not logged in. Run `not-manage auth login`.");
200
+ }
201
+
202
+ const now = Math.floor(Date.now() / 1000);
203
+ const expiresSoon =
204
+ tokenSet.expiresAt && Number(tokenSet.expiresAt) <= now + 60;
205
+
206
+ if (!expiresSoon) {
207
+ return tokenSet.accessToken;
208
+ }
209
+
210
+ const refreshed = await refreshAccessToken(config, tokenSet);
211
+ return refreshed.accessToken;
212
+ }
213
+
214
+ async function fetchWhoAmI(config, accessToken) {
215
+ const url = `${apiBaseUrl(config)}/users/who_am_i`;
216
+ return getJson(url, {
217
+ authorization: `Bearer ${accessToken}`,
218
+ });
219
+ }
220
+
221
+ async function deauthorize(config, accessToken) {
222
+ const url = `${authBaseUrl(config)}/oauth/deauthorize`;
223
+ return postForm(
224
+ url,
225
+ {
226
+ token: accessToken,
227
+ client_id: config.clientId,
228
+ client_secret: config.clientSecret,
229
+ },
230
+ {
231
+ authorization: `Bearer ${accessToken}`,
232
+ }
233
+ );
234
+ }
235
+
236
+ function resourceCollectionUrl(config, resourcePath, query = {}) {
237
+ return buildUrlWithQuery(`${apiBaseUrl(config)}/${resourcePath}.json`, query);
238
+ }
239
+
240
+ function resourceItemUrl(config, resourcePath, id, query = {}) {
241
+ return buildUrlWithQuery(
242
+ `${apiBaseUrl(config)}/${resourcePath}/${encodeURIComponent(String(id))}.json`,
243
+ query
244
+ );
245
+ }
246
+
247
+ async function fetchResourcePage(
248
+ config,
249
+ accessToken,
250
+ resourcePath,
251
+ query = {},
252
+ nextPageUrl = null
253
+ ) {
254
+ const url = nextPageUrl
255
+ ? parseTrustedApiUrl(config, nextPageUrl)
256
+ : resourceCollectionUrl(config, resourcePath, query);
257
+ return getJson(url, {
258
+ authorization: `Bearer ${accessToken}`,
259
+ });
260
+ }
261
+
262
+ async function fetchResourceById(config, accessToken, resourcePath, id, query = {}) {
263
+ const url = resourceItemUrl(config, resourcePath, id, query);
264
+ return getJson(url, {
265
+ authorization: `Bearer ${accessToken}`,
266
+ });
267
+ }
268
+
269
+ async function createResource(config, accessToken, resourcePath, data, query = {}) {
270
+ const url = resourceCollectionUrl(config, resourcePath, query);
271
+ return postJson(
272
+ url,
273
+ { data },
274
+ {
275
+ authorization: `Bearer ${accessToken}`,
276
+ }
277
+ );
278
+ }
279
+
280
+ async function fetchContactsPage(config, accessToken, query = {}, nextPageUrl = null) {
281
+ return fetchResourcePage(config, accessToken, "contacts", query, nextPageUrl);
282
+ }
283
+
284
+ async function fetchContact(config, accessToken, id, query = {}) {
285
+ return fetchResourceById(config, accessToken, "contacts", id, query);
286
+ }
287
+
288
+ async function fetchMattersPage(config, accessToken, query = {}, nextPageUrl = null) {
289
+ return fetchResourcePage(config, accessToken, "matters", query, nextPageUrl);
290
+ }
291
+
292
+ async function fetchMatter(config, accessToken, id, query = {}) {
293
+ return fetchResourceById(config, accessToken, "matters", id, query);
294
+ }
295
+
296
+ async function fetchBillsPage(config, accessToken, query = {}, nextPageUrl = null) {
297
+ return fetchResourcePage(config, accessToken, "bills", query, nextPageUrl);
298
+ }
299
+
300
+ async function fetchBill(config, accessToken, id, query = {}) {
301
+ return fetchResourceById(config, accessToken, "bills", id, query);
302
+ }
303
+
304
+ async function fetchUsersPage(config, accessToken, query = {}, nextPageUrl = null) {
305
+ return fetchResourcePage(config, accessToken, "users", query, nextPageUrl);
306
+ }
307
+
308
+ async function fetchUser(config, accessToken, id, query = {}) {
309
+ return fetchResourceById(config, accessToken, "users", id, query);
310
+ }
311
+
312
+ async function fetchPracticeAreasPage(config, accessToken, query = {}, nextPageUrl = null) {
313
+ return fetchResourcePage(config, accessToken, "practice_areas", query, nextPageUrl);
314
+ }
315
+
316
+ async function fetchPracticeArea(config, accessToken, id, query = {}) {
317
+ return fetchResourceById(config, accessToken, "practice_areas", id, query);
318
+ }
319
+
320
+ async function fetchActivitiesPage(config, accessToken, query = {}, nextPageUrl = null) {
321
+ return fetchResourcePage(config, accessToken, "activities", query, nextPageUrl);
322
+ }
323
+
324
+ async function fetchActivity(config, accessToken, id, query = {}) {
325
+ return fetchResourceById(config, accessToken, "activities", id, query);
326
+ }
327
+
328
+ async function createActivity(config, accessToken, data, query = {}) {
329
+ return createResource(config, accessToken, "activities", data, query);
330
+ }
331
+
332
+ async function fetchTasksPage(config, accessToken, query = {}, nextPageUrl = null) {
333
+ return fetchResourcePage(config, accessToken, "tasks", query, nextPageUrl);
334
+ }
335
+
336
+ async function fetchTask(config, accessToken, id, query = {}) {
337
+ return fetchResourceById(config, accessToken, "tasks", id, query);
338
+ }
339
+
340
+ async function fetchBillableMattersPage(
341
+ config,
342
+ accessToken,
343
+ query = {},
344
+ nextPageUrl = null
345
+ ) {
346
+ return fetchResourcePage(config, accessToken, "billable_matters", query, nextPageUrl);
347
+ }
348
+
349
+ async function fetchBillableClientsPage(
350
+ config,
351
+ accessToken,
352
+ query = {},
353
+ nextPageUrl = null
354
+ ) {
355
+ return fetchResourcePage(config, accessToken, "billable_clients", query, nextPageUrl);
356
+ }
357
+
358
+ module.exports = {
359
+ authorizeUrl,
360
+ createActivity,
361
+ deauthorize,
362
+ exchangeAuthorizationCode,
363
+ fetchActivitiesPage,
364
+ fetchActivity,
365
+ fetchTask,
366
+ fetchTasksPage,
367
+ fetchBill,
368
+ fetchBillableClientsPage,
369
+ fetchBillableMattersPage,
370
+ fetchBillsPage,
371
+ fetchContact,
372
+ fetchContactsPage,
373
+ fetchMatter,
374
+ fetchMattersPage,
375
+ fetchPracticeArea,
376
+ fetchPracticeAreasPage,
377
+ fetchUser,
378
+ fetchUsersPage,
379
+ fetchWhoAmI,
380
+ getValidAccessToken,
381
+ __private: {
382
+ parseTrustedApiUrl,
383
+ },
384
+ };