@dropby/server 0.4.0 → 0.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/README.md +36 -9
- package/dist/index.d.ts +34 -4
- package/dist/index.js +87 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -94,6 +94,28 @@ workflows. Ids are plain strings: one in a URL path is checked before anything i
|
|
|
94
94
|
sent (`invalid_input`), and one in a request body is checked by the API (a 400
|
|
95
95
|
naming the field).
|
|
96
96
|
|
|
97
|
+
## Asks, chat and ideas
|
|
98
|
+
|
|
99
|
+
Ask a user a question, reply to their conversation, or tell them an idea is planned:
|
|
100
|
+
|
|
101
|
+
<!-- snippet: import type { DropByServer } from "@dropby/server"; declare const dropby: DropByServer; declare const threadId: string; declare const ideaId: string; -->
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await dropby.asks.create({
|
|
105
|
+
title: "how's the new inbox?",
|
|
106
|
+
fields: [{ type: "long_text", label: "what would make it better?", required: true }],
|
|
107
|
+
targets: [{ type: "user", id: "user_123" }],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await dropby.chat.reply(threadId, { text: "fixed in today's release" });
|
|
111
|
+
|
|
112
|
+
await dropby.ideas.update(ideaId, { status: "planned", teamResponse: { text: "on the roadmap" } });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The ask shows in that user's widget, or wherever your own UI lists pending asks.
|
|
116
|
+
Creating one needs a secret key with `ask:create`, replying `chat:write`, and
|
|
117
|
+
updating an idea `ideas:write`.
|
|
118
|
+
|
|
97
119
|
## Feature flags
|
|
98
120
|
|
|
99
121
|
`config.evaluate` resolves every flag for a user and account, as the browser
|
|
@@ -123,7 +145,9 @@ Every call retries by itself after a network error, a timeout, a 408, a 429 or
|
|
|
123
145
|
a 5xx: twice by default, waiting up to half a second, then up to a second (set
|
|
124
146
|
`maxRetries`, 0 to 10). A `Retry-After` of up to 10 seconds is waited out; a
|
|
125
147
|
longer one ends the call at once, and the error's `retryAfter` says how long the
|
|
126
|
-
API asked for. `timeoutMs` (30 seconds by default) bounds each attempt.
|
|
148
|
+
API asked for. `timeoutMs` (30 seconds by default) bounds each attempt. When a
|
|
149
|
+
call still fails, the error's `retryable` says whether the same call is worth
|
|
150
|
+
trying again later, such as from a job queue.
|
|
127
151
|
|
|
128
152
|
Retrying is safe for every call. Updates and merges change nothing when
|
|
129
153
|
repeated (though an update retried after a lost answer can overwrite a change
|
|
@@ -168,8 +192,8 @@ import { DropByError } from "@dropby/server";
|
|
|
168
192
|
try {
|
|
169
193
|
await dropby.chat.reply(threadId, { text });
|
|
170
194
|
} catch (error) {
|
|
171
|
-
if (error instanceof DropByError && error.
|
|
172
|
-
// error.retryAfter says how long the API asked to wait
|
|
195
|
+
if (error instanceof DropByError && error.retryable) {
|
|
196
|
+
// worth another try later; error.retryAfter says how long the API asked to wait
|
|
173
197
|
}
|
|
174
198
|
throw error;
|
|
175
199
|
}
|
|
@@ -180,12 +204,15 @@ API codes are `bad_request`, `unauthorized`, `forbidden`, `not_found`,
|
|
|
180
204
|
`rate_limited`, `unavailable` and `internal_error`; the API may add more. The
|
|
181
205
|
client adds `invalid_input` (a bad argument or option, raised before anything
|
|
182
206
|
is sent), `network_error`, `timeout` (30 seconds per attempt by default;
|
|
183
|
-
set `timeoutMs`), `invalid_response`
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
set `timeoutMs`), `invalid_response` (a success that is not JSON, or lacks what
|
|
208
|
+
the method returns: usually an `apiBaseUrl` that is not DropBy's) and
|
|
209
|
+
`request_failed` (an error response without a DropBy error body). `retryable` is
|
|
210
|
+
true for `network_error`, `timeout`, and a 408, 429 or 5xx. No message includes
|
|
211
|
+
your keys.
|
|
212
|
+
|
|
213
|
+
Responses are returned as the API sends them, once they carry what the method
|
|
214
|
+
returns. New fields and values can appear as the API grows, so treat enums such
|
|
215
|
+
as a status as open and keep a default branch when you switch over one.
|
|
189
216
|
|
|
190
217
|
## Identity proofs
|
|
191
218
|
|
package/dist/index.d.ts
CHANGED
|
@@ -48,7 +48,15 @@ type Attributes = Record<string, AttributeValue>;
|
|
|
48
48
|
*/
|
|
49
49
|
interface SessionUser {
|
|
50
50
|
id: string;
|
|
51
|
+
/**
|
|
52
|
+
* Shown to your team in the dropby dashboard. Empty or blank means no email, and
|
|
53
|
+
* then the one DropBy already has is kept; any other value must be an email address.
|
|
54
|
+
*/
|
|
51
55
|
email?: string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Shown to your team in the dropby dashboard. Trimmed and cut at 500 characters;
|
|
58
|
+
* empty or blank means no name, and then the name DropBy already has is kept.
|
|
59
|
+
*/
|
|
52
60
|
name?: string | undefined;
|
|
53
61
|
attributes?: Attributes | undefined;
|
|
54
62
|
[key: string]: AttributeValue | Attributes | undefined;
|
|
@@ -61,6 +69,10 @@ interface SessionUser {
|
|
|
61
69
|
interface SessionAccount {
|
|
62
70
|
id: string;
|
|
63
71
|
domain?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Shown to your team in the dropby dashboard. Trimmed and cut at 500 characters;
|
|
74
|
+
* empty or blank means no name, and then the name DropBy already has is kept.
|
|
75
|
+
*/
|
|
64
76
|
name?: string | undefined;
|
|
65
77
|
attributes?: Attributes | undefined;
|
|
66
78
|
[key: string]: AttributeValue | Attributes | undefined;
|
|
@@ -765,7 +777,9 @@ interface DropByConfigClient {
|
|
|
765
777
|
/**
|
|
766
778
|
* A client for DropBy's server API, from `createDropByServer`. Calls retry network
|
|
767
779
|
* errors, timeouts, 408, 429 and 5xx up to `maxRetries` times, and reject with a
|
|
768
|
-
* `DropByError`; a `Retry-After` over 10 seconds rejects at once.
|
|
780
|
+
* `DropByError`; a `Retry-After` over 10 seconds rejects at once. A success that
|
|
781
|
+
* lacks what its method returns rejects as `invalid_response`, usually an
|
|
782
|
+
* `apiBaseUrl` that is not DropBy's.
|
|
769
783
|
*/
|
|
770
784
|
interface DropByServer {
|
|
771
785
|
/** Asks: questions and surveys, and their answers. */
|
|
@@ -809,6 +823,13 @@ export declare class DropByError extends Error {
|
|
|
809
823
|
readonly status: number | undefined;
|
|
810
824
|
/** The seconds the API asked to wait before retrying, when it said. */
|
|
811
825
|
readonly retryAfter: number | undefined;
|
|
826
|
+
/**
|
|
827
|
+
* Whether this kind of failure is worth trying again: no response arrived, or the
|
|
828
|
+
* status was 408, 429 or 5xx. By the time it reaches you the client has done any
|
|
829
|
+
* retrying it will do, so true means try later, such as from a queue, and not
|
|
830
|
+
* before `retryAfter` when it is set.
|
|
831
|
+
*/
|
|
832
|
+
get retryable(): boolean;
|
|
812
833
|
constructor(code: DropByErrorCode, message: string, options?: DropByErrorOptions);
|
|
813
834
|
}
|
|
814
835
|
/**
|
|
@@ -818,9 +839,15 @@ export declare class DropByError extends Error {
|
|
|
818
839
|
interface IdentityProofUser {
|
|
819
840
|
/** Your own id for the user, 1 to 256 characters. */
|
|
820
841
|
id: string;
|
|
821
|
-
/**
|
|
842
|
+
/**
|
|
843
|
+
* Shown to your team in the dropby dashboard. Trimmed and cut at 500 characters;
|
|
844
|
+
* empty or blank means no name, and then the name DropBy already has is kept.
|
|
845
|
+
*/
|
|
822
846
|
name?: string | undefined;
|
|
823
|
-
/**
|
|
847
|
+
/**
|
|
848
|
+
* Shown to your team in the dropby dashboard. Empty or blank means no email, and
|
|
849
|
+
* then the one DropBy already has is kept; any other value must be an email address.
|
|
850
|
+
*/
|
|
824
851
|
email?: string | undefined;
|
|
825
852
|
/** Saved on the user, for audiences. */
|
|
826
853
|
attributes?: Attributes | undefined;
|
|
@@ -832,7 +859,10 @@ interface IdentityProofUser {
|
|
|
832
859
|
interface IdentityProofAccount {
|
|
833
860
|
/** Your own id for the account, 1 to 256 characters. */
|
|
834
861
|
id: string;
|
|
835
|
-
/**
|
|
862
|
+
/**
|
|
863
|
+
* Shown to your team in the dropby dashboard. Trimmed and cut at 500 characters;
|
|
864
|
+
* empty or blank means no name, and then the name DropBy already has is kept.
|
|
865
|
+
*/
|
|
836
866
|
name?: string | undefined;
|
|
837
867
|
/** The account's web domain. */
|
|
838
868
|
domain?: string | undefined;
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ var DropByError = class extends Error {
|
|
|
3
3
|
code;
|
|
4
4
|
status;
|
|
5
5
|
retryAfter;
|
|
6
|
+
get retryable() {
|
|
7
|
+
return retryable(this);
|
|
8
|
+
}
|
|
6
9
|
constructor(code, message, options = {}) {
|
|
7
10
|
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
8
11
|
this.code = code;
|
|
@@ -10,6 +13,10 @@ var DropByError = class extends Error {
|
|
|
10
13
|
this.retryAfter = options.retryAfter;
|
|
11
14
|
}
|
|
12
15
|
};
|
|
16
|
+
function retryable(error) {
|
|
17
|
+
if (error.status === void 0) return error.code === "network_error" || error.code === "timeout";
|
|
18
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
19
|
+
}
|
|
13
20
|
function invalidInput(message) {
|
|
14
21
|
return new DropByError("invalid_input", message);
|
|
15
22
|
}
|
|
@@ -30,6 +37,58 @@ function retryAfter(response) {
|
|
|
30
37
|
function isRecord(value) {
|
|
31
38
|
return typeof value === "object" && value !== null;
|
|
32
39
|
}
|
|
40
|
+
const replies = {
|
|
41
|
+
"asks.create": { ask: "object" },
|
|
42
|
+
"asks.list": { items: "list" },
|
|
43
|
+
"asks.get": { ask: "object" },
|
|
44
|
+
"asks.listResponses": { items: "list" },
|
|
45
|
+
"browserSessions.create": { session: "object" },
|
|
46
|
+
"chat.list": { items: "list" },
|
|
47
|
+
"chat.get": { thread: "object" },
|
|
48
|
+
"chat.listMessages": { items: "list" },
|
|
49
|
+
"chat.create": {
|
|
50
|
+
thread: "object",
|
|
51
|
+
message: "object"
|
|
52
|
+
},
|
|
53
|
+
"chat.reply": {
|
|
54
|
+
thread: "object",
|
|
55
|
+
message: "object"
|
|
56
|
+
},
|
|
57
|
+
"chat.update": { thread: "object" },
|
|
58
|
+
"chat.getAttachmentDownload": { download: "object" },
|
|
59
|
+
"config.evaluate": { config: "object" },
|
|
60
|
+
"ideas.list": { items: "list" },
|
|
61
|
+
"ideas.get": { idea: "object" },
|
|
62
|
+
"ideas.listVoters": { items: "list" },
|
|
63
|
+
"ideas.listDuplicates": { items: "list" },
|
|
64
|
+
"ideas.create": { idea: "object" },
|
|
65
|
+
"ideas.update": { idea: "object" },
|
|
66
|
+
"ideas.merge": {
|
|
67
|
+
idea: "object",
|
|
68
|
+
canonicalIdea: "object"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const isoDateTime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
72
|
+
function checkedReply(name, body, status) {
|
|
73
|
+
if (!isObject(body)) throw refused(name, "is not a JSON object", status);
|
|
74
|
+
for (const [field, kind] of Object.entries(replies[name])) {
|
|
75
|
+
const value = body[field];
|
|
76
|
+
if (kind === "list" ? !Array.isArray(value) : !isObject(value)) throw refused(name, `has no ${field} ${kind}`, status);
|
|
77
|
+
}
|
|
78
|
+
if (name === "browserSessions.create") checkSession(body.session, status);
|
|
79
|
+
return body;
|
|
80
|
+
}
|
|
81
|
+
function checkSession(session, status) {
|
|
82
|
+
const { sessionToken, expiresAt } = isObject(session) ? session : {};
|
|
83
|
+
if (typeof sessionToken !== "string" || !sessionToken.startsWith("dbs_")) throw refused("browserSessions.create", "has no session.sessionToken", status);
|
|
84
|
+
if (typeof expiresAt !== "string" || !isoDateTime.test(expiresAt) || Number.isNaN(Date.parse(expiresAt))) throw refused("browserSessions.create", "has no session.expiresAt date-time", status);
|
|
85
|
+
}
|
|
86
|
+
function refused(name, problem, status) {
|
|
87
|
+
return new DropByError("invalid_response", `${name}: DropBy's reply ${problem}`, { status });
|
|
88
|
+
}
|
|
89
|
+
function isObject(value) {
|
|
90
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
33
92
|
const defaultApiBaseUrl = "https://api.dropby.chat";
|
|
34
93
|
const defaultTimeoutMs = 3e4;
|
|
35
94
|
const defaultMaxRetries = 2;
|
|
@@ -59,89 +118,89 @@ var Server = class {
|
|
|
59
118
|
this.#maxRetries = retries(options.maxRetries ?? defaultMaxRetries);
|
|
60
119
|
this.#cache = acceptsCacheOption();
|
|
61
120
|
this.asks = {
|
|
62
|
-
create: async (input, options = {}) => this.#send("POST", "/v1/asks", {
|
|
121
|
+
create: async (input, options = {}) => this.#send("asks.create", "POST", "/v1/asks", {
|
|
63
122
|
body: input,
|
|
64
123
|
key: options.idempotencyKey,
|
|
65
124
|
keyed: true
|
|
66
125
|
}),
|
|
67
|
-
list: async ({ filter, sort, limit, cursor } = {}) => this.#send("GET", "/v1/asks", { query: {
|
|
126
|
+
list: async ({ filter, sort, limit, cursor } = {}) => this.#send("asks.list", "GET", "/v1/asks", { query: {
|
|
68
127
|
filter,
|
|
69
128
|
sort,
|
|
70
129
|
limit,
|
|
71
130
|
cursor
|
|
72
131
|
} }),
|
|
73
|
-
get: async (askId) => this.#send("GET", `/v1/asks/${pathId(askId, "ask_")}`),
|
|
74
|
-
listResponses: async (askId, { limit, cursor } = {}) => this.#send("GET", `/v1/asks/${pathId(askId, "ask_")}/responses`, { query: {
|
|
132
|
+
get: async (askId) => this.#send("asks.get", "GET", `/v1/asks/${pathId(askId, "ask_")}`),
|
|
133
|
+
listResponses: async (askId, { limit, cursor } = {}) => this.#send("asks.listResponses", "GET", `/v1/asks/${pathId(askId, "ask_")}/responses`, { query: {
|
|
75
134
|
limit,
|
|
76
135
|
cursor
|
|
77
136
|
} })
|
|
78
137
|
};
|
|
79
|
-
this.browserSessions = { create: async (input) => this.#send("POST", "/v1/browser-sessions", { body: input }) };
|
|
138
|
+
this.browserSessions = { create: async (input) => this.#send("browserSessions.create", "POST", "/v1/browser-sessions", { body: input }) };
|
|
80
139
|
this.chat = {
|
|
81
|
-
list: async ({ filter, limit, cursor } = {}) => this.#send("GET", "/v1/chat/threads", { query: {
|
|
140
|
+
list: async ({ filter, limit, cursor } = {}) => this.#send("chat.list", "GET", "/v1/chat/threads", { query: {
|
|
82
141
|
filter,
|
|
83
142
|
limit,
|
|
84
143
|
cursor
|
|
85
144
|
} }),
|
|
86
|
-
get: async (threadId) => this.#send("GET", `/v1/chat/threads/${pathId(threadId, "thr_")}`),
|
|
87
|
-
listMessages: async (threadId, { limit, cursor } = {}) => this.#send("GET", `/v1/chat/threads/${pathId(threadId, "thr_")}/messages`, { query: {
|
|
145
|
+
get: async (threadId) => this.#send("chat.get", "GET", `/v1/chat/threads/${pathId(threadId, "thr_")}`),
|
|
146
|
+
listMessages: async (threadId, { limit, cursor } = {}) => this.#send("chat.listMessages", "GET", `/v1/chat/threads/${pathId(threadId, "thr_")}/messages`, { query: {
|
|
88
147
|
limit,
|
|
89
148
|
cursor
|
|
90
149
|
} }),
|
|
91
|
-
create: async (input, options = {}) => this.#send("POST", "/v1/chat/threads", {
|
|
150
|
+
create: async (input, options = {}) => this.#send("chat.create", "POST", "/v1/chat/threads", {
|
|
92
151
|
body: input,
|
|
93
152
|
key: options.idempotencyKey,
|
|
94
153
|
keyed: true
|
|
95
154
|
}),
|
|
96
|
-
reply: async (threadId, input, options = {}) => this.#send("POST", `/v1/chat/threads/${pathId(threadId, "thr_")}/messages`, {
|
|
155
|
+
reply: async (threadId, input, options = {}) => this.#send("chat.reply", "POST", `/v1/chat/threads/${pathId(threadId, "thr_")}/messages`, {
|
|
97
156
|
body: input,
|
|
98
157
|
key: options.idempotencyKey,
|
|
99
158
|
keyed: true
|
|
100
159
|
}),
|
|
101
|
-
update: async (threadId, changes) => this.#send("PATCH", `/v1/chat/threads/${pathId(threadId, "thr_")}`, { body: changes }),
|
|
102
|
-
getAttachmentDownload: async (attachmentId) => this.#send("GET", `/v1/chat/attachments/${pathId(attachmentId, "att_")}/download`)
|
|
160
|
+
update: async (threadId, changes) => this.#send("chat.update", "PATCH", `/v1/chat/threads/${pathId(threadId, "thr_")}`, { body: changes }),
|
|
161
|
+
getAttachmentDownload: async (attachmentId) => this.#send("chat.getAttachmentDownload", "GET", `/v1/chat/attachments/${pathId(attachmentId, "att_")}/download`)
|
|
103
162
|
};
|
|
104
|
-
this.config = { evaluate: async (input) => this.#send("POST", "/v1/config/evaluate", { body: input }) };
|
|
163
|
+
this.config = { evaluate: async (input) => this.#send("config.evaluate", "POST", "/v1/config/evaluate", { body: input }) };
|
|
105
164
|
this.ideas = {
|
|
106
|
-
list: async ({ filter, sort, limit, cursor } = {}) => this.#send("GET", "/v1/ideas", { query: {
|
|
165
|
+
list: async ({ filter, sort, limit, cursor } = {}) => this.#send("ideas.list", "GET", "/v1/ideas", { query: {
|
|
107
166
|
filter,
|
|
108
167
|
sort,
|
|
109
168
|
limit,
|
|
110
169
|
cursor
|
|
111
170
|
} }),
|
|
112
|
-
get: async (ideaId) => this.#send("GET", `/v1/ideas/${pathId(ideaId, "idea_")}`),
|
|
113
|
-
listVoters: async (ideaId, { limit, cursor } = {}) => this.#send("GET", `/v1/ideas/${pathId(ideaId, "idea_")}/voters`, { query: {
|
|
171
|
+
get: async (ideaId) => this.#send("ideas.get", "GET", `/v1/ideas/${pathId(ideaId, "idea_")}`),
|
|
172
|
+
listVoters: async (ideaId, { limit, cursor } = {}) => this.#send("ideas.listVoters", "GET", `/v1/ideas/${pathId(ideaId, "idea_")}/voters`, { query: {
|
|
114
173
|
limit,
|
|
115
174
|
cursor
|
|
116
175
|
} }),
|
|
117
|
-
listDuplicates: async (ideaId, { limit, cursor } = {}) => this.#send("GET", `/v1/ideas/${pathId(ideaId, "idea_")}/duplicates`, { query: {
|
|
176
|
+
listDuplicates: async (ideaId, { limit, cursor } = {}) => this.#send("ideas.listDuplicates", "GET", `/v1/ideas/${pathId(ideaId, "idea_")}/duplicates`, { query: {
|
|
118
177
|
limit,
|
|
119
178
|
cursor
|
|
120
179
|
} }),
|
|
121
|
-
create: async (input, options = {}) => this.#send("POST", "/v1/ideas", {
|
|
180
|
+
create: async (input, options = {}) => this.#send("ideas.create", "POST", "/v1/ideas", {
|
|
122
181
|
body: input,
|
|
123
182
|
key: options.idempotencyKey,
|
|
124
183
|
keyed: true
|
|
125
184
|
}),
|
|
126
|
-
update: async (ideaId, changes) => this.#send("PATCH", `/v1/ideas/${pathId(ideaId, "idea_")}`, { body: changes }),
|
|
127
|
-
merge: async (ideaId, input) => this.#send("POST", `/v1/ideas/${pathId(ideaId, "idea_")}/merge`, { body: input })
|
|
185
|
+
update: async (ideaId, changes) => this.#send("ideas.update", "PATCH", `/v1/ideas/${pathId(ideaId, "idea_")}`, { body: changes }),
|
|
186
|
+
merge: async (ideaId, input) => this.#send("ideas.merge", "POST", `/v1/ideas/${pathId(ideaId, "idea_")}/merge`, { body: input })
|
|
128
187
|
};
|
|
129
188
|
}
|
|
130
|
-
async #send(method, path, options = {}) {
|
|
189
|
+
async #send(name, method, path, options = {}) {
|
|
131
190
|
const { key, keyed } = options;
|
|
132
191
|
if (key !== void 0 && (typeof key !== "string" || !idempotencyKeyPattern.test(key))) throw invalidInput("idempotencyKey must be 1 to 128 visible ASCII characters, such as a UUID");
|
|
133
192
|
const sentKey = key ?? (keyed ? crypto.randomUUID() : void 0);
|
|
134
193
|
const payload = options.body === void 0 ? void 0 : serialise(options.body);
|
|
135
194
|
const url = `${this.#origin}${path}${search(options.query)}`;
|
|
136
195
|
for (let attempt = 0;; attempt++) try {
|
|
137
|
-
return await this.#attempt(method, url, payload, sentKey);
|
|
196
|
+
return await this.#attempt(name, method, url, payload, sentKey);
|
|
138
197
|
} catch (error) {
|
|
139
|
-
if (!(error instanceof DropByError) || !
|
|
198
|
+
if (!(error instanceof DropByError) || !retryable(error) || attempt >= this.#maxRetries) throw error;
|
|
140
199
|
if (error.retryAfter !== void 0 && error.retryAfter > maxRetryAfterSeconds) throw error;
|
|
141
200
|
await sleep(Math.max(backoff(attempt + 1), (error.retryAfter ?? 0) * 1e3));
|
|
142
201
|
}
|
|
143
202
|
}
|
|
144
|
-
async #attempt(method, url, payload, key) {
|
|
203
|
+
async #attempt(name, method, url, payload, key) {
|
|
145
204
|
const controller = new AbortController();
|
|
146
205
|
const timer = setTimeout(() => controller.abort(), this.#timeoutMs);
|
|
147
206
|
try {
|
|
@@ -159,11 +218,13 @@ var Server = class {
|
|
|
159
218
|
});
|
|
160
219
|
if (!response.ok) throw await apiError(response);
|
|
161
220
|
const text = await response.text();
|
|
221
|
+
let body;
|
|
162
222
|
try {
|
|
163
|
-
|
|
223
|
+
body = JSON.parse(text);
|
|
164
224
|
} catch {
|
|
165
|
-
throw new DropByError("invalid_response",
|
|
225
|
+
throw new DropByError("invalid_response", `${name}: DropBy answered ${response.status} with a body that is not JSON`, { status: response.status });
|
|
166
226
|
}
|
|
227
|
+
return checkedReply(name, body, response.status);
|
|
167
228
|
} catch (error) {
|
|
168
229
|
if (controller.signal.aborted) throw new DropByError("timeout", `DropBy did not answer within ${this.#timeoutMs} ms`, { cause: error });
|
|
169
230
|
if (error instanceof DropByError) throw error;
|
|
@@ -176,10 +237,6 @@ var Server = class {
|
|
|
176
237
|
function createDropByServer(options) {
|
|
177
238
|
return new Server(options);
|
|
178
239
|
}
|
|
179
|
-
function transient(error) {
|
|
180
|
-
if (error.status === void 0) return error.code === "network_error" || error.code === "timeout";
|
|
181
|
-
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
182
|
-
}
|
|
183
240
|
function backoff(retry) {
|
|
184
241
|
return Math.min(8e3, 500 * 2 ** (retry - 1)) * (.5 + Math.random() / 2);
|
|
185
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dropby/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "DropBy for your backend: browser sessions, identity proofs and the server API for chat, ideas and asks.",
|
|
6
6
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "e08819c87c99727dbd8f8e6c3c697985c69c4d12",
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"main": "./dist/index.js"
|
|
40
40
|
}
|