@darseen/vaze 2.3.0 → 2.5.0

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.
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // src/utils/index.ts
2
+ function env(name) {
3
+ return typeof process === "undefined" ? void 0 : process.env?.[name];
4
+ }
2
5
  function constructFileUrls({
3
6
  vazeUrl,
4
7
  files
@@ -25,21 +28,26 @@ function parseContentDispositionFilename(header) {
25
28
  // src/base.ts
26
29
  var Base = class {
27
30
  apiKey;
31
+ headers;
32
+ // wrapped so the global fetch is never called with this class as `this`
33
+ fetch;
28
34
  vazeUrl;
29
35
  constructor(options = {}) {
30
- const vazeUrl = options.vazeUrl || process.env.VAZE_URL;
31
- const apiKey = options.apiKey || process.env.VAZE_API_KEY;
36
+ const vazeUrl = options.vazeUrl || env("VAZE_URL");
37
+ const apiKey = options.apiKey || env("VAZE_API_KEY");
32
38
  if (!vazeUrl) {
33
39
  throw new Error(
34
40
  "You must provide a Vaze URL or set the VAZE_URL environment variable."
35
41
  );
36
42
  }
37
- if (!apiKey) {
43
+ if (!apiKey && !options.headers) {
38
44
  throw new Error(
39
- "You must provide an API key or set the VAZE_API_KEY environment variable."
45
+ "You must provide an API key or headers, or set the VAZE_API_KEY environment variable."
40
46
  );
41
47
  }
42
48
  this.apiKey = apiKey;
49
+ this.headers = options.headers;
50
+ this.fetch = options.fetch ?? ((input, init) => fetch(input, init));
43
51
  this.vazeUrl = vazeUrl.endsWith("/") ? vazeUrl : `${vazeUrl}/`;
44
52
  }
45
53
  apiUrl(path, params) {
@@ -51,35 +59,45 @@ var Base = class {
51
59
  }
52
60
  return url.toString();
53
61
  }
62
+ /** The headers every request carries, for clients fetching an SDK URL themselves. */
63
+ async authHeaders() {
64
+ const headers = typeof this.headers === "function" ? await this.headers() : this.headers;
65
+ return this.apiKey ? { ...headers, "API-Key": this.apiKey } : { ...headers };
66
+ }
54
67
  async request(method, url, body, headers) {
55
68
  try {
56
69
  const fetchHeaders = {
57
- "API-Key": this.apiKey,
70
+ ...await this.authHeaders(),
58
71
  ...headers
59
72
  };
60
73
  const isFormData = body instanceof FormData;
61
74
  if (body !== void 0 && !isFormData && !fetchHeaders["Content-Type"]) {
62
75
  fetchHeaders["Content-Type"] = "application/json";
63
76
  }
64
- const response = await fetch(url, {
77
+ const response = await this.fetch(url, {
65
78
  method,
66
79
  headers: fetchHeaders,
67
80
  body: isFormData ? body : body !== void 0 ? JSON.stringify(body) : null
68
81
  });
82
+ const { status } = response;
69
83
  const contentType = response.headers.get("content-type") ?? "";
70
84
  if (!contentType.includes("application/json")) {
71
85
  return {
72
86
  data: null,
73
87
  error: {
74
- message: `Unexpected non-JSON response from server (status ${response.status}).`
88
+ message: `Unexpected non-JSON response from server (status ${status}).`,
89
+ status
75
90
  }
76
91
  };
77
92
  }
78
93
  const result = await response.json();
79
- if (!response.ok && !result.error) {
94
+ if (!response.ok) {
80
95
  return {
81
96
  data: null,
82
- error: { message: `Request failed with status ${response.status}.` }
97
+ error: {
98
+ message: result.error?.message ?? `Request failed with status ${status}.`,
99
+ status
100
+ }
83
101
  };
84
102
  }
85
103
  return result;
@@ -94,22 +112,25 @@ var Base = class {
94
112
  }
95
113
  async requestBlob(url, method = "GET", body) {
96
114
  try {
97
- const headers = { "API-Key": this.apiKey };
115
+ const headers = await this.authHeaders();
98
116
  if (body !== void 0) headers["Content-Type"] = "application/json";
99
- const response = await fetch(url, {
117
+ const response = await this.fetch(url, {
100
118
  method,
101
119
  headers,
102
120
  body: body !== void 0 ? JSON.stringify(body) : null
103
121
  });
122
+ const { status } = response;
104
123
  const contentType = response.headers.get("content-type") ?? "";
105
124
  if (!response.ok) {
106
125
  if (contentType.includes("application/json")) {
107
126
  const result = await response.json();
108
- if (result.error) return { data: null, error: result.error };
127
+ if (result.error) {
128
+ return { data: null, error: { ...result.error, status } };
129
+ }
109
130
  }
110
131
  return {
111
132
  data: null,
112
- error: { message: `Request failed with status ${response.status}.` }
133
+ error: { message: `Request failed with status ${status}.`, status }
113
134
  };
114
135
  }
115
136
  return {
@@ -133,6 +154,70 @@ var Base = class {
133
154
  }
134
155
  };
135
156
 
157
+ // src/account.ts
158
+ var Account = class extends Base {
159
+ /** Changes the display name, the sign-in email, or both. */
160
+ async update(changes) {
161
+ return await this.request("PUT", this.apiUrl("/api/me"), changes);
162
+ }
163
+ async changePassword({
164
+ currentPassword,
165
+ newPassword,
166
+ revokeOtherSessions = false
167
+ }) {
168
+ return await this.request("PUT", this.apiUrl("/api/me/password"), {
169
+ currentPassword,
170
+ newPassword,
171
+ revokeOtherSessions
172
+ });
173
+ }
174
+ /** Every device signed in to the account, the current one first. */
175
+ async getSessions() {
176
+ return await this.request(
177
+ "GET",
178
+ this.apiUrl("/api/me/sessions")
179
+ );
180
+ }
181
+ /** Signs out another device. The current session ends with a sign-out instead. */
182
+ async revokeSession(id) {
183
+ return await this.request("DELETE", this.apiUrl("/api/me/sessions"), {
184
+ id
185
+ });
186
+ }
187
+ /** Signs out every device but the current one. */
188
+ async revokeOtherSessions() {
189
+ return await this.request("DELETE", this.apiUrl("/api/me/sessions"), {
190
+ others: true
191
+ });
192
+ }
193
+ };
194
+
195
+ // src/activity.ts
196
+ var Activity = class extends Base {
197
+ /** The most recent entries, newest first. */
198
+ async getAll() {
199
+ return await this.request(
200
+ "GET",
201
+ this.apiUrl("/api/activity")
202
+ );
203
+ }
204
+ async delete(ids) {
205
+ return await this.request(
206
+ "DELETE",
207
+ this.apiUrl("/api/activity"),
208
+ { ids }
209
+ );
210
+ }
211
+ /** Deletes every entry. Files, keys and accounts are not affected. */
212
+ async clear() {
213
+ return await this.request(
214
+ "DELETE",
215
+ this.apiUrl("/api/activity"),
216
+ { all: true }
217
+ );
218
+ }
219
+ };
220
+
136
221
  // src/files.ts
137
222
  var Files = class extends Base {
138
223
  async getAll(options) {
@@ -201,7 +286,7 @@ var Files = class extends Base {
201
286
  }
202
287
  async upload(data) {
203
288
  const { files, folder, visibility } = data;
204
- const url = this.apiUrl("/api/files");
289
+ const url = this.uploadUrl();
205
290
  const formData = new FormData();
206
291
  if (folder) formData.append("folder", folder);
207
292
  if (visibility) formData.append("visibility", visibility);
@@ -219,15 +304,36 @@ var Files = class extends Base {
219
304
  } : null
220
305
  };
221
306
  }
307
+ /** Where `upload` posts its form, for upload managers that report progress. */
308
+ uploadUrl() {
309
+ return this.apiUrl("/api/files");
310
+ }
311
+ /** Record uploads canceled before they were sent, in the activity history. */
312
+ async reportCanceled(names) {
313
+ return await this.request(
314
+ "POST",
315
+ this.apiUrl("/api/files/canceled"),
316
+ { names }
317
+ );
318
+ }
222
319
  async download(id) {
223
- const url = this.apiUrl(`/api/files/download/${encodeURIComponent(id)}`);
224
- return await this.requestBlob(url);
320
+ return await this.requestBlob(this.downloadUrl(id));
321
+ }
322
+ /** Where `download` reads from; fetch it yourself with `authHeaders()`. */
323
+ downloadUrl(id) {
324
+ return this.apiUrl(`/api/files/download/${encodeURIComponent(id)}`);
325
+ }
326
+ /** A WebP thumbnail of an image file; fetch it with `authHeaders()`. */
327
+ thumbnailUrl(id) {
328
+ return this.apiUrl(`/api/files/thumbnail/${encodeURIComponent(id)}`);
225
329
  }
226
330
  /** Download several files as one zip, with paths below their shared folder. */
227
331
  async downloadMany(ids) {
228
- return await this.requestBlob(this.apiUrl("/api/files/download"), "POST", {
229
- ids
230
- });
332
+ return await this.requestBlob(this.downloadManyUrl(), "POST", { ids });
333
+ }
334
+ /** Where `downloadMany` posts `{ ids }`; stream it yourself with `authHeaders()`. */
335
+ downloadManyUrl() {
336
+ return this.apiUrl("/api/files/download");
231
337
  }
232
338
  async rename(data) {
233
339
  return await this.request("PUT", this.apiUrl("/api/files"), data);
@@ -311,9 +417,11 @@ var Folders = class extends Base {
311
417
  }
312
418
  /** Download a folder and everything below it as a zip; the root by default. */
313
419
  async download(options) {
314
- return await this.requestBlob(
315
- this.apiUrl("/api/folders/download", options)
316
- );
420
+ return await this.requestBlob(this.downloadUrl(options));
421
+ }
422
+ /** Where `download` reads from; fetch it yourself with `authHeaders()`. */
423
+ downloadUrl(options) {
424
+ return this.apiUrl("/api/folders/download", options);
317
425
  }
318
426
  async create(folder) {
319
427
  return await this.request(
@@ -344,14 +452,140 @@ var Folders = class extends Base {
344
452
  }
345
453
  };
346
454
 
455
+ // src/keys.ts
456
+ var Keys = class extends Base {
457
+ /** Every key the caller owns, newest first. */
458
+ async getAll() {
459
+ return await this.request(
460
+ "GET",
461
+ this.apiUrl("/api/keys")
462
+ );
463
+ }
464
+ /** The response holds the key's secret, which can't be read again later. */
465
+ async create({ name, scopes, expiresAt }) {
466
+ return await this.request(
467
+ "POST",
468
+ this.apiUrl("/api/keys"),
469
+ {
470
+ name,
471
+ scopes,
472
+ expiresAt: expiresAt instanceof Date ? expiresAt.toISOString() : expiresAt
473
+ }
474
+ );
475
+ }
476
+ async rename(data) {
477
+ return await this.request("PUT", this.apiUrl("/api/keys"), data);
478
+ }
479
+ /** Revokes the key at once for anything using it. */
480
+ async delete(id) {
481
+ return await this.request("DELETE", this.apiUrl("/api/keys"), {
482
+ id
483
+ });
484
+ }
485
+ };
486
+
487
+ // src/settings.ts
488
+ var Settings = class extends Base {
489
+ async get() {
490
+ return await this.request(
491
+ "GET",
492
+ this.apiUrl("/api/settings")
493
+ );
494
+ }
495
+ /** Limits left out keep their current values. */
496
+ async update(changes) {
497
+ return await this.request(
498
+ "PUT",
499
+ this.apiUrl("/api/settings"),
500
+ changes
501
+ );
502
+ }
503
+ /** Gives every existing file this visibility. */
504
+ async applyVisibility(visibility) {
505
+ return await this.request(
506
+ "POST",
507
+ this.apiUrl("/api/settings/apply-visibility"),
508
+ { visibility }
509
+ );
510
+ }
511
+ };
512
+
513
+ // src/users.ts
514
+ var Users = class extends Base {
515
+ /** Every account, oldest first. */
516
+ async getAll() {
517
+ return await this.request(
518
+ "GET",
519
+ this.apiUrl("/api/users")
520
+ );
521
+ }
522
+ async create(options) {
523
+ return await this.request(
524
+ "POST",
525
+ this.apiUrl("/api/users"),
526
+ options
527
+ );
528
+ }
529
+ /** Applies right away, to the user's sessions and API keys alike. */
530
+ async setAccess({
531
+ id,
532
+ role,
533
+ scopes = []
534
+ }) {
535
+ return await this.request(
536
+ "PUT",
537
+ this.apiUrl("/api/users"),
538
+ { id, role, scopes }
539
+ );
540
+ }
541
+ /** Also signs the user out of every device. */
542
+ async resetPassword({
543
+ id,
544
+ password
545
+ }) {
546
+ return await this.request(
547
+ "PUT",
548
+ this.apiUrl("/api/users"),
549
+ { id, password }
550
+ );
551
+ }
552
+ /** A disabled user is signed out and their API keys stop until re-enabled. */
553
+ async setDisabled({
554
+ id,
555
+ disabled
556
+ }) {
557
+ return await this.request(
558
+ "PUT",
559
+ this.apiUrl("/api/users"),
560
+ { id, disabled }
561
+ );
562
+ }
563
+ /** Removes the account, its API keys and its history. Files stay. */
564
+ async delete(id) {
565
+ return await this.request("DELETE", this.apiUrl("/api/users"), {
566
+ id
567
+ });
568
+ }
569
+ };
570
+
347
571
  // src/index.ts
348
572
  var Vaze = class extends Base {
349
573
  files;
350
574
  folders;
575
+ keys;
576
+ account;
577
+ activity;
578
+ users;
579
+ settings;
351
580
  constructor(options = {}) {
352
581
  super(options);
353
582
  this.files = new Files(options);
354
583
  this.folders = new Folders(options);
584
+ this.keys = new Keys(options);
585
+ this.account = new Account(options);
586
+ this.activity = new Activity(options);
587
+ this.users = new Users(options);
588
+ this.settings = new Settings(options);
355
589
  }
356
590
  async health() {
357
591
  return await this.request(
@@ -359,6 +593,42 @@ var Vaze = class extends Base {
359
593
  this.apiUrl("/api/health")
360
594
  );
361
595
  }
596
+ /** Whether the instance still needs its first account, which becomes the admin. */
597
+ async setupStatus() {
598
+ return await this.request(
599
+ "GET",
600
+ this.apiUrl("/api/setup")
601
+ );
602
+ }
603
+ /** The caller's account and the scopes it may use. */
604
+ async me() {
605
+ return await this.request("GET", this.apiUrl("/api/me"));
606
+ }
607
+ /** Dashboard numbers, with each section limited to the caller's scopes. */
608
+ async stats() {
609
+ const { data, error } = await this.request(
610
+ "GET",
611
+ this.apiUrl("/api/stats")
612
+ );
613
+ const files = data?.files;
614
+ return {
615
+ error,
616
+ data: data ? {
617
+ ...data,
618
+ files: files ? {
619
+ ...files,
620
+ largest: constructFileUrls({
621
+ vazeUrl: this.vazeUrl,
622
+ files: files.largest
623
+ }),
624
+ recent: constructFileUrls({
625
+ vazeUrl: this.vazeUrl,
626
+ files: files.recent
627
+ })
628
+ } : null
629
+ } : null
630
+ };
631
+ }
362
632
  };
363
633
  export {
364
634
  Vaze as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darseen/vaze",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Vaze Self-Hosted file storage and hosting service SDK.",
5
5
  "keywords": [
6
6
  "api",