@darseen/vaze 2.3.0 → 2.4.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) {
@@ -220,8 +305,15 @@ var Files = class extends Base {
220
305
  };
221
306
  }
222
307
  async download(id) {
223
- const url = this.apiUrl(`/api/files/download/${encodeURIComponent(id)}`);
224
- return await this.requestBlob(url);
308
+ return await this.requestBlob(this.downloadUrl(id));
309
+ }
310
+ /** Where `download` reads from; fetch it yourself with `authHeaders()`. */
311
+ downloadUrl(id) {
312
+ return this.apiUrl(`/api/files/download/${encodeURIComponent(id)}`);
313
+ }
314
+ /** A WebP thumbnail of an image file; fetch it with `authHeaders()`. */
315
+ thumbnailUrl(id) {
316
+ return this.apiUrl(`/api/files/thumbnail/${encodeURIComponent(id)}`);
225
317
  }
226
318
  /** Download several files as one zip, with paths below their shared folder. */
227
319
  async downloadMany(ids) {
@@ -344,14 +436,140 @@ var Folders = class extends Base {
344
436
  }
345
437
  };
346
438
 
439
+ // src/keys.ts
440
+ var Keys = class extends Base {
441
+ /** Every key the caller owns, newest first. */
442
+ async getAll() {
443
+ return await this.request(
444
+ "GET",
445
+ this.apiUrl("/api/keys")
446
+ );
447
+ }
448
+ /** The response holds the key's secret, which can't be read again later. */
449
+ async create({ name, scopes, expiresAt }) {
450
+ return await this.request(
451
+ "POST",
452
+ this.apiUrl("/api/keys"),
453
+ {
454
+ name,
455
+ scopes,
456
+ expiresAt: expiresAt instanceof Date ? expiresAt.toISOString() : expiresAt
457
+ }
458
+ );
459
+ }
460
+ async rename(data) {
461
+ return await this.request("PUT", this.apiUrl("/api/keys"), data);
462
+ }
463
+ /** Revokes the key at once for anything using it. */
464
+ async delete(id) {
465
+ return await this.request("DELETE", this.apiUrl("/api/keys"), {
466
+ id
467
+ });
468
+ }
469
+ };
470
+
471
+ // src/settings.ts
472
+ var Settings = class extends Base {
473
+ async get() {
474
+ return await this.request(
475
+ "GET",
476
+ this.apiUrl("/api/settings")
477
+ );
478
+ }
479
+ /** Limits left out keep their current values. */
480
+ async update(changes) {
481
+ return await this.request(
482
+ "PUT",
483
+ this.apiUrl("/api/settings"),
484
+ changes
485
+ );
486
+ }
487
+ /** Gives every existing file this visibility. */
488
+ async applyVisibility(visibility) {
489
+ return await this.request(
490
+ "POST",
491
+ this.apiUrl("/api/settings/apply-visibility"),
492
+ { visibility }
493
+ );
494
+ }
495
+ };
496
+
497
+ // src/users.ts
498
+ var Users = class extends Base {
499
+ /** Every account, oldest first. */
500
+ async getAll() {
501
+ return await this.request(
502
+ "GET",
503
+ this.apiUrl("/api/users")
504
+ );
505
+ }
506
+ async create(options) {
507
+ return await this.request(
508
+ "POST",
509
+ this.apiUrl("/api/users"),
510
+ options
511
+ );
512
+ }
513
+ /** Applies right away, to the user's sessions and API keys alike. */
514
+ async setAccess({
515
+ id,
516
+ role,
517
+ scopes = []
518
+ }) {
519
+ return await this.request(
520
+ "PUT",
521
+ this.apiUrl("/api/users"),
522
+ { id, role, scopes }
523
+ );
524
+ }
525
+ /** Also signs the user out of every device. */
526
+ async resetPassword({
527
+ id,
528
+ password
529
+ }) {
530
+ return await this.request(
531
+ "PUT",
532
+ this.apiUrl("/api/users"),
533
+ { id, password }
534
+ );
535
+ }
536
+ /** A disabled user is signed out and their API keys stop until re-enabled. */
537
+ async setDisabled({
538
+ id,
539
+ disabled
540
+ }) {
541
+ return await this.request(
542
+ "PUT",
543
+ this.apiUrl("/api/users"),
544
+ { id, disabled }
545
+ );
546
+ }
547
+ /** Removes the account, its API keys and its history. Files stay. */
548
+ async delete(id) {
549
+ return await this.request("DELETE", this.apiUrl("/api/users"), {
550
+ id
551
+ });
552
+ }
553
+ };
554
+
347
555
  // src/index.ts
348
556
  var Vaze = class extends Base {
349
557
  files;
350
558
  folders;
559
+ keys;
560
+ account;
561
+ activity;
562
+ users;
563
+ settings;
351
564
  constructor(options = {}) {
352
565
  super(options);
353
566
  this.files = new Files(options);
354
567
  this.folders = new Folders(options);
568
+ this.keys = new Keys(options);
569
+ this.account = new Account(options);
570
+ this.activity = new Activity(options);
571
+ this.users = new Users(options);
572
+ this.settings = new Settings(options);
355
573
  }
356
574
  async health() {
357
575
  return await this.request(
@@ -359,6 +577,35 @@ var Vaze = class extends Base {
359
577
  this.apiUrl("/api/health")
360
578
  );
361
579
  }
580
+ /** The caller's account and the scopes it may use. */
581
+ async me() {
582
+ return await this.request("GET", this.apiUrl("/api/me"));
583
+ }
584
+ /** Dashboard numbers, with each section limited to the caller's scopes. */
585
+ async stats() {
586
+ const { data, error } = await this.request(
587
+ "GET",
588
+ this.apiUrl("/api/stats")
589
+ );
590
+ const files = data?.files;
591
+ return {
592
+ error,
593
+ data: data ? {
594
+ ...data,
595
+ files: files ? {
596
+ ...files,
597
+ largest: constructFileUrls({
598
+ vazeUrl: this.vazeUrl,
599
+ files: files.largest
600
+ }),
601
+ recent: constructFileUrls({
602
+ vazeUrl: this.vazeUrl,
603
+ files: files.recent
604
+ })
605
+ } : null
606
+ } : null
607
+ };
608
+ }
362
609
  };
363
610
  export {
364
611
  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.4.0",
4
4
  "description": "Vaze Self-Hosted file storage and hosting service SDK.",
5
5
  "keywords": [
6
6
  "api",