@unboundcx/sdk 4.13.94 → 4.13.95
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/base.js +10 -39
- package/index.js +0 -5
- package/package.json +1 -4
- package/services/storage.js +12 -11
- package/services/taskRouter/ParticipantService.js +11 -2
- package/services/webchat/VisitorService.js +9 -33
- package/services/forms/HealthService.js +0 -35
- package/services/forms/PublicService.js +0 -110
- package/services/forms/README.md +0 -160
- package/services/forms/SettingsService.js +0 -66
- package/services/forms/SubmissionsService.js +0 -68
- package/services/forms.js +0 -64
package/base.js
CHANGED
|
@@ -16,17 +16,6 @@
|
|
|
16
16
|
const requestBySdk = new WeakMap();
|
|
17
17
|
const SDK_REQUEST = Symbol.for('unbound.sdk.request');
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* Safely read a process.env var. `process` is an undeclared global in a
|
|
21
|
-
* browser/Vite bundle -- `process?.env?.X` does NOT protect against that
|
|
22
|
-
* (optional chaining only guards property access, not a bare identifier
|
|
23
|
-
* reference), so a browser bundle throws `ReferenceError: process is not
|
|
24
|
-
* defined` the moment `process` is referenced at all. Gate on
|
|
25
|
-
* `typeof process !== 'undefined'` first.
|
|
26
|
-
*/
|
|
27
|
-
export const env = (key) =>
|
|
28
|
-
typeof process !== 'undefined' && process.env ? process.env[key] : undefined;
|
|
29
|
-
|
|
30
19
|
/**
|
|
31
20
|
* Service-internal request. Not part of the public SDK surface.
|
|
32
21
|
* Named SDK methods call this; app code must not. Optional transports
|
|
@@ -54,14 +43,14 @@ export class BaseSDK {
|
|
|
54
43
|
// Support both object and legacy positional parameters for backwards compatibility
|
|
55
44
|
if (typeof options === 'string') {
|
|
56
45
|
// Legacy positional parameters: (namespace, callId, token, fwRequestId)
|
|
57
|
-
this.namespace = options || env
|
|
46
|
+
this.namespace = options || process?.env?.namespace;
|
|
58
47
|
this.callId = arguments[1];
|
|
59
48
|
this.token = arguments[2];
|
|
60
49
|
this.fwRequestId = arguments[3];
|
|
61
50
|
} else {
|
|
62
51
|
// New object-based parameters
|
|
63
52
|
const { namespace, callId, token, fwRequestId, baseURL } = options;
|
|
64
|
-
this.namespace = namespace || env
|
|
53
|
+
this.namespace = namespace || process?.env?.namespace;
|
|
65
54
|
this.callId = callId;
|
|
66
55
|
this.token = token;
|
|
67
56
|
this.fwRequestId = fwRequestId;
|
|
@@ -89,7 +78,7 @@ export class BaseSDK {
|
|
|
89
78
|
// Server-side (Node.js)
|
|
90
79
|
this.environment = 'node';
|
|
91
80
|
this.baseURL = this._constructorBaseURL || `https://${this.namespace ? this.namespace : 'login'}.${
|
|
92
|
-
env
|
|
81
|
+
process.env?.API_BASE_URL || defaultDomain
|
|
93
82
|
}`;
|
|
94
83
|
} else {
|
|
95
84
|
// Client-side (browser)
|
|
@@ -102,7 +91,7 @@ export class BaseSDK {
|
|
|
102
91
|
this.baseUrl = url.hostname.replace(/^[^.]+\./, '');
|
|
103
92
|
} else {
|
|
104
93
|
this.baseUrl =
|
|
105
|
-
this.baseUrl || env
|
|
94
|
+
this.baseUrl || process?.env?.API_BASE_URL || defaultDomain;
|
|
106
95
|
if (this.baseUrl && !this.baseUrl.startsWith('api.')) {
|
|
107
96
|
this.baseUrl = `api.${this.baseUrl}`;
|
|
108
97
|
}
|
|
@@ -122,23 +111,9 @@ export class BaseSDK {
|
|
|
122
111
|
if (this.environment === 'node') {
|
|
123
112
|
if (!this._constructorBaseURL) {
|
|
124
113
|
this.baseURL = `https://${this.namespace ? this.namespace : 'login'}.${
|
|
125
|
-
env
|
|
114
|
+
process.env?.API_BASE_URL || defaultDomain
|
|
126
115
|
}`;
|
|
127
116
|
}
|
|
128
|
-
} else if (this._constructorBaseURL && !this.namespace) {
|
|
129
|
-
// Forms v2 P3 fix: a browser-environment instance constructed with
|
|
130
|
-
// ONLY a literal baseURL (no namespace) -- e.g. sdk.forms.public /
|
|
131
|
-
// sdk.webchat.visitor on a page that resolves its tenant server-side
|
|
132
|
-
// from an opaque key, never a namespace subdomain -- must hit that
|
|
133
|
-
// exact baseURL. Before this fix, this branch unconditionally built
|
|
134
|
-
// `https://${namespace}.${baseUrl}` even when namespace was
|
|
135
|
-
// undefined, producing a literal "https://undefined.<host>" request
|
|
136
|
-
// URL (caught via forms-v2 marketing-site integration, P3). The
|
|
137
|
-
// Node branch above already had the equivalent guard
|
|
138
|
-
// (`if (!this._constructorBaseURL)`); this mirrors it for browser.
|
|
139
|
-
// A namespace passed ALONGSIDE baseURL still prefixes it (unchanged,
|
|
140
|
-
// existing behavior other callers rely on).
|
|
141
|
-
this.fullUrl = this._constructorBaseURL;
|
|
142
117
|
} else {
|
|
143
118
|
this.fullUrl = `https://${this.namespace}.${this.baseUrl}`;
|
|
144
119
|
}
|
|
@@ -307,9 +282,9 @@ export class BaseSDK {
|
|
|
307
282
|
}
|
|
308
283
|
} else {
|
|
309
284
|
// No transport available, fallback to HTTP
|
|
310
|
-
if (forceFetch && env
|
|
285
|
+
if (forceFetch && process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE) {
|
|
311
286
|
params.headers['x-token-type-override'] =
|
|
312
|
-
env
|
|
287
|
+
process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE;
|
|
313
288
|
}
|
|
314
289
|
return this._httpRequest(
|
|
315
290
|
endpoint,
|
|
@@ -383,7 +358,7 @@ export class BaseSDK {
|
|
|
383
358
|
returnRawResponse = false,
|
|
384
359
|
startTime = Date.now(),
|
|
385
360
|
) {
|
|
386
|
-
const { body, query, headers = {}
|
|
361
|
+
const { body, query, headers = {} } = params;
|
|
387
362
|
|
|
388
363
|
const options = {
|
|
389
364
|
method,
|
|
@@ -398,13 +373,9 @@ export class BaseSDK {
|
|
|
398
373
|
},
|
|
399
374
|
};
|
|
400
375
|
|
|
401
|
-
// Set credentials for browser environment
|
|
402
|
-
// endpoints called from third-party origins (forms, webchat loader) opt
|
|
403
|
-
// out via params.credentials = 'omit' — app1-api's CORS only sends
|
|
404
|
-
// access-control-allow-credentials for trusted app1 base domains, so a
|
|
405
|
-
// browser rejects the response for `include` requests from other origins.
|
|
376
|
+
// Set credentials for browser environment
|
|
406
377
|
if (this.environment === 'browser') {
|
|
407
|
-
options.credentials =
|
|
378
|
+
options.credentials = 'include';
|
|
408
379
|
}
|
|
409
380
|
|
|
410
381
|
let url;
|
package/index.js
CHANGED
|
@@ -26,7 +26,6 @@ import { CobrowseService } from './services/cobrowse.js';
|
|
|
26
26
|
import { MessageTemplatesService } from './services/messageTemplates.js';
|
|
27
27
|
import { ExternalOAuthService } from './services/externalOAuth.js';
|
|
28
28
|
import { GoogleCalendarService } from './services/googleCalendar.js';
|
|
29
|
-
import { FormsService } from './services/forms.js';
|
|
30
29
|
import { DriveService } from './services/drive.js';
|
|
31
30
|
import { EnrollService } from './services/enroll.js';
|
|
32
31
|
import { PhoneNumbersService } from './services/phoneNumbers.js';
|
|
@@ -126,7 +125,6 @@ class UnboundSDK extends BaseSDK {
|
|
|
126
125
|
this.messageTemplates = new MessageTemplatesService(this);
|
|
127
126
|
this.externalOAuth = new ExternalOAuthService(this);
|
|
128
127
|
this.googleCalendar = new GoogleCalendarService(this);
|
|
129
|
-
this.forms = new FormsService(this);
|
|
130
128
|
this.drive = new DriveService(this);
|
|
131
129
|
this.enroll = new EnrollService(this);
|
|
132
130
|
this.phoneNumbers = new PhoneNumbersService(this);
|
|
@@ -328,9 +326,6 @@ export { MessageTemplatesService } from './services/messageTemplates.js';
|
|
|
328
326
|
export { WebchatVisitorService } from './services/webchat/VisitorService.js';
|
|
329
327
|
export { ExternalOAuthService } from './services/externalOAuth.js';
|
|
330
328
|
export { GoogleCalendarService } from './services/googleCalendar.js';
|
|
331
|
-
export { FormsService } from './services/forms.js';
|
|
332
|
-
export { FormsPublicService } from './services/forms/PublicService.js';
|
|
333
|
-
export { FormsHealthService } from './services/forms/HealthService.js';
|
|
334
329
|
export { DriveService } from './services/drive.js';
|
|
335
330
|
export { EnrollService } from './services/enroll.js';
|
|
336
331
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.95",
|
|
4
4
|
"description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -52,9 +52,6 @@
|
|
|
52
52
|
"import": "./index.js",
|
|
53
53
|
"require": "./index.cjs"
|
|
54
54
|
},
|
|
55
|
-
"./base.js": {
|
|
56
|
-
"import": "./base.js"
|
|
57
|
-
},
|
|
58
55
|
"./services/*": {
|
|
59
56
|
"import": "./services/*.js"
|
|
60
57
|
},
|
package/services/storage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { internalRequest
|
|
1
|
+
import { internalRequest } from '../base.js';
|
|
2
2
|
export class StorageService {
|
|
3
3
|
constructor(sdk) {
|
|
4
4
|
this.sdk = sdk;
|
|
@@ -314,12 +314,13 @@ export class StorageService {
|
|
|
314
314
|
headers = result.headers;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
if (env
|
|
318
|
-
headers['x-token-type-override'] =
|
|
317
|
+
if (process?.env?.AUTH_V3_TOKEN_TYPE_OVERRIDE) {
|
|
318
|
+
headers['x-token-type-override'] =
|
|
319
|
+
process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE;
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
if (skipClamscan && env
|
|
322
|
-
headers['x-clamscan-override-key'] = env
|
|
322
|
+
if (skipClamscan && process?.env?.CLAMSCAN_OVERRIDE_KEY) {
|
|
323
|
+
headers['x-clamscan-override-key'] = process.env.CLAMSCAN_OVERRIDE_KEY;
|
|
323
324
|
}
|
|
324
325
|
|
|
325
326
|
const params = {
|
|
@@ -407,16 +408,16 @@ export class StorageService {
|
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
// Add environment variable override headers
|
|
410
|
-
if (env
|
|
411
|
+
if (process?.env?.AUTH_V3_TOKEN_TYPE_OVERRIDE) {
|
|
411
412
|
xhr.setRequestHeader(
|
|
412
413
|
'x-token-type-override',
|
|
413
|
-
env
|
|
414
|
+
process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE,
|
|
414
415
|
);
|
|
415
416
|
}
|
|
416
|
-
if (skipClamscan && env
|
|
417
|
+
if (skipClamscan && process?.env?.CLAMSCAN_OVERRIDE_KEY) {
|
|
417
418
|
xhr.setRequestHeader(
|
|
418
419
|
'x-clamscan-override-key',
|
|
419
|
-
env
|
|
420
|
+
process.env.CLAMSCAN_OVERRIDE_KEY,
|
|
420
421
|
);
|
|
421
422
|
}
|
|
422
423
|
|
|
@@ -655,8 +656,8 @@ Response:
|
|
|
655
656
|
if (metadata) formData.append('metadata', JSON.stringify(metadata));
|
|
656
657
|
}
|
|
657
658
|
|
|
658
|
-
if (_options?.skipScan && env
|
|
659
|
-
headers['x-clamscan-override-key'] = env
|
|
659
|
+
if (_options?.skipScan && process?.env?.CLAMSCAN_OVERRIDE_KEY) {
|
|
660
|
+
headers['x-clamscan-override-key'] = process.env.CLAMSCAN_OVERRIDE_KEY;
|
|
660
661
|
}
|
|
661
662
|
|
|
662
663
|
const params = {
|
|
@@ -170,15 +170,23 @@ export class ParticipantService {
|
|
|
170
170
|
* @example
|
|
171
171
|
* await sdk.taskRouter.participants.update({ taskId: 'task_123', participantId: 'tp_456', held: true });
|
|
172
172
|
*/
|
|
173
|
-
async update({
|
|
173
|
+
async update({
|
|
174
|
+
taskId,
|
|
175
|
+
participantId,
|
|
176
|
+
held,
|
|
177
|
+
muted,
|
|
178
|
+
bridgeRole,
|
|
179
|
+
joinAudio,
|
|
180
|
+
} = {}) {
|
|
174
181
|
this.sdk.validateParams(
|
|
175
|
-
{ taskId, participantId, held, muted, bridgeRole },
|
|
182
|
+
{ taskId, participantId, held, muted, bridgeRole, joinAudio },
|
|
176
183
|
{
|
|
177
184
|
taskId: { type: 'string', required: true },
|
|
178
185
|
participantId: { type: 'string', required: true },
|
|
179
186
|
held: { type: 'boolean', required: false },
|
|
180
187
|
muted: { type: 'boolean', required: false },
|
|
181
188
|
bridgeRole: { type: 'string', required: false },
|
|
189
|
+
joinAudio: { type: 'boolean', required: false },
|
|
182
190
|
},
|
|
183
191
|
);
|
|
184
192
|
|
|
@@ -186,6 +194,7 @@ export class ParticipantService {
|
|
|
186
194
|
if (held !== undefined) params.body.held = held;
|
|
187
195
|
if (muted !== undefined) params.body.muted = muted;
|
|
188
196
|
if (bridgeRole !== undefined) params.body.bridgeRole = bridgeRole;
|
|
197
|
+
if (joinAudio !== undefined) params.body.joinAudio = joinAudio;
|
|
189
198
|
|
|
190
199
|
return await internalRequest(
|
|
191
200
|
this.sdk,
|
|
@@ -14,16 +14,6 @@ import { internalRequest } from '../../base.js';
|
|
|
14
14
|
// method forces HTTP (esign.public precedent, base.js `forceFetch`) since
|
|
15
15
|
// these are one-shot fetches from a customer page or a custom UI, never
|
|
16
16
|
// NATS-transport traffic.
|
|
17
|
-
//
|
|
18
|
-
// Every call also passes `credentials: 'omit'` (base.js `_httpRequest`
|
|
19
|
-
// opt-in), matching app1-webchat-embed's own webchatApi.js fetch wrapper
|
|
20
|
-
// (which already sets `credentials:'omit'` for the same reason). This
|
|
21
|
-
// surface is meant for a custom UI on a third-party origin calling the API
|
|
22
|
-
// directly (see `grant()` below), not just the embed iframe -- and
|
|
23
|
-
// app1-api's CORS only sends `access-control-allow-credentials` for
|
|
24
|
-
// trusted app1 base domains, so a browser rejects the response for a
|
|
25
|
-
// `credentials:'include'` request from any other origin even though auth
|
|
26
|
-
// here is Authorization-header/body-token based, not cookie based.
|
|
27
17
|
function authHeaders(token) {
|
|
28
18
|
return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
|
|
29
19
|
}
|
|
@@ -74,7 +64,6 @@ class WebchatVisitorSessionService {
|
|
|
74
64
|
trackingId,
|
|
75
65
|
initialMessage,
|
|
76
66
|
},
|
|
77
|
-
credentials: 'omit',
|
|
78
67
|
},
|
|
79
68
|
true,
|
|
80
69
|
);
|
|
@@ -100,7 +89,7 @@ class WebchatVisitorSessionService {
|
|
|
100
89
|
this.sdk,
|
|
101
90
|
`/webchat/${widgetId}/visitor-profile`,
|
|
102
91
|
'POST',
|
|
103
|
-
{ body: { embedGrant, trackingId }
|
|
92
|
+
{ body: { embedGrant, trackingId } },
|
|
104
93
|
true,
|
|
105
94
|
);
|
|
106
95
|
}
|
|
@@ -126,7 +115,7 @@ class WebchatVisitorSessionService {
|
|
|
126
115
|
this.sdk,
|
|
127
116
|
`/webchat/${widgetId}/session`,
|
|
128
117
|
'POST',
|
|
129
|
-
{ body: { resumeToken }
|
|
118
|
+
{ body: { resumeToken } },
|
|
130
119
|
true,
|
|
131
120
|
);
|
|
132
121
|
}
|
|
@@ -151,7 +140,7 @@ class WebchatVisitorSessionService {
|
|
|
151
140
|
this.sdk,
|
|
152
141
|
`/webchat/${widgetId}/session/end`,
|
|
153
142
|
'POST',
|
|
154
|
-
{ ...authHeaders(token)
|
|
143
|
+
{ ...authHeaders(token) },
|
|
155
144
|
true,
|
|
156
145
|
);
|
|
157
146
|
}
|
|
@@ -190,7 +179,6 @@ class WebchatVisitorMessagesService {
|
|
|
190
179
|
...(limit !== undefined && limit !== null ? { limit } : {}),
|
|
191
180
|
},
|
|
192
181
|
...authHeaders(token),
|
|
193
|
-
credentials: 'omit',
|
|
194
182
|
},
|
|
195
183
|
true,
|
|
196
184
|
);
|
|
@@ -217,7 +205,7 @@ class WebchatVisitorMessagesService {
|
|
|
217
205
|
this.sdk,
|
|
218
206
|
`/webchat/${widgetId}/messages`,
|
|
219
207
|
'POST',
|
|
220
|
-
{ body: { message, media }, ...authHeaders(token)
|
|
208
|
+
{ body: { message, media }, ...authHeaders(token) },
|
|
221
209
|
true,
|
|
222
210
|
);
|
|
223
211
|
}
|
|
@@ -260,7 +248,7 @@ class WebchatVisitorFilesService {
|
|
|
260
248
|
this.sdk,
|
|
261
249
|
`/webchat/${widgetId}/files`,
|
|
262
250
|
'POST',
|
|
263
|
-
{ body, ...authHeaders(token)
|
|
251
|
+
{ body, ...authHeaders(token) },
|
|
264
252
|
true,
|
|
265
253
|
);
|
|
266
254
|
}
|
|
@@ -309,13 +297,7 @@ export class WebchatVisitorService {
|
|
|
309
297
|
{ widgetId },
|
|
310
298
|
{ widgetId: { type: 'string', required: true } },
|
|
311
299
|
);
|
|
312
|
-
return internalRequest(
|
|
313
|
-
this.sdk,
|
|
314
|
-
`/webchat/${widgetId}/grant`,
|
|
315
|
-
'GET',
|
|
316
|
-
{ credentials: 'omit' },
|
|
317
|
-
true,
|
|
318
|
-
);
|
|
300
|
+
return internalRequest(this.sdk, `/webchat/${widgetId}/grant`, 'GET', {}, true);
|
|
319
301
|
}
|
|
320
302
|
|
|
321
303
|
/**
|
|
@@ -328,13 +310,7 @@ export class WebchatVisitorService {
|
|
|
328
310
|
{ widgetId },
|
|
329
311
|
{ widgetId: { type: 'string', required: true } },
|
|
330
312
|
);
|
|
331
|
-
return internalRequest(
|
|
332
|
-
this.sdk,
|
|
333
|
-
`/webchat/${widgetId}/status`,
|
|
334
|
-
'GET',
|
|
335
|
-
{ credentials: 'omit' },
|
|
336
|
-
true,
|
|
337
|
-
);
|
|
313
|
+
return internalRequest(this.sdk, `/webchat/${widgetId}/status`, 'GET', {}, true);
|
|
338
314
|
}
|
|
339
315
|
|
|
340
316
|
/**
|
|
@@ -360,7 +336,7 @@ export class WebchatVisitorService {
|
|
|
360
336
|
this.sdk,
|
|
361
337
|
`/webchat/${widgetId}/transcript`,
|
|
362
338
|
'POST',
|
|
363
|
-
{ body: { optIn, email }, ...authHeaders(token)
|
|
339
|
+
{ body: { optIn, email }, ...authHeaders(token) },
|
|
364
340
|
true,
|
|
365
341
|
);
|
|
366
342
|
}
|
|
@@ -396,7 +372,7 @@ export class WebchatVisitorService {
|
|
|
396
372
|
this.sdk,
|
|
397
373
|
`/webchat/${widgetId}/identify`,
|
|
398
374
|
'POST',
|
|
399
|
-
{ body: { ...fields, hash }, ...authHeaders(token)
|
|
375
|
+
{ body: { ...fields, hash }, ...authHeaders(token) },
|
|
400
376
|
true,
|
|
401
377
|
);
|
|
402
378
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from '../../base.js';
|
|
2
|
-
|
|
3
|
-
// Forms v2 P8 (plans/forms-v2-plan.md D13, forms-v2-precheck.md §9) --
|
|
4
|
-
// agent-authenticated Health tab data (`sdk.forms.health`): submissions
|
|
5
|
-
// tiles, a 30-day submissions-per-day sparkline, per-field fill rate, and
|
|
6
|
-
// drift alerts (a field with 0 non-blank values in the last 7 days after
|
|
7
|
-
// previously being filled >=50% of the time). Backed by the nightly
|
|
8
|
-
// `formFieldStats` rollup -- server route:
|
|
9
|
-
// `app1-api/src/services/forms/controllers/getFormHealth.js`.
|
|
10
|
-
export class FormsHealthService {
|
|
11
|
-
constructor(sdk) {
|
|
12
|
-
this.sdk = sdk;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param {string} formId
|
|
17
|
-
* @returns {Promise<{
|
|
18
|
-
* tiles: {submissions:number, submissionsChangePct:number|null,
|
|
19
|
-
* spamCount:number, spamRatePct:number, companyLinkedPct:number,
|
|
20
|
-
* companyCreated:number, actionsFailed:number},
|
|
21
|
-
* sparkline: {day:string, submitted:number}[],
|
|
22
|
-
* fields: {fieldKey:string, label:string, fillRate:number|null,
|
|
23
|
-
* priorFillRate:number|null, isDrifting:boolean}[],
|
|
24
|
-
* driftAlerts: object[],
|
|
25
|
-
* windowDays: number,
|
|
26
|
-
* }>}
|
|
27
|
-
*/
|
|
28
|
-
async get(formId) {
|
|
29
|
-
return internalRequest(
|
|
30
|
-
this.sdk,
|
|
31
|
-
`/forms/${encodeURIComponent(formId)}/health`,
|
|
32
|
-
'GET',
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from '../../base.js';
|
|
2
|
-
|
|
3
|
-
// Public, unauthenticated forms surface -- `sdk.forms.public`. Mirrors
|
|
4
|
-
// WebchatVisitorService.js: the sdk instance backing this only needs
|
|
5
|
-
// `namespace` (or a custom `baseURL`) set at construction, never
|
|
6
|
-
// `sdk.token`. POSTs `forceFetch` (HTTP-only, no NATS transport) since
|
|
7
|
-
// these are one-shot fetches from a customer page / marketing site, same
|
|
8
|
-
// as every other visitor-facing call.
|
|
9
|
-
//
|
|
10
|
-
// Every call passes `credentials: 'omit'` (base.js `_httpRequest` opt-in) --
|
|
11
|
-
// this surface is hit from third-party origins (a marketing site embedding
|
|
12
|
-
// a form, not an app1 base domain), and app1-api's CORS
|
|
13
|
-
// (`src/index.js` allowlist) only sends `access-control-allow-credentials`
|
|
14
|
-
// for trusted app1 base domains. A browser rejects the whole response for
|
|
15
|
-
// a `credentials:'include'` request when that header is missing, even
|
|
16
|
-
// though these endpoints don't use cookies for auth -- so third-party
|
|
17
|
-
// callers must opt out explicitly.
|
|
18
|
-
//
|
|
19
|
-
// Wire shape matches `POST /f/:publicKey` (app1-api webhooks service,
|
|
20
|
-
// forms-v2-precheck.md §4.1): plain form fields go at the top level of the
|
|
21
|
-
// body (fieldKey -> value, arrays allowed), and everything the server
|
|
22
|
-
// treats as a *control* field (not a form value) is sent with a leading
|
|
23
|
-
// underscore, e.g. `_idempotencyKey`/`_captchaToken` below --
|
|
24
|
-
// formSubmitPublicKey.js splits on that same convention. `context` is NOT a
|
|
25
|
-
// control field: app1-api's captureContext.js builds the submission's
|
|
26
|
-
// context column by scanning the body's TOP-LEVEL keys against an
|
|
27
|
-
// allowlist (utm_*, gclid, fbclid, referrer, landingUrl, pageUrl,
|
|
28
|
-
// userAgent) and explicitly skips any `_`-prefixed key as a control
|
|
29
|
-
// field -- so `context` values are spread onto the top level alongside
|
|
30
|
-
// `fields`, never nested, or the server drops them silently.
|
|
31
|
-
export class FormsPublicService {
|
|
32
|
-
constructor(sdk) {
|
|
33
|
-
this.sdk = sdk;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Submit a public form by its publicKey (D1 -- every form has its own
|
|
38
|
-
* publicKey; no tracking-code/_token needed -- publicKey + origin/
|
|
39
|
-
* domainAllowlist is the only auth for a public form submit).
|
|
40
|
-
* @param {string} publicKey
|
|
41
|
-
* @param {Object} fields - fieldKey -> value (arrays kept as arrays).
|
|
42
|
-
* @param {Object} [options]
|
|
43
|
-
* @param {Object} [options.context] - Client-observed context (utm/referrer/
|
|
44
|
-
* landingUrl/etc, D12) to merge with what the server infers from the
|
|
45
|
-
* request itself -- spread onto the top-level body (never nested);
|
|
46
|
-
* the server's captureContext.js allowlist only reads top-level keys.
|
|
47
|
-
* A key here that collides with a `fields` key loses to `fields`.
|
|
48
|
-
* @param {string} [options.captchaToken] - Turnstile/etc response token
|
|
49
|
-
* (D10); sent as `_captchaToken`. Ignored server-side until a captcha
|
|
50
|
-
* provider is configured for the form/account.
|
|
51
|
-
* @param {string} [options.idempotencyKey] - Per-render dedupe token
|
|
52
|
-
* (D10); sent as `_idempotencyKey`.
|
|
53
|
-
* @returns {Promise<{ok:boolean, message?:string}>}
|
|
54
|
-
*/
|
|
55
|
-
async submit(publicKey, fields, { context, captchaToken, idempotencyKey } = {}) {
|
|
56
|
-
this.sdk.validateParams(
|
|
57
|
-
{ publicKey },
|
|
58
|
-
{ publicKey: { type: 'string', required: true } },
|
|
59
|
-
);
|
|
60
|
-
const body = { ...(context || {}), ...(fields || {}) };
|
|
61
|
-
if (idempotencyKey) body._idempotencyKey = idempotencyKey;
|
|
62
|
-
if (captchaToken) body._captchaToken = captchaToken;
|
|
63
|
-
|
|
64
|
-
return internalRequest(
|
|
65
|
-
this.sdk,
|
|
66
|
-
`/f/${publicKey}`,
|
|
67
|
-
'POST',
|
|
68
|
-
{ body, credentials: 'omit' },
|
|
69
|
-
true,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Upload a file for a `file` inputType field (D21), ahead of the real
|
|
75
|
-
* `submit()` call. Mirrors `webchat.visitor.files.upload()`'s shape:
|
|
76
|
-
* browser-only (needs `FormData`), no progress event. The returned
|
|
77
|
-
* `token` must be echoed back inside `submit()`'s `fields[fieldKey]` as
|
|
78
|
-
* `JSON.stringify({fileId, token})` -- the server (formFileAttach.js)
|
|
79
|
-
* verifies it names this exact (formId, fieldKey, fileId) triple before
|
|
80
|
-
* attaching the file to the submission.
|
|
81
|
-
* @param {string} publicKey
|
|
82
|
-
* @param {string} fieldKey - Must match a `file` inputType field on the form.
|
|
83
|
-
* @param {File|Blob} file
|
|
84
|
-
* @returns {Promise<{fileId:string, token:string, fileName:string, fileType:string, fileSize:number}>}
|
|
85
|
-
*/
|
|
86
|
-
async upload(publicKey, fieldKey, file) {
|
|
87
|
-
this.sdk.validateParams(
|
|
88
|
-
{ publicKey, fieldKey },
|
|
89
|
-
{
|
|
90
|
-
publicKey: { type: 'string', required: true },
|
|
91
|
-
fieldKey: { type: 'string', required: true },
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
|
-
if (typeof FormData === 'undefined' || !file) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
'forms.public.upload :: a browser File/Blob and FormData support are required',
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
const body = new FormData();
|
|
100
|
-
body.append('fieldKey', fieldKey);
|
|
101
|
-
body.append('file', file);
|
|
102
|
-
return internalRequest(
|
|
103
|
-
this.sdk,
|
|
104
|
-
`/f/${publicKey}/upload`,
|
|
105
|
-
'POST',
|
|
106
|
-
{ body, credentials: 'omit' },
|
|
107
|
-
true,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
package/services/forms/README.md
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# Forms v2 SDK module (`sdk.forms`)
|
|
2
|
-
|
|
3
|
-
Internal reference for `services/forms.js` + `services/forms/*.js` as they exist in code
|
|
4
|
-
today on branch `f-forms-v2` (sdk version `4.13.92`). Not the plan — every claim below is
|
|
5
|
-
cited to a source line. See `/workspace/code/app1/plans/forms-v2-plan.md` /
|
|
6
|
-
`forms-v2-progress.md` / `forms-v2-precheck.md` for design history; §"Deviations from
|
|
7
|
-
plan" at the bottom lists where progress-doc claims and code disagree.
|
|
8
|
-
|
|
9
|
-
## Files
|
|
10
|
-
|
|
11
|
-
| File | Exports |
|
|
12
|
-
|---|---|
|
|
13
|
-
| `services/forms.js` | `FormsService` (registers as `sdk.forms`) |
|
|
14
|
-
| `services/forms/PublicService.js` | `FormsPublicService` → `sdk.forms.public` |
|
|
15
|
-
| `services/forms/SubmissionsService.js` | `FormsSubmissionsService` → `sdk.forms.submissions` |
|
|
16
|
-
| `services/forms/SettingsService.js` | `FormsSettingsService` → `sdk.forms.settings` |
|
|
17
|
-
| `services/forms/HealthService.js` | `FormsHealthService` → `sdk.forms.health` |
|
|
18
|
-
|
|
19
|
-
Registered in `index.js:29` (import), `index.js:129` (`this.forms = new FormsService(this)`),
|
|
20
|
-
exported at `index.js:331-333`.
|
|
21
|
-
|
|
22
|
-
## Methods
|
|
23
|
-
|
|
24
|
-
| Method | Signature | Returns | Purpose |
|
|
25
|
-
|---|---|---|---|
|
|
26
|
-
| `sdk.forms.public.submit` | `submit(publicKey: string, fields: Object, { context, captchaToken, idempotencyKey }: Object = {})` | `Promise<{ok: boolean, message?: string}>` | Submit a public form by `publicKey`; `POST /f/:publicKey` |
|
|
27
|
-
| `sdk.forms.public.upload` | `upload(publicKey: string, fieldKey: string, file: File\|Blob)` | `Promise<{fileId, token, fileName, fileType, fileSize}>` | Browser-only file upload ahead of `submit()`, for a `file`-type field; `POST /f/:publicKey/upload` |
|
|
28
|
-
| `sdk.forms.submissions.reprocess` | `reprocess(id: string)` | `Promise<Object>` | Re-run the pipeline for a stored submission; `POST /object/formSubmissions/:id/reprocess` |
|
|
29
|
-
| `sdk.forms.submissions.markNotSpam` | `markNotSpam(id: string)` | `Promise<Object>` | Clear a submission's spam status; `POST /object/formSubmissions/:id/mark-not-spam` |
|
|
30
|
-
| `sdk.forms.submissions.resolveReview` | `resolveReview(id: string, choice: string)` | `Promise<Object>` | Resolve an `identityConflict`/`review`-flagged submission; `POST /object/formSubmissions/:id/resolve-review`, body `{choice}` |
|
|
31
|
-
| `sdk.forms.settings.get` | `get()` | `Promise<{defaultRegion?, turnstileSiteKey?, turnstileSecretRef?}>` | Read the account-level `formsAccountSettings` singleton; `GET /forms/settings` |
|
|
32
|
-
| `sdk.forms.settings.set` | `set(patch: {defaultRegion?, turnstileSiteKey?, turnstileSecret?})` | `Promise<Object>` | Merge-patch the singleton; `turnstileSecret` is plaintext in, never echoed back; `PUT /forms/settings` |
|
|
33
|
-
| `sdk.forms.settings.setForm` | `setForm(formId: string, patch: {captchaProvider?, turnstileSiteKey?, turnstileSecret?})` | `Promise<Object>` | Per-form Turnstile override; `PUT /forms/:formId/settings` |
|
|
34
|
-
| `sdk.forms.health.get` | `get(formId: string)` | `Promise<{tiles, sparkline, fields, driftAlerts, windowDays}>` | Health tab data (tiles, 30-day sparkline, per-field fill rate, drift alerts); `GET /forms/:formId/health` |
|
|
35
|
-
| `sdk.forms.regeneratePublicKey` | `regeneratePublicKey(formId: string)` | `Promise<{publicKey: string}>` | Mint a new `publicKey`, invalidating every embed using the old one (destructive); `POST /forms/:formId/regenerate-key` |
|
|
36
|
-
| `sdk.forms.previewToken` | `previewToken(formId: string)` | `Promise<{token: string, expiresIn: number}>` | Mint a signed 15-min draft-preview token; `POST /forms/:formId/preview-token` |
|
|
37
|
-
|
|
38
|
-
Sources: `PublicService.js:46-100`, `SubmissionsService.js:28-79`, `SettingsService.js:28-72`,
|
|
39
|
-
`HealthService.js:28-34`, `forms.js:20-63`.
|
|
40
|
-
|
|
41
|
-
## Auth model
|
|
42
|
-
|
|
43
|
-
| Group | Methods | Session/JWT required? | Mechanism |
|
|
44
|
-
|---|---|---|---|
|
|
45
|
-
| Public | `public.submit`, `public.upload` | No | `internalRequest(..., forceFetch=true)` — `PublicService.js:46-62,77-100` explicitly pass `true` as the 5th arg. No `sdk.token` involved; auth is `publicKey` + server-side origin/domainAllowlist check, not a session (comment `PublicService.js:1-8`, mirrors `WebchatVisitorService`/`VisitorService` pattern). |
|
|
46
|
-
| Agent-authenticated | `submissions.*`, `settings.*`, `health.get`, `regeneratePublicKey`, `previewToken` | Yes | `internalRequest(sdk, endpoint, method, {...})` with no `forceFetch`/`httpOnly` flag — routed through the SDK's normal transport (socket/HTTP) using `sdk.token`, same as any other authenticated service call (e.g. `WebchatWidgetsService.get()`). |
|
|
47
|
-
|
|
48
|
-
`internalRequest`'s `forceFetch` param (`base.js:22-38`) skips optional transports and goes
|
|
49
|
-
HTTP-only — used for the two public methods because they're one-shot calls from a visitor
|
|
50
|
-
page with no socket session, same convention as file upload/download elsewhere in the SDK.
|
|
51
|
-
|
|
52
|
-
Both `public.submit` and `public.upload` also pass `credentials: 'omit'` in the request
|
|
53
|
-
params (`PublicService.js:46-62,77-100`; consumed by `base.js` `_httpRequest`, which
|
|
54
|
-
otherwise defaults browser fetches to `credentials:'include'`). This surface is called
|
|
55
|
-
from third-party origins (a marketing site, not an app1 base domain), and app1-api's CORS
|
|
56
|
-
only sends `access-control-allow-credentials` for trusted app1 base domains — a browser
|
|
57
|
-
rejects the entire response for a `credentials:'include'` fetch when that header is
|
|
58
|
-
missing, even though these endpoints don't use cookies for auth. `sdk.webchat.visitor.*`
|
|
59
|
-
(`services/webchat/VisitorService.js`) has the same third-party-origin exposure and sets
|
|
60
|
-
the same option for the same reason.
|
|
61
|
-
|
|
62
|
-
**Known SDK-surface gap (not a code bug, documented in-file):** `submissions.*` and
|
|
63
|
-
`settings.get/set` call server routes that, per their own doc comments, were not yet built
|
|
64
|
-
as of P2 (`SubmissionsService.js:7-16`, `SettingsService.js:7-17` — "GAP (forms-v2 P2) ...
|
|
65
|
-
the server-side routes these call do NOT exist yet"). `forms-v2-progress.md` marks P5 done
|
|
66
|
-
and lists "forms account settings API" and "reprocess/mark-not-spam/review actions" as
|
|
67
|
-
shipped, so these comments are likely stale relative to the API side — the SDK client code
|
|
68
|
-
itself was not re-verified against `app1-api` route files in this pass (out of scope: this
|
|
69
|
-
README covers the SDK repo only). Flagged in Deviations below.
|
|
70
|
-
|
|
71
|
-
## Example — browser handler mode (public form submission)
|
|
72
|
-
|
|
73
|
-
Realistic embed-adjacent usage: a page with its own HTML/`<form>` calling the SDK directly
|
|
74
|
-
(the `forms.public` surface backs both the generated embed script and any hand-rolled
|
|
75
|
-
integration like this).
|
|
76
|
-
|
|
77
|
-
```js
|
|
78
|
-
import { BaseSDK, internalRequest } from '@unboundcx/sdk/base.js';
|
|
79
|
-
import { FormsPublicService } from '@unboundcx/sdk/services/forms/PublicService.js';
|
|
80
|
-
|
|
81
|
-
// Minimal public-only instance — namespace or a custom baseURL, NEVER sdk.token.
|
|
82
|
-
// Importing only base.js + PublicService.js avoids pulling in the full SDK's
|
|
83
|
-
// service tree (some of which have bundler-unfriendly static imports that broke
|
|
84
|
-
// marketing_unbound_cx's browser build — see CHANGELOG 4.13.90).
|
|
85
|
-
const sdk = new BaseSDK({ namespace: 'masterc' });
|
|
86
|
-
const forms = new FormsPublicService(sdk);
|
|
87
|
-
|
|
88
|
-
document.querySelector('#lead-form').addEventListener('submit', async (e) => {
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
const data = new FormData(e.target);
|
|
91
|
-
|
|
92
|
-
await forms.submit(
|
|
93
|
-
'PUBLIC_KEY_HERE',
|
|
94
|
-
{
|
|
95
|
-
email: data.get('email'),
|
|
96
|
-
name: data.get('name'),
|
|
97
|
-
interests: data.getAll('interests'), // arrays kept as arrays
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
context: {
|
|
101
|
-
utm_source: new URLSearchParams(location.search).get('utm_source'),
|
|
102
|
-
referrer: document.referrer,
|
|
103
|
-
landingUrl: location.href,
|
|
104
|
-
},
|
|
105
|
-
idempotencyKey: crypto.randomUUID(),
|
|
106
|
-
},
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
`context` is spread onto the top level of the request body alongside `fields` (not nested
|
|
112
|
-
under a `_context` key — that was a bug fixed in 4.13.89, `PublicService.js:10-21` +
|
|
113
|
-
CHANGELOG). Control fields (`_idempotencyKey`, `_captchaToken`) are sent with a leading
|
|
114
|
-
underscore so the server can distinguish them from real form values (`PublicService.js:51-53`).
|
|
115
|
-
|
|
116
|
-
## Example — server-side submit (Node)
|
|
117
|
-
|
|
118
|
-
```js
|
|
119
|
-
import UnboundSDK from '@unboundcx/sdk';
|
|
120
|
-
|
|
121
|
-
// GOTCHA: in Node, UnboundSDK's constructor does NOT use a literal `baseURL` you pass
|
|
122
|
-
// unless you pass it explicitly as `options.baseURL` — and even then only via the
|
|
123
|
-
// object-form constructor. If you only pass `namespace` (the common case), Node builds
|
|
124
|
-
// the URL as `https://<namespace||'login'>.${process.env.API_BASE_URL || 'api.unbound.cx'}`
|
|
125
|
-
// (base.js:79-82). So:
|
|
126
|
-
// - No API_BASE_URL env var set -> requests go to https://masterc.api.unbound.cx (WRONG
|
|
127
|
-
// for dev — hits the real production-shaped host, not dev-d01).
|
|
128
|
-
// - API_BASE_URL is treated as a DOMAIN SUFFIX appended after the namespace, not a full
|
|
129
|
-
// URL override — set it to e.g. `dev-d01.app1svc.com`, not `https://api.dev-d01...`.
|
|
130
|
-
process.env.API_BASE_URL = 'dev-d01.app1svc.com';
|
|
131
|
-
|
|
132
|
-
const sdk = new UnboundSDK({ namespace: 'masterc', token: process.env.AGENT_JWT });
|
|
133
|
-
|
|
134
|
-
const health = await sdk.forms.health.get('formIdHere');
|
|
135
|
-
console.log(health.tiles);
|
|
136
|
-
|
|
137
|
-
// Public submit also works from Node (no browser needed) since forms.public.submit()
|
|
138
|
-
// only requires FormData for upload(), not submit():
|
|
139
|
-
await sdk.forms.public.submit('PUBLIC_KEY_HERE', { email: 'test@example.com' });
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Verified against `base.js:56-82` (`_initializeEnvironment`, Node branch) and
|
|
143
|
-
`base.js:107-134` (`setNamespace`, Node branch uses the same `!this._constructorBaseURL`
|
|
144
|
-
guard) — this gotcha is still true for this SDK's client construction as of `4.13.92`.
|
|
145
|
-
Matches the repo memory note "SDK Node baseURL gotcha."
|
|
146
|
-
|
|
147
|
-
## Deviations from plan / progress docs
|
|
148
|
-
|
|
149
|
-
1. **Health service exists but isn't in the precheck's SDK contract.** `forms-v2-precheck.md`
|
|
150
|
-
§5 only specifies `public`, `submissions`, `settings` on `FormsService`; `health` (P8,
|
|
151
|
-
`HealthService.js`) and the two gap-closure methods `regeneratePublicKey`/`previewToken`
|
|
152
|
-
(added directly on `FormsService`, not a sub-service) were added later and aren't in the
|
|
153
|
-
precheck's code sample — expected drift for a precheck doc frozen at P0, not a bug.
|
|
154
|
-
2. **`submissions.*` / `settings.get`/`settings.set` carry "route may not exist yet" comments
|
|
155
|
-
still in the source** (`SubmissionsService.js:7-16`, `SettingsService.js:7-17`), even
|
|
156
|
-
though `forms-v2-progress.md` marks P5 ("Captcha gate ... forms account settings API,
|
|
157
|
-
submission reprocess/mark-not-spam/review actions") as done. This README does not resolve
|
|
158
|
-
the discrepancy — it was not in scope to audit `app1-api`'s route files — but flags it as
|
|
159
|
-
a live contradiction between the sdk source comments and the progress ledger. Reported
|
|
160
|
-
back to Cameron per task instructions.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from "../../base.js";
|
|
2
|
-
|
|
3
|
-
// Agent-authenticated `formsAccountSettings` singleton -- `sdk.forms.settings`
|
|
4
|
-
// (defaultRegion, turnstileSiteKey, turnstileSecretRef; plan §6.5's
|
|
5
|
-
// account-settings page, `/app/setup/forms/account-settings`).
|
|
6
|
-
//
|
|
7
|
-
// Methods call:
|
|
8
|
-
// - get() → GET /forms/settings (services/forms/routes.js)
|
|
9
|
-
// - set() → PUT /forms/settings (services/forms/routes.js)
|
|
10
|
-
// - setForm() → PUT /forms/:id/settings (services/forms/routes.js)
|
|
11
|
-
export class FormsSettingsService {
|
|
12
|
-
constructor(sdk) {
|
|
13
|
-
this.sdk = sdk;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @returns {Promise<{defaultRegion?:string, turnstileSiteKey?:string, turnstileSecretRef?:string}>}
|
|
18
|
-
* `turnstileSecretRef` is a reference/handle only -- the raw secret
|
|
19
|
-
* never round-trips to the client.
|
|
20
|
-
*/
|
|
21
|
-
async get() {
|
|
22
|
-
return internalRequest(this.sdk, "/forms/settings", "GET");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Merge-patch the singleton row.
|
|
27
|
-
* @param {Object} patch
|
|
28
|
-
* @param {string} [patch.defaultRegion]
|
|
29
|
-
* @param {string} [patch.turnstileSiteKey]
|
|
30
|
-
* @param {string} [patch.turnstileSecret] - Plaintext; server stores only
|
|
31
|
-
* `turnstileSecretRef` and never echoes the raw value back.
|
|
32
|
-
* @returns {Promise<Object>}
|
|
33
|
-
*/
|
|
34
|
-
async set(patch) {
|
|
35
|
-
return internalRequest(this.sdk, "/forms/settings", "PUT", {
|
|
36
|
-
body: { ...(patch || {}) },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Review fix -- per-form Turnstile override (Settings tab's "Secret key
|
|
42
|
-
* (override)" field). Mirrors `set()`: the raw secret goes under
|
|
43
|
-
* `turnstileSecret`, never the stored `turnstileSecretRef` column name.
|
|
44
|
-
* Server route: `PUT /forms/:id/settings`
|
|
45
|
-
* (app1-api/src/services/forms/controllers/putFormSettings.js).
|
|
46
|
-
* @param {string} formId
|
|
47
|
-
* @param {Object} patch
|
|
48
|
-
* @param {string|null} [patch.captchaProvider]
|
|
49
|
-
* @param {string|null} [patch.turnstileSiteKey]
|
|
50
|
-
* @param {string} [patch.turnstileSecret] - Plaintext; omit (or send '')
|
|
51
|
-
* to leave the currently stored secret untouched.
|
|
52
|
-
* @returns {Promise<Object>}
|
|
53
|
-
*/
|
|
54
|
-
async setForm(formId, patch) {
|
|
55
|
-
this.sdk.validateParams(
|
|
56
|
-
{ formId },
|
|
57
|
-
{ formId: { type: "string", required: true } },
|
|
58
|
-
);
|
|
59
|
-
return internalRequest(
|
|
60
|
-
this.sdk,
|
|
61
|
-
`/forms/${encodeURIComponent(formId)}/settings`,
|
|
62
|
-
"PUT",
|
|
63
|
-
{ body: { ...(patch || {}) } },
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from "../../base.js";
|
|
2
|
-
|
|
3
|
-
// Agent-authenticated formSubmissions actions -- `sdk.forms.submissions`.
|
|
4
|
-
// Normal agent-token path (no forceFetch, no authHeaders), same as any
|
|
5
|
-
// other authenticated service method (e.g. WebchatWidgetsService.get()).
|
|
6
|
-
//
|
|
7
|
-
// Methods call:
|
|
8
|
-
// - reprocess() → POST /object/formSubmissions/:id/reprocess (api objects/routes.js)
|
|
9
|
-
// - markNotSpam() → POST /object/formSubmissions/:id/mark-not-spam (api objects/routes.js)
|
|
10
|
-
// - resolveReview() → POST /object/formSubmissions/:id/resolve-review (api objects/routes.js)
|
|
11
|
-
export class FormsSubmissionsService {
|
|
12
|
-
constructor(sdk) {
|
|
13
|
-
this.sdk = sdk;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Re-run the pipeline for an existing submission from its stored
|
|
18
|
-
* rawFields (marks the new submission's `reprocessedFromId`).
|
|
19
|
-
* @param {string} id - formSubmissions id.
|
|
20
|
-
* @returns {Promise<Object>}
|
|
21
|
-
*/
|
|
22
|
-
async reprocess(id) {
|
|
23
|
-
this.sdk.validateParams({ id }, { id: { type: "string", required: true } });
|
|
24
|
-
return internalRequest(
|
|
25
|
-
this.sdk,
|
|
26
|
-
`/object/formSubmissions/${id}/reprocess`,
|
|
27
|
-
"POST",
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Clear a submission's spam status (Quarantine "Not spam" action, D10).
|
|
33
|
-
* @param {string} id - formSubmissions id.
|
|
34
|
-
* @returns {Promise<Object>}
|
|
35
|
-
*/
|
|
36
|
-
async markNotSpam(id) {
|
|
37
|
-
this.sdk.validateParams({ id }, { id: { type: "string", required: true } });
|
|
38
|
-
return internalRequest(
|
|
39
|
-
this.sdk,
|
|
40
|
-
`/object/formSubmissions/${id}/mark-not-spam`,
|
|
41
|
-
"POST",
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resolve a submission flagged `identityConflict`/`review` (D16).
|
|
47
|
-
* @param {string} id - formSubmissions id.
|
|
48
|
-
* @param {string} choice - Which candidate record to keep/link; shape is
|
|
49
|
-
* whatever the Review queue UI (P4) settles on -- documented here as a
|
|
50
|
-
* passthrough until that's built.
|
|
51
|
-
* @returns {Promise<Object>}
|
|
52
|
-
*/
|
|
53
|
-
async resolveReview(id, choice) {
|
|
54
|
-
this.sdk.validateParams(
|
|
55
|
-
{ id, choice },
|
|
56
|
-
{
|
|
57
|
-
id: { type: "string", required: true },
|
|
58
|
-
choice: { type: "string", required: true },
|
|
59
|
-
},
|
|
60
|
-
);
|
|
61
|
-
return internalRequest(
|
|
62
|
-
this.sdk,
|
|
63
|
-
`/object/formSubmissions/${id}/resolve-review`,
|
|
64
|
-
"POST",
|
|
65
|
-
{ body: { choice } },
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
package/services/forms.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from '../base.js';
|
|
2
|
-
import { FormsPublicService } from './forms/PublicService.js';
|
|
3
|
-
import { FormsSubmissionsService } from './forms/SubmissionsService.js';
|
|
4
|
-
import { FormsSettingsService } from './forms/SettingsService.js';
|
|
5
|
-
import { FormsHealthService } from './forms/HealthService.js';
|
|
6
|
-
|
|
7
|
-
// Forms v2 (forms-v2-plan.md §7 / forms-v2-precheck.md §5) -- `sdk.forms`.
|
|
8
|
-
// `public` needs no agent auth (VisitorService pattern, publicKey-scoped);
|
|
9
|
-
// `submissions`/`settings`/`health` are normal agent-token calls.
|
|
10
|
-
export class FormsService {
|
|
11
|
-
constructor(sdk) {
|
|
12
|
-
this.sdk = sdk;
|
|
13
|
-
this.public = new FormsPublicService(sdk);
|
|
14
|
-
this.submissions = new FormsSubmissionsService(sdk);
|
|
15
|
-
this.settings = new FormsSettingsService(sdk);
|
|
16
|
-
this.health = new FormsHealthService(sdk);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Gap closure (task item 2, D1/D20). Mints a brand new `forms.publicKey`
|
|
21
|
-
* and overwrites the old one -- every embed snippet/HTML still carrying
|
|
22
|
-
* the old key stops resolving immediately (see the server route's doc
|
|
23
|
-
* comment). A destructive action; callers should confirm with the user
|
|
24
|
-
* before invoking (FormIdentitySettingsCard.svelte's "Regenerate" flow).
|
|
25
|
-
* Server route: `POST /forms/:id/regenerate-key`
|
|
26
|
-
* (app1-api/src/services/forms/controllers/regenerateFormPublicKey.js).
|
|
27
|
-
* @param {string} formId
|
|
28
|
-
* @returns {Promise<{publicKey:string}>}
|
|
29
|
-
*/
|
|
30
|
-
async regeneratePublicKey(formId) {
|
|
31
|
-
this.sdk.validateParams(
|
|
32
|
-
{ formId },
|
|
33
|
-
{ formId: { type: 'string', required: true } },
|
|
34
|
-
);
|
|
35
|
-
return internalRequest(
|
|
36
|
-
this.sdk,
|
|
37
|
-
`/forms/${encodeURIComponent(formId)}/regenerate-key`,
|
|
38
|
-
'POST',
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Gap closure (task item 3, D19). Mints a signed, 15-min-TTL token
|
|
44
|
-
* scoped to this exact formId so a `draft`/non-`active` form's real
|
|
45
|
-
* embed script can be requested via
|
|
46
|
-
* `GET /f/:publicKey.js?preview=<token>` without making the form
|
|
47
|
-
* publicly live. Server route: `POST /forms/:id/preview-token`
|
|
48
|
-
* (app1-api/src/services/forms/controllers/mintFormPreviewToken.js);
|
|
49
|
-
* verify side has been live since P2 (formEmbedPublicKey.js).
|
|
50
|
-
* @param {string} formId
|
|
51
|
-
* @returns {Promise<{token:string, expiresIn:number}>}
|
|
52
|
-
*/
|
|
53
|
-
async previewToken(formId) {
|
|
54
|
-
this.sdk.validateParams(
|
|
55
|
-
{ formId },
|
|
56
|
-
{ formId: { type: 'string', required: true } },
|
|
57
|
-
);
|
|
58
|
-
return internalRequest(
|
|
59
|
-
this.sdk,
|
|
60
|
-
`/forms/${encodeURIComponent(formId)}/preview-token`,
|
|
61
|
-
'POST',
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
}
|