@wokuapp/react-native 0.1.0 → 0.2.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 +73 -14
- package/dist/index.cjs +303 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -60
- package/dist/index.d.ts +88 -60
- package/dist/index.js +303 -93
- package/dist/index.js.map +1 -1
- package/package.json +27 -18
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@ var fetchHttpClient = {
|
|
|
4
4
|
const res = await fetch(req.url, {
|
|
5
5
|
method: req.method,
|
|
6
6
|
headers: req.headers,
|
|
7
|
-
body: req.body
|
|
7
|
+
body: req.body,
|
|
8
|
+
signal: req.signal
|
|
8
9
|
});
|
|
9
10
|
return {
|
|
10
11
|
status: res.status,
|
|
@@ -69,12 +70,16 @@ var WokuNetworkError = class extends WokuError {
|
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
// src/client.ts
|
|
72
|
-
var DEFAULT_CAPTURE_PATH = "/
|
|
73
|
+
var DEFAULT_CAPTURE_PATH = "/v1/captures";
|
|
73
74
|
var WokuClient = class {
|
|
74
75
|
constructor(config) {
|
|
75
76
|
this.config = config;
|
|
76
77
|
if (!config.apiUrl) throw new WokuConfigError("apiUrl is required");
|
|
77
78
|
if (!config.publicKey) throw new WokuConfigError("publicKey is required");
|
|
79
|
+
if (config.publicKey.startsWith("sk_"))
|
|
80
|
+
throw new WokuConfigError(
|
|
81
|
+
"Use a publishable key, never a management secret key, in a mobile app."
|
|
82
|
+
);
|
|
78
83
|
if (!config.companyId) throw new WokuConfigError("companyId is required");
|
|
79
84
|
this.http = config.http ?? fetchHttpClient;
|
|
80
85
|
this.logger = config.logger ?? noopLogger;
|
|
@@ -89,37 +94,87 @@ var WokuClient = class {
|
|
|
89
94
|
* non-2xx responses (so the caller can decide to re-queue).
|
|
90
95
|
*/
|
|
91
96
|
async send(submission) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
if (submission.companyId !== this.config.companyId)
|
|
98
|
+
throw new WokuConfigError(
|
|
99
|
+
"Cannot deliver a capture for another company."
|
|
100
|
+
);
|
|
101
|
+
if (submission.audio && !submission.language)
|
|
102
|
+
submission = { ...submission, language: this.config.language ?? "es" };
|
|
103
|
+
const headers = {
|
|
104
|
+
"x-woku-key": this.config.publicKey,
|
|
105
|
+
"X-Woku-Company": this.config.companyId,
|
|
106
|
+
"X-Woku-Idempotency-Key": submission.id
|
|
107
|
+
};
|
|
108
|
+
let requestBody;
|
|
109
|
+
if (submission.audio) {
|
|
110
|
+
const form = new FormData();
|
|
111
|
+
form.append("file", {
|
|
112
|
+
uri: submission.audio.uri,
|
|
113
|
+
type: submission.audio.mimeType,
|
|
114
|
+
name: "capture-audio"
|
|
104
115
|
});
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
form.append("payload", JSON.stringify(submission));
|
|
117
|
+
requestBody = form;
|
|
118
|
+
} else {
|
|
119
|
+
headers["Content-Type"] = "application/json";
|
|
120
|
+
requestBody = JSON.stringify(submission);
|
|
107
121
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
let timer;
|
|
124
|
+
try {
|
|
125
|
+
return await Promise.race([
|
|
126
|
+
(async () => {
|
|
127
|
+
const response = await this.http.request({
|
|
128
|
+
method: "POST",
|
|
129
|
+
url: this.endpoint,
|
|
130
|
+
headers,
|
|
131
|
+
body: requestBody,
|
|
132
|
+
signal: controller.signal
|
|
133
|
+
});
|
|
134
|
+
if (response.ok) {
|
|
135
|
+
const body2 = await this.safeJson(response.json);
|
|
136
|
+
return {
|
|
137
|
+
id: submission.id,
|
|
138
|
+
status: "sent",
|
|
139
|
+
remoteId: body2?.remoteId ?? body2?.id
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (response.status === 429) {
|
|
143
|
+
const raw = response.headers.get("Retry-After");
|
|
144
|
+
const retryAfter = raw === null ? void 0 : Number(raw);
|
|
145
|
+
throw new WokuQuarantineError(
|
|
146
|
+
"Submission blocked by quarantine",
|
|
147
|
+
retryAfter !== void 0 && Number.isFinite(retryAfter) ? Math.max(0, retryAfter) : void 0
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const body = await this.safeJson(response.json);
|
|
151
|
+
const value = body?.message;
|
|
152
|
+
const message = value && typeof value === "object" && !Array.isArray(value) ? value.message : value;
|
|
153
|
+
const detail = Array.isArray(message) ? message.filter((item) => typeof item === "string").join(", ") : message;
|
|
154
|
+
const error = typeof detail === "string" && detail ? detail : `HTTP ${response.status}`;
|
|
155
|
+
this.logger.warn(`woku capture rejected: ${error}`);
|
|
156
|
+
return {
|
|
157
|
+
id: submission.id,
|
|
158
|
+
status: "failed",
|
|
159
|
+
error,
|
|
160
|
+
retryable: response.status === 408 || response.status >= 500
|
|
161
|
+
};
|
|
162
|
+
})(),
|
|
163
|
+
new Promise((_resolve, reject) => {
|
|
164
|
+
timer = setTimeout(() => {
|
|
165
|
+
controller.abort();
|
|
166
|
+
reject(new WokuNetworkError("Capture request timed out"));
|
|
167
|
+
}, this.config.timeoutMs ?? 3e4);
|
|
168
|
+
})
|
|
169
|
+
]);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err instanceof WokuQuarantineError) throw err;
|
|
172
|
+
throw new WokuNetworkError(
|
|
173
|
+
err instanceof Error ? err.message : "Capture request failed"
|
|
117
174
|
);
|
|
175
|
+
} finally {
|
|
176
|
+
if (timer) clearTimeout(timer);
|
|
118
177
|
}
|
|
119
|
-
const body = await this.safeJson(response.json);
|
|
120
|
-
const error = body?.message ?? `HTTP ${response.status}`;
|
|
121
|
-
this.logger.warn(`woku capture rejected: ${error}`);
|
|
122
|
-
return { id: submission.id, status: "failed", error };
|
|
123
178
|
}
|
|
124
179
|
async safeJson(json) {
|
|
125
180
|
try {
|
|
@@ -132,94 +187,193 @@ var WokuClient = class {
|
|
|
132
187
|
|
|
133
188
|
// src/queue.ts
|
|
134
189
|
var DEFAULT_KEY = "woku.sdk.queue.v1";
|
|
135
|
-
var
|
|
190
|
+
var mutations = /* @__PURE__ */ new WeakMap();
|
|
191
|
+
var flushes = /* @__PURE__ */ new WeakMap();
|
|
136
192
|
var OfflineQueue = class {
|
|
137
193
|
constructor(opts = {}) {
|
|
138
|
-
this.items = null;
|
|
139
194
|
this.storage = opts.storage ?? new InMemoryStorage();
|
|
140
195
|
this.key = opts.storageKey ?? DEFAULT_KEY;
|
|
141
|
-
this.maxAttempts = opts.maxAttempts ??
|
|
196
|
+
this.maxAttempts = opts.maxAttempts ?? 8;
|
|
197
|
+
this.companyId = opts.companyId;
|
|
198
|
+
}
|
|
199
|
+
owned(item) {
|
|
200
|
+
return !this.companyId || item.submission.companyId === this.companyId;
|
|
142
201
|
}
|
|
143
202
|
async load() {
|
|
144
|
-
if (this.items) return this.items;
|
|
145
203
|
const raw = await this.storage.getItem(this.key);
|
|
146
|
-
if (!raw)
|
|
147
|
-
this.items = [];
|
|
148
|
-
return this.items;
|
|
149
|
-
}
|
|
204
|
+
if (!raw) return [];
|
|
150
205
|
try {
|
|
151
206
|
const parsed = JSON.parse(raw);
|
|
152
|
-
|
|
207
|
+
if (Array.isArray(parsed) && parsed.every(
|
|
208
|
+
(item) => item && typeof item === "object" && item.submission && typeof item.submission.id === "string" && typeof item.submission.companyId === "string" && Number.isFinite(item.submission.createdAt) && Number.isInteger(item.attempts) && item.attempts >= 0
|
|
209
|
+
))
|
|
210
|
+
return parsed;
|
|
153
211
|
} catch {
|
|
154
|
-
this.items = [];
|
|
155
212
|
}
|
|
156
|
-
|
|
213
|
+
throw new WokuConfigError(
|
|
214
|
+
"The capture queue contains invalid data; repair it before writing."
|
|
215
|
+
);
|
|
157
216
|
}
|
|
158
|
-
async persist() {
|
|
159
|
-
await this.storage.setItem(this.key, JSON.stringify(
|
|
217
|
+
async persist(items) {
|
|
218
|
+
await this.storage.setItem(this.key, JSON.stringify(items));
|
|
219
|
+
}
|
|
220
|
+
mutation(run) {
|
|
221
|
+
let locks = mutations.get(this.storage);
|
|
222
|
+
if (!locks) {
|
|
223
|
+
locks = /* @__PURE__ */ new Map();
|
|
224
|
+
mutations.set(this.storage, locks);
|
|
225
|
+
}
|
|
226
|
+
const previous = locks.get(this.key) ?? Promise.resolve();
|
|
227
|
+
const result = previous.then(run, run);
|
|
228
|
+
const settled = result.then(
|
|
229
|
+
() => void 0,
|
|
230
|
+
() => void 0
|
|
231
|
+
);
|
|
232
|
+
locks.set(this.key, settled);
|
|
233
|
+
void settled.then(() => {
|
|
234
|
+
if (locks?.get(this.key) === settled) locks.delete(this.key);
|
|
235
|
+
});
|
|
236
|
+
return result;
|
|
160
237
|
}
|
|
161
|
-
/** Adds a submission to the tail of the queue (dedup by id). */
|
|
162
238
|
async enqueue(submission) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
239
|
+
if (this.companyId && submission.companyId !== this.companyId)
|
|
240
|
+
throw new WokuConfigError(
|
|
241
|
+
"Cannot enqueue a capture from another company."
|
|
242
|
+
);
|
|
243
|
+
const snapshot = JSON.parse(
|
|
244
|
+
JSON.stringify(submission)
|
|
245
|
+
);
|
|
246
|
+
return this.mutation(async () => {
|
|
247
|
+
const items = await this.load();
|
|
248
|
+
if (items.some(
|
|
249
|
+
(item) => item.submission.id === snapshot.id && item.submission.companyId === snapshot.companyId
|
|
250
|
+
))
|
|
251
|
+
return;
|
|
252
|
+
items.push({ submission: snapshot, attempts: 0 });
|
|
253
|
+
await this.persist(items);
|
|
254
|
+
});
|
|
167
255
|
}
|
|
168
|
-
|
|
169
|
-
return
|
|
256
|
+
size() {
|
|
257
|
+
return this.mutation(
|
|
258
|
+
async () => (await this.load()).filter((item) => this.owned(item) && !item.failure).length
|
|
259
|
+
);
|
|
170
260
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
261
|
+
pending() {
|
|
262
|
+
return this.mutation(
|
|
263
|
+
async () => (await this.load()).filter((item) => this.owned(item) && !item.failure).map((item) => item.submission)
|
|
264
|
+
);
|
|
174
265
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
266
|
+
failures() {
|
|
267
|
+
return this.mutation(
|
|
268
|
+
async () => (await this.load()).filter((item) => this.owned(item) && item.failure).map((item) => ({
|
|
269
|
+
submission: item.submission,
|
|
270
|
+
reason: item.failure
|
|
271
|
+
}))
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
acknowledge(submission) {
|
|
275
|
+
return this.mutation(async () => {
|
|
276
|
+
const items = await this.load();
|
|
277
|
+
await this.persist(
|
|
278
|
+
items.filter(
|
|
279
|
+
(item) => !(item.submission.id === submission.id && item.submission.companyId === submission.companyId && this.owned(item))
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
reject(submission, reason) {
|
|
285
|
+
return this.mutation(async () => {
|
|
286
|
+
const items = await this.load();
|
|
287
|
+
const item = items.find(
|
|
288
|
+
(row) => row.submission.id === submission.id && row.submission.companyId === submission.companyId && this.owned(row)
|
|
289
|
+
);
|
|
290
|
+
if (item) {
|
|
291
|
+
item.failure = reason;
|
|
292
|
+
await this.persist(items);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
flush(send) {
|
|
297
|
+
let jobs = flushes.get(this.storage);
|
|
298
|
+
if (!jobs) {
|
|
299
|
+
jobs = /* @__PURE__ */ new Map();
|
|
300
|
+
flushes.set(this.storage, jobs);
|
|
301
|
+
}
|
|
302
|
+
const scope = JSON.stringify([this.key, this.companyId ?? "*"]);
|
|
303
|
+
const existing = jobs.get(scope);
|
|
304
|
+
if (existing) return existing;
|
|
305
|
+
const result = this.runFlush(send).finally(() => {
|
|
306
|
+
if (jobs?.get(scope) === result) jobs.delete(scope);
|
|
307
|
+
});
|
|
308
|
+
jobs.set(scope, result);
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
async runFlush(send) {
|
|
312
|
+
const batch = await this.pending();
|
|
313
|
+
let sent = 0, failed = 0, quarantined = false;
|
|
314
|
+
for (const candidate of batch) {
|
|
315
|
+
const current = await this.mutation(
|
|
316
|
+
async () => (await this.load()).find(
|
|
317
|
+
(item) => this.owned(item) && !item.failure && item.submission.id === candidate.id && item.submission.companyId === candidate.companyId
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
if (!current) continue;
|
|
321
|
+
const submission = current.submission;
|
|
322
|
+
if (submission.createdAt > 0 && Date.now() - submission.createdAt >= 24 * 60 * 60 * 1e3) {
|
|
323
|
+
await this.reject(
|
|
324
|
+
submission,
|
|
325
|
+
"Retry window expired; inspect delivery before resubmitting."
|
|
326
|
+
);
|
|
327
|
+
failed++;
|
|
190
328
|
continue;
|
|
191
329
|
}
|
|
330
|
+
let permanent = false;
|
|
331
|
+
let reason;
|
|
332
|
+
let accepted = false;
|
|
192
333
|
try {
|
|
193
|
-
const result = await send(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
else survivors.push(item);
|
|
200
|
-
}
|
|
201
|
-
} catch (err) {
|
|
202
|
-
if (err instanceof WokuQuarantineError) {
|
|
334
|
+
const result = await send(submission);
|
|
335
|
+
accepted = result.status === "sent";
|
|
336
|
+
reason = result.error;
|
|
337
|
+
permanent = result.retryable === false;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error instanceof WokuQuarantineError) {
|
|
203
340
|
quarantined = true;
|
|
204
|
-
|
|
205
|
-
} else if (err instanceof WokuNetworkError) {
|
|
206
|
-
item.attempts++;
|
|
207
|
-
survivors.push(item);
|
|
208
|
-
} else {
|
|
209
|
-
item.attempts++;
|
|
210
|
-
if (item.attempts >= this.maxAttempts) failed++;
|
|
211
|
-
else survivors.push(item);
|
|
341
|
+
break;
|
|
212
342
|
}
|
|
343
|
+
reason = error instanceof Error ? error.message : "Delivery failed";
|
|
213
344
|
}
|
|
345
|
+
const outcome = await this.mutation(async () => {
|
|
346
|
+
const items = await this.load();
|
|
347
|
+
const index = items.findIndex(
|
|
348
|
+
(item) => item.submission.id === submission.id && item.submission.companyId === submission.companyId && !item.failure
|
|
349
|
+
);
|
|
350
|
+
if (index < 0) return "removed";
|
|
351
|
+
if (accepted) items.splice(index, 1);
|
|
352
|
+
else {
|
|
353
|
+
const item = items[index];
|
|
354
|
+
item.attempts++;
|
|
355
|
+
if (permanent || item.attempts >= this.maxAttempts)
|
|
356
|
+
item.failure = reason ?? "Attempt budget exhausted";
|
|
357
|
+
}
|
|
358
|
+
const exhausted = !accepted && Boolean(items[index]?.failure);
|
|
359
|
+
await this.persist(items);
|
|
360
|
+
return accepted ? "sent" : exhausted ? "failed" : "pending";
|
|
361
|
+
});
|
|
362
|
+
if (outcome === "sent") sent++;
|
|
363
|
+
if (outcome === "failed") failed++;
|
|
214
364
|
}
|
|
215
|
-
this.
|
|
216
|
-
await this.persist();
|
|
217
|
-
return { sent, failed, remaining: survivors.length, quarantined };
|
|
365
|
+
return { sent, failed, remaining: await this.size(), quarantined };
|
|
218
366
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
367
|
+
clear() {
|
|
368
|
+
return this.mutation(async () => {
|
|
369
|
+
if (!this.companyId) {
|
|
370
|
+
await this.storage.removeItem(this.key);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const remaining = (await this.load()).filter((item) => !this.owned(item));
|
|
374
|
+
if (remaining.length) await this.persist(remaining);
|
|
375
|
+
else await this.storage.removeItem(this.key);
|
|
376
|
+
});
|
|
223
377
|
}
|
|
224
378
|
};
|
|
225
379
|
|
|
@@ -241,10 +395,12 @@ var WokuSdk = class {
|
|
|
241
395
|
this.client = new WokuClient(config);
|
|
242
396
|
this.queue = new OfflineQueue({
|
|
243
397
|
storage: config.storage,
|
|
398
|
+
companyId: config.companyId,
|
|
244
399
|
maxAttempts: config.maxQueueAttempts
|
|
245
400
|
});
|
|
246
401
|
this.logger = config.logger ?? noopLogger;
|
|
247
402
|
this.companyId = config.companyId;
|
|
403
|
+
this.language = config.language ?? "es";
|
|
248
404
|
}
|
|
249
405
|
/** Captures a Woku rating (1..5) with optional text/audio comment. */
|
|
250
406
|
async captureWoku(input) {
|
|
@@ -259,6 +415,8 @@ var WokuSdk = class {
|
|
|
259
415
|
audio: input.audio,
|
|
260
416
|
respondent: input.respondent,
|
|
261
417
|
metadata: input.metadata,
|
|
418
|
+
dispatchToken: input.dispatchToken,
|
|
419
|
+
language: input.language ?? (input.audio ? this.language : void 0),
|
|
262
420
|
createdAt: Date.now()
|
|
263
421
|
};
|
|
264
422
|
return this.deliver(submission);
|
|
@@ -276,10 +434,42 @@ var WokuSdk = class {
|
|
|
276
434
|
audio: input.audio,
|
|
277
435
|
respondent: input.respondent,
|
|
278
436
|
metadata: input.metadata,
|
|
437
|
+
dispatchToken: input.dispatchToken,
|
|
438
|
+
language: input.language ?? (input.audio ? this.language : void 0),
|
|
279
439
|
createdAt: Date.now()
|
|
280
440
|
};
|
|
281
441
|
return this.deliver(submission);
|
|
282
442
|
}
|
|
443
|
+
async captureCsat(input) {
|
|
444
|
+
assertInt(input.score, 1, 5, "score");
|
|
445
|
+
return this.deliver({
|
|
446
|
+
id: generateId(),
|
|
447
|
+
kind: "csat",
|
|
448
|
+
companyId: this.companyId,
|
|
449
|
+
targetId: input.csatId,
|
|
450
|
+
score: input.score,
|
|
451
|
+
comment: input.comment,
|
|
452
|
+
respondent: input.respondent,
|
|
453
|
+
metadata: input.metadata,
|
|
454
|
+
dispatchToken: input.dispatchToken,
|
|
455
|
+
createdAt: Date.now()
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async captureCes(input) {
|
|
459
|
+
assertInt(input.score, 1, 5, "score");
|
|
460
|
+
return this.deliver({
|
|
461
|
+
id: generateId(),
|
|
462
|
+
kind: "ces",
|
|
463
|
+
companyId: this.companyId,
|
|
464
|
+
targetId: input.cesId,
|
|
465
|
+
score: input.score,
|
|
466
|
+
comment: input.comment,
|
|
467
|
+
respondent: input.respondent,
|
|
468
|
+
metadata: input.metadata,
|
|
469
|
+
dispatchToken: input.dispatchToken,
|
|
470
|
+
createdAt: Date.now()
|
|
471
|
+
});
|
|
472
|
+
}
|
|
283
473
|
/** Retries every queued submission. Safe to call on app foreground or
|
|
284
474
|
* when connectivity is regained. */
|
|
285
475
|
flush() {
|
|
@@ -289,6 +479,10 @@ var WokuSdk = class {
|
|
|
289
479
|
pendingCount() {
|
|
290
480
|
return this.queue.size();
|
|
291
481
|
}
|
|
482
|
+
/** Inspect retained captures whose automatic attempt budget was exhausted. */
|
|
483
|
+
failedCaptures() {
|
|
484
|
+
return this.queue.failures();
|
|
485
|
+
}
|
|
292
486
|
/** Clears the offline queue (e.g. on user logout). */
|
|
293
487
|
clearQueue() {
|
|
294
488
|
return this.queue.clear();
|
|
@@ -298,9 +492,25 @@ var WokuSdk = class {
|
|
|
298
492
|
* submission is queued for a later flush rather than thrown away.
|
|
299
493
|
*/
|
|
300
494
|
async deliver(submission) {
|
|
495
|
+
submission = JSON.parse(JSON.stringify(submission));
|
|
496
|
+
await this.queue.enqueue(submission);
|
|
301
497
|
try {
|
|
302
498
|
const result = await this.client.send(submission);
|
|
303
|
-
if (result.status === "sent")
|
|
499
|
+
if (result.status === "sent") {
|
|
500
|
+
try {
|
|
501
|
+
await this.queue.acknowledge(submission);
|
|
502
|
+
} catch {
|
|
503
|
+
this.logger.warn(
|
|
504
|
+
"Capture accepted; local acknowledgement is pending",
|
|
505
|
+
{ id: submission.id }
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
if (result.retryable === false) {
|
|
511
|
+
await this.queue.reject(submission, result.error ?? "Capture rejected");
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
304
514
|
await this.queue.enqueue(submission);
|
|
305
515
|
return { ...result, status: "queued" };
|
|
306
516
|
} catch (err) {
|