@splitin/verification-adapter-sdk 0.1.0-beta.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/LICENSE +21 -0
- package/NOTICE +32 -0
- package/README.md +15 -0
- package/dist/conformance.cjs +727 -0
- package/dist/conformance.cjs.map +1 -0
- package/dist/conformance.d.cts +20 -0
- package/dist/conformance.d.ts +20 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +720 -0
- package/dist/conformance.js.map +1 -0
- package/dist/contracts-5VqcOmjU.d.cts +388 -0
- package/dist/contracts-5VqcOmjU.d.ts +388 -0
- package/dist/contracts-5VqcOmjU.d.ts.map +1 -0
- package/dist/fakes.cjs +543 -0
- package/dist/fakes.cjs.map +1 -0
- package/dist/fakes.d.cts +37 -0
- package/dist/fakes.d.ts +37 -0
- package/dist/fakes.d.ts.map +1 -0
- package/dist/fakes.js +538 -0
- package/dist/fakes.js.map +1 -0
- package/dist/index.cjs +1187 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +302 -0
- package/dist/index.d.ts +302 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1124 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/fakes.d.cts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { V as VerificationAdapterV1, a as ProviderManifestV1, b as ProviderRuntimeContext, j as ProviderAttemptCommand, k as ProviderAttemptResult, D as ProviderResourceCommand, r as ProviderLaunchEnvelope, c as NormalizedProviderSnapshot, H as ProviderRetryCommand, u as ProviderOperationResult, x as ProviderRedactionCommand, y as ProviderRedactionResult, a7 as VerifiedWebhookEnvelope, N as NormalizedProviderEvent } from './contracts-5VqcOmjU.cjs';
|
|
2
|
+
|
|
3
|
+
type FakeScenario = 'success' | 'input_required' | 'processing' | 'verified' | 'decline' | 'failure' | 'manual_review' | 'timeout' | 'rate_limit' | 'malformed_response' | 'unknown_state' | 'cancellation' | 'resume' | 'asynchronous_redaction' | 'retryable_provider_failure';
|
|
4
|
+
declare const fakeProviderManifest: ProviderManifestV1;
|
|
5
|
+
declare class FakeVerificationAdapter implements VerificationAdapterV1<Record<string, never>> {
|
|
6
|
+
readonly contractVersion: "1.0.0";
|
|
7
|
+
readonly manifest: ProviderManifestV1;
|
|
8
|
+
readonly provider = "test_fake";
|
|
9
|
+
readonly environment: "sandbox";
|
|
10
|
+
readonly runtime: ProviderRuntimeContext<Record<string, never>>;
|
|
11
|
+
private readonly scenario;
|
|
12
|
+
private readonly resources;
|
|
13
|
+
constructor(scenario?: FakeScenario, runtime?: ProviderRuntimeContext<Record<string, never>>);
|
|
14
|
+
validateConfiguration(): void;
|
|
15
|
+
createAttempt(command: ProviderAttemptCommand): Promise<ProviderAttemptResult>;
|
|
16
|
+
resumeAttempt(command: ProviderResourceCommand): Promise<ProviderLaunchEnvelope>;
|
|
17
|
+
retrieveAttempt(command: ProviderResourceCommand): Promise<NormalizedProviderSnapshot>;
|
|
18
|
+
retryAttempt(command: ProviderRetryCommand): Promise<ProviderAttemptResult>;
|
|
19
|
+
cancelAttempt(_command: ProviderResourceCommand): Promise<ProviderOperationResult>;
|
|
20
|
+
redactSubject(_command: ProviderRedactionCommand): Promise<ProviderRedactionResult>;
|
|
21
|
+
verifyWebhook(): Promise<VerifiedWebhookEnvelope>;
|
|
22
|
+
normalizeWebhook(_input: VerifiedWebhookEnvelope): Promise<NormalizedProviderEvent>;
|
|
23
|
+
private throwIfErrorScenario;
|
|
24
|
+
private launch;
|
|
25
|
+
}
|
|
26
|
+
declare function createFakeAdapterForScenario(scenario: FakeScenario): FakeVerificationAdapter;
|
|
27
|
+
/** Intentionally incomplete adapter used to assert diagnostic quality. */
|
|
28
|
+
declare class IncompleteVerificationAdapter {
|
|
29
|
+
readonly provider = "incomplete_fixture";
|
|
30
|
+
readonly environment = "sandbox";
|
|
31
|
+
readonly contractVersion: "1.0.0";
|
|
32
|
+
readonly manifest: ProviderManifestV1;
|
|
33
|
+
readonly runtime: ProviderRuntimeContext<{}>;
|
|
34
|
+
validateConfiguration(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { type FakeScenario, FakeVerificationAdapter, IncompleteVerificationAdapter, createFakeAdapterForScenario, fakeProviderManifest };
|
package/dist/fakes.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { V as VerificationAdapterV1, a as ProviderManifestV1, b as ProviderRuntimeContext, j as ProviderAttemptCommand, k as ProviderAttemptResult, D as ProviderResourceCommand, r as ProviderLaunchEnvelope, c as NormalizedProviderSnapshot, H as ProviderRetryCommand, u as ProviderOperationResult, x as ProviderRedactionCommand, y as ProviderRedactionResult, a7 as VerifiedWebhookEnvelope, N as NormalizedProviderEvent } from './contracts-5VqcOmjU.js';
|
|
2
|
+
|
|
3
|
+
type FakeScenario = 'success' | 'input_required' | 'processing' | 'verified' | 'decline' | 'failure' | 'manual_review' | 'timeout' | 'rate_limit' | 'malformed_response' | 'unknown_state' | 'cancellation' | 'resume' | 'asynchronous_redaction' | 'retryable_provider_failure';
|
|
4
|
+
declare const fakeProviderManifest: ProviderManifestV1;
|
|
5
|
+
declare class FakeVerificationAdapter implements VerificationAdapterV1<Record<string, never>> {
|
|
6
|
+
readonly contractVersion: "1.0.0";
|
|
7
|
+
readonly manifest: ProviderManifestV1;
|
|
8
|
+
readonly provider = "test_fake";
|
|
9
|
+
readonly environment: "sandbox";
|
|
10
|
+
readonly runtime: ProviderRuntimeContext<Record<string, never>>;
|
|
11
|
+
private readonly scenario;
|
|
12
|
+
private readonly resources;
|
|
13
|
+
constructor(scenario?: FakeScenario, runtime?: ProviderRuntimeContext<Record<string, never>>);
|
|
14
|
+
validateConfiguration(): void;
|
|
15
|
+
createAttempt(command: ProviderAttemptCommand): Promise<ProviderAttemptResult>;
|
|
16
|
+
resumeAttempt(command: ProviderResourceCommand): Promise<ProviderLaunchEnvelope>;
|
|
17
|
+
retrieveAttempt(command: ProviderResourceCommand): Promise<NormalizedProviderSnapshot>;
|
|
18
|
+
retryAttempt(command: ProviderRetryCommand): Promise<ProviderAttemptResult>;
|
|
19
|
+
cancelAttempt(_command: ProviderResourceCommand): Promise<ProviderOperationResult>;
|
|
20
|
+
redactSubject(_command: ProviderRedactionCommand): Promise<ProviderRedactionResult>;
|
|
21
|
+
verifyWebhook(): Promise<VerifiedWebhookEnvelope>;
|
|
22
|
+
normalizeWebhook(_input: VerifiedWebhookEnvelope): Promise<NormalizedProviderEvent>;
|
|
23
|
+
private throwIfErrorScenario;
|
|
24
|
+
private launch;
|
|
25
|
+
}
|
|
26
|
+
declare function createFakeAdapterForScenario(scenario: FakeScenario): FakeVerificationAdapter;
|
|
27
|
+
/** Intentionally incomplete adapter used to assert diagnostic quality. */
|
|
28
|
+
declare class IncompleteVerificationAdapter {
|
|
29
|
+
readonly provider = "incomplete_fixture";
|
|
30
|
+
readonly environment = "sandbox";
|
|
31
|
+
readonly contractVersion: "1.0.0";
|
|
32
|
+
readonly manifest: ProviderManifestV1;
|
|
33
|
+
readonly runtime: ProviderRuntimeContext<{}>;
|
|
34
|
+
validateConfiguration(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { type FakeScenario, FakeVerificationAdapter, IncompleteVerificationAdapter, createFakeAdapterForScenario, fakeProviderManifest };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakes.d.ts","sources":["fakes.d.ts"],"names":[],"mappings":"AAAA"}
|
package/dist/fakes.js
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var ProviderError = class extends Error {
|
|
5
|
+
constructor(code, message, options = {}) {
|
|
6
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = "ProviderError";
|
|
9
|
+
this.retryable = options.retryable ?? false;
|
|
10
|
+
this.safeCode = options.safeCode ?? code.toLowerCase();
|
|
11
|
+
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
12
|
+
}
|
|
13
|
+
code;
|
|
14
|
+
retryable;
|
|
15
|
+
safeCode;
|
|
16
|
+
retryAfterSeconds;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/identifiers.ts
|
|
20
|
+
var VERIFICATION_ADAPTER_CONTRACT_VERSION = "1.0.0";
|
|
21
|
+
var ENGINE_CONTRACT_VERSION = "1.0.0";
|
|
22
|
+
var PROVIDER_MANIFEST_SCHEMA_URN = "urn:splitin:verification:provider-manifest:v1";
|
|
23
|
+
var STANDARD_PACKAGE_CODES = [
|
|
24
|
+
"human_idv",
|
|
25
|
+
"business_kyb",
|
|
26
|
+
"associated_person_idv",
|
|
27
|
+
"ownership_review"
|
|
28
|
+
];
|
|
29
|
+
var CANONICAL_STATUSES = [
|
|
30
|
+
"created",
|
|
31
|
+
"pending_user_input",
|
|
32
|
+
"paused",
|
|
33
|
+
"processing",
|
|
34
|
+
"manual_review_required",
|
|
35
|
+
"verified",
|
|
36
|
+
"declined",
|
|
37
|
+
"failed",
|
|
38
|
+
"expired",
|
|
39
|
+
"canceled",
|
|
40
|
+
"provider_unavailable",
|
|
41
|
+
"redacted"
|
|
42
|
+
];
|
|
43
|
+
var TERMINAL_STATUSES = [
|
|
44
|
+
"verified",
|
|
45
|
+
"declined",
|
|
46
|
+
"failed",
|
|
47
|
+
"expired",
|
|
48
|
+
"canceled",
|
|
49
|
+
"redacted"
|
|
50
|
+
];
|
|
51
|
+
var STANDARD_PACKAGE_SET = new Set(STANDARD_PACKAGE_CODES);
|
|
52
|
+
new Set(CANONICAL_STATUSES);
|
|
53
|
+
new Set(TERMINAL_STATUSES);
|
|
54
|
+
var PROVIDER_CODE = /^[a-z][a-z0-9_]{1,63}$/;
|
|
55
|
+
var LAUNCHER_KEY = /^[a-z][a-z0-9_]{1,63}$/;
|
|
56
|
+
var CUSTOM_PACKAGE = /^[a-z0-9][a-z0-9-]{0,32}(?:\.[a-z0-9][a-z0-9_-]{0,63}){1,6}$/;
|
|
57
|
+
var WEBHOOK_PROTOCOL = /^(?:[a-z][a-z0-9_]{1,63}|[a-z0-9][a-z0-9-]{0,32}(?:\.[a-z0-9][a-z0-9_-]{0,63}){1,6})$/;
|
|
58
|
+
var COUNTRY = /^[A-Z]{2}$/;
|
|
59
|
+
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
60
|
+
function isProviderCode(value) {
|
|
61
|
+
return PROVIDER_CODE.test(value);
|
|
62
|
+
}
|
|
63
|
+
function isLauncherKey(value) {
|
|
64
|
+
return LAUNCHER_KEY.test(value);
|
|
65
|
+
}
|
|
66
|
+
function isStandardPackageCode(value) {
|
|
67
|
+
return STANDARD_PACKAGE_SET.has(value);
|
|
68
|
+
}
|
|
69
|
+
function isCustomPackageCode(value) {
|
|
70
|
+
return CUSTOM_PACKAGE.test(value) && !STANDARD_PACKAGE_SET.has(value);
|
|
71
|
+
}
|
|
72
|
+
function isPackageCode(value) {
|
|
73
|
+
return isStandardPackageCode(value) || isCustomPackageCode(value);
|
|
74
|
+
}
|
|
75
|
+
function isWebhookProtocol(value) {
|
|
76
|
+
return WEBHOOK_PROTOCOL.test(value);
|
|
77
|
+
}
|
|
78
|
+
function isCountryCode(value) {
|
|
79
|
+
return COUNTRY.test(value);
|
|
80
|
+
}
|
|
81
|
+
function isSemver(value) {
|
|
82
|
+
return SEMVER.test(value);
|
|
83
|
+
}
|
|
84
|
+
function majorsCompatible(left, right) {
|
|
85
|
+
return left.split(".")[0] === right.split(".")[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/schema.ts
|
|
89
|
+
var providerManifestV1JsonSchema = {
|
|
90
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
91
|
+
$id: PROVIDER_MANIFEST_SCHEMA_URN,
|
|
92
|
+
title: "Verification Provider Manifest V1",
|
|
93
|
+
type: "object",
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
required: [
|
|
96
|
+
"contractVersion",
|
|
97
|
+
"adapterVersion",
|
|
98
|
+
"engineCompatibility",
|
|
99
|
+
"provider",
|
|
100
|
+
"displayName",
|
|
101
|
+
"supportedPackages",
|
|
102
|
+
"supportedCountries",
|
|
103
|
+
"environments",
|
|
104
|
+
"capabilities",
|
|
105
|
+
"launcherKeys",
|
|
106
|
+
"launchPresentations",
|
|
107
|
+
"configurationSchemaVersion",
|
|
108
|
+
"configurationSchema",
|
|
109
|
+
"webhook",
|
|
110
|
+
"dataPolicy",
|
|
111
|
+
"retry",
|
|
112
|
+
"cancellation",
|
|
113
|
+
"redaction",
|
|
114
|
+
"apiHosts",
|
|
115
|
+
"testedApiVersions"
|
|
116
|
+
],
|
|
117
|
+
properties: {
|
|
118
|
+
contractVersion: { const: "1.0.0" },
|
|
119
|
+
adapterVersion: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
|
|
120
|
+
engineCompatibility: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
|
|
121
|
+
provider: { type: "string", pattern: "^[a-z][a-z0-9_]{1,63}$" },
|
|
122
|
+
displayName: { type: "string", minLength: 1 },
|
|
123
|
+
description: { type: "string" },
|
|
124
|
+
supportedPackages: {
|
|
125
|
+
type: "array",
|
|
126
|
+
minItems: 1,
|
|
127
|
+
uniqueItems: true,
|
|
128
|
+
items: {
|
|
129
|
+
type: "string",
|
|
130
|
+
anyOf: [
|
|
131
|
+
{ enum: ["human_idv", "business_kyb", "associated_person_idv", "ownership_review"] },
|
|
132
|
+
{ pattern: "^[a-z0-9][a-z0-9-]{0,32}(?:\\.[a-z0-9][a-z0-9_-]{0,63}){1,6}$" }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
supportedCountries: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", pattern: "^[A-Z]{2}$" } },
|
|
137
|
+
environments: { type: "array", minItems: 1, uniqueItems: true, items: { enum: ["sandbox", "production"] } },
|
|
138
|
+
capabilities: {
|
|
139
|
+
type: "object",
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
required: ["presentations", "canResume", "canRetry", "canCancel", "canRedact"],
|
|
142
|
+
properties: {
|
|
143
|
+
presentations: { type: "array", minItems: 1, uniqueItems: true, items: { enum: ["embedded", "hosted", "qr", "none"] } },
|
|
144
|
+
canResume: { type: "boolean" },
|
|
145
|
+
canRetry: { type: "boolean" },
|
|
146
|
+
canCancel: { type: "boolean" },
|
|
147
|
+
canRedact: { type: "boolean" }
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
launcherKeys: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", pattern: "^[a-z][a-z0-9_]{1,63}$" } },
|
|
151
|
+
launchPresentations: { type: "array", minItems: 1, uniqueItems: true, items: { enum: ["embedded", "hosted", "qr", "none"] } },
|
|
152
|
+
configurationSchemaVersion: { type: "string", minLength: 1 },
|
|
153
|
+
configurationSchema: { $ref: "#/$defs/jsonSchema2020" },
|
|
154
|
+
webhook: {
|
|
155
|
+
type: "object",
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
required: ["protocol", "eventFamilies"],
|
|
158
|
+
properties: {
|
|
159
|
+
protocol: { type: "string", minLength: 1, maxLength: 128 },
|
|
160
|
+
eventFamilies: { type: "array", uniqueItems: true, items: { type: "string", minLength: 1 } },
|
|
161
|
+
toleranceSeconds: { type: "integer", minimum: 1 }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
dataPolicy: {
|
|
165
|
+
type: "object",
|
|
166
|
+
additionalProperties: false,
|
|
167
|
+
required: [
|
|
168
|
+
"classifications",
|
|
169
|
+
"prohibitedPersistence",
|
|
170
|
+
"rawPayloadPersistence",
|
|
171
|
+
"browserSecretPersistence",
|
|
172
|
+
"governmentIdentifierPersistence"
|
|
173
|
+
],
|
|
174
|
+
properties: {
|
|
175
|
+
classifications: { type: "array", uniqueItems: true, items: { type: "string", minLength: 1 } },
|
|
176
|
+
prohibitedPersistence: { type: "array", uniqueItems: true, items: { type: "string", minLength: 1 } },
|
|
177
|
+
rawPayloadPersistence: { const: false },
|
|
178
|
+
browserSecretPersistence: { const: false },
|
|
179
|
+
governmentIdentifierPersistence: { const: false }
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
retry: {
|
|
183
|
+
type: "object",
|
|
184
|
+
additionalProperties: false,
|
|
185
|
+
required: ["sameResourceWhenResumable", "newAttemptAfterTerminal"],
|
|
186
|
+
properties: {
|
|
187
|
+
sameResourceWhenResumable: { type: "boolean" },
|
|
188
|
+
newAttemptAfterTerminal: { type: "boolean" }
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
cancellation: {
|
|
192
|
+
type: "object",
|
|
193
|
+
additionalProperties: false,
|
|
194
|
+
required: ["supported", "terminal"],
|
|
195
|
+
properties: { supported: { type: "boolean" }, terminal: { type: "boolean" } }
|
|
196
|
+
},
|
|
197
|
+
redaction: {
|
|
198
|
+
type: "object",
|
|
199
|
+
additionalProperties: false,
|
|
200
|
+
required: ["supported", "asynchronous"],
|
|
201
|
+
properties: {
|
|
202
|
+
supported: { type: "boolean" },
|
|
203
|
+
asynchronous: { type: "boolean" },
|
|
204
|
+
notApplicable: { type: "boolean" }
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
apiHosts: {
|
|
208
|
+
type: "array",
|
|
209
|
+
minItems: 1,
|
|
210
|
+
uniqueItems: true,
|
|
211
|
+
items: { type: "string", pattern: "^[a-z0-9.-]+$" }
|
|
212
|
+
},
|
|
213
|
+
testedApiVersions: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", minLength: 1 } }
|
|
214
|
+
},
|
|
215
|
+
$defs: {
|
|
216
|
+
jsonSchema2020: {
|
|
217
|
+
type: "object",
|
|
218
|
+
required: ["$schema", "type", "additionalProperties", "required", "properties"],
|
|
219
|
+
properties: {
|
|
220
|
+
$schema: { const: "https://json-schema.org/draft/2020-12/schema" },
|
|
221
|
+
type: { const: "object" },
|
|
222
|
+
additionalProperties: { const: false },
|
|
223
|
+
required: { type: "array", uniqueItems: true, items: { type: "string", minLength: 1 } },
|
|
224
|
+
properties: { type: "object" }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var emptyConfigurationSchema = {
|
|
230
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
231
|
+
type: "object",
|
|
232
|
+
additionalProperties: false,
|
|
233
|
+
required: [],
|
|
234
|
+
properties: {}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/manifest.ts
|
|
238
|
+
var manifestValidator = new Ajv2020({ allErrors: true, strict: false }).compile(providerManifestV1JsonSchema);
|
|
239
|
+
function defineProviderManifest(input) {
|
|
240
|
+
assertProviderManifest(input);
|
|
241
|
+
return deepFreeze(JSON.parse(JSON.stringify(input)));
|
|
242
|
+
}
|
|
243
|
+
function assertProviderManifest(manifest) {
|
|
244
|
+
if (!manifestValidator(manifest)) {
|
|
245
|
+
const first = manifestValidator.errors?.[0];
|
|
246
|
+
invalid(
|
|
247
|
+
`json_schema_${toSafeCode(first?.keyword ?? "invalid")}`,
|
|
248
|
+
`The provider adapter manifest does not satisfy ProviderManifestV1 JSON Schema at ${first?.instancePath || "/"}.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (manifest.contractVersion !== VERIFICATION_ADAPTER_CONTRACT_VERSION) invalid("contract_version");
|
|
252
|
+
if (!isProviderCode(manifest.provider)) invalid("provider_code");
|
|
253
|
+
if (!manifest.displayName.trim() || !isSemver(manifest.adapterVersion) || !isSemver(manifest.engineCompatibility)) {
|
|
254
|
+
invalid("identity");
|
|
255
|
+
}
|
|
256
|
+
if (!majorsCompatible(manifest.adapterVersion, VERIFICATION_ADAPTER_CONTRACT_VERSION)) {
|
|
257
|
+
invalid("adapter_version_incompatible", "Provider adapter major version must be compatible with contract V1.");
|
|
258
|
+
}
|
|
259
|
+
if (!majorsCompatible(manifest.engineCompatibility, ENGINE_CONTRACT_VERSION)) {
|
|
260
|
+
invalid("engine_incompatible", "Provider adapter is not compatible with this engine major version.");
|
|
261
|
+
}
|
|
262
|
+
if (!manifest.supportedPackages.length || manifest.supportedPackages.some((value) => !isPackageCode(value))) {
|
|
263
|
+
invalid("supported_packages");
|
|
264
|
+
}
|
|
265
|
+
if (manifest.supportedPackages.some((value) => !isStandardPackageCode(value) && !isCustomPackageCode(value))) {
|
|
266
|
+
invalid("supported_packages");
|
|
267
|
+
}
|
|
268
|
+
if (!manifest.supportedCountries.length || manifest.supportedCountries.some((value) => !isCountryCode(value))) {
|
|
269
|
+
invalid("supported_countries");
|
|
270
|
+
}
|
|
271
|
+
if (!manifest.launcherKeys.length || manifest.launcherKeys.some((value) => !isLauncherKey(value))) {
|
|
272
|
+
invalid("launcher_keys");
|
|
273
|
+
}
|
|
274
|
+
if (!isWebhookProtocol(manifest.webhook.protocol)) invalid("webhook_protocol");
|
|
275
|
+
if (!manifest.apiHosts.length || manifest.apiHosts.some((host) => host.includes("/") || host.includes(":"))) {
|
|
276
|
+
invalid("api_hosts", "Provider API hosts must be code-owned hostnames, not URLs or caller-supplied origins.");
|
|
277
|
+
}
|
|
278
|
+
if (manifest.dataPolicy.rawPayloadPersistence !== false || manifest.dataPolicy.browserSecretPersistence !== false || manifest.dataPolicy.governmentIdentifierPersistence !== false) {
|
|
279
|
+
invalid("data_policy");
|
|
280
|
+
}
|
|
281
|
+
if (manifest.capabilities.canCancel !== manifest.cancellation.supported) invalid("cancellation_capability");
|
|
282
|
+
if (manifest.capabilities.canRedact !== manifest.redaction.supported) invalid("redaction_capability");
|
|
283
|
+
assertConfigurationSecrets(manifest);
|
|
284
|
+
}
|
|
285
|
+
function assertConfigurationSecrets(manifest) {
|
|
286
|
+
const properties = manifest.configurationSchema.properties ?? {};
|
|
287
|
+
for (const [name, schema] of Object.entries(properties)) {
|
|
288
|
+
if (schema["x-secret"] === true && schema.type !== "string") {
|
|
289
|
+
invalid("secret_schema", `Configuration field "${name}" marked x-secret must be a string.`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function createAllowlistedHttp(allowedHosts, fetchImpl, timeoutMs = 1e4) {
|
|
294
|
+
const hosts = new Set(allowedHosts.map((host) => host.toLowerCase()));
|
|
295
|
+
return {
|
|
296
|
+
async fetch(input, init) {
|
|
297
|
+
const url = new URL(typeof input === "string" || input instanceof URL ? String(input) : input.url);
|
|
298
|
+
if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost") {
|
|
299
|
+
throw new ProviderError("INVALID_CONFIGURATION", "Provider HTTP is limited to HTTPS or loopback.", {
|
|
300
|
+
safeCode: "http_origin_denied"
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (!hosts.has(url.hostname.toLowerCase())) {
|
|
304
|
+
throw new ProviderError("INVALID_CONFIGURATION", "Provider HTTP origin is not allowlisted in the adapter manifest.", {
|
|
305
|
+
safeCode: "http_origin_denied"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
310
|
+
try {
|
|
311
|
+
const parentSignal = init?.signal;
|
|
312
|
+
if (parentSignal) {
|
|
313
|
+
if (parentSignal.aborted) controller.abort();
|
|
314
|
+
else parentSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
315
|
+
}
|
|
316
|
+
return await fetchImpl(input, { ...init, signal: controller.signal });
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (controller.signal.aborted) {
|
|
319
|
+
throw new ProviderError("TIMEOUT", "The provider HTTP request timed out.", { retryable: true, safeCode: "timeout" });
|
|
320
|
+
}
|
|
321
|
+
throw error;
|
|
322
|
+
} finally {
|
|
323
|
+
clearTimeout(timeout);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createDefaultRuntime(environment, configuration, options = {}) {
|
|
329
|
+
const fetchImpl = options.http?.fetch ?? fetch;
|
|
330
|
+
return {
|
|
331
|
+
environment,
|
|
332
|
+
configuration: Object.freeze({ ...configuration }),
|
|
333
|
+
http: options.http ?? createAllowlistedHttp(options.allowedHosts ?? ["127.0.0.1"], fetchImpl),
|
|
334
|
+
now: options.now ?? (() => /* @__PURE__ */ new Date()),
|
|
335
|
+
crypto: options.crypto ?? globalThis.crypto,
|
|
336
|
+
idempotency: options.idempotency ?? {
|
|
337
|
+
keyFor: (operation, attemptId, suppliedKey) => suppliedKey ?? `${operation}:${attemptId}`
|
|
338
|
+
},
|
|
339
|
+
logger: options.logger ?? silentLogger,
|
|
340
|
+
telemetry: options.telemetry,
|
|
341
|
+
recordHealth: options.recordHealth ?? (async () => void 0),
|
|
342
|
+
rateBudget: options.rateBudget
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
var silentLogger = Object.freeze({
|
|
346
|
+
info: () => void 0,
|
|
347
|
+
warn: () => void 0,
|
|
348
|
+
error: () => void 0
|
|
349
|
+
});
|
|
350
|
+
function invalid(safeCode, message = "The provider adapter manifest is invalid.") {
|
|
351
|
+
throw new ProviderError("INVALID_CONFIGURATION", message, { safeCode });
|
|
352
|
+
}
|
|
353
|
+
function toSafeCode(value) {
|
|
354
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
355
|
+
}
|
|
356
|
+
function deepFreeze(value) {
|
|
357
|
+
if (value && typeof value === "object") {
|
|
358
|
+
Object.freeze(value);
|
|
359
|
+
for (const nested of Object.values(value)) deepFreeze(nested);
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/fakes.ts
|
|
365
|
+
var fakeProviderManifest = defineProviderManifest({
|
|
366
|
+
contractVersion: VERIFICATION_ADAPTER_CONTRACT_VERSION,
|
|
367
|
+
adapterVersion: "1.0.0",
|
|
368
|
+
engineCompatibility: "1.0.0",
|
|
369
|
+
provider: "test_fake",
|
|
370
|
+
displayName: "Conformance Fake Provider",
|
|
371
|
+
supportedPackages: ["human_idv", "com.example.employee_check"],
|
|
372
|
+
supportedCountries: ["US"],
|
|
373
|
+
environments: ["sandbox"],
|
|
374
|
+
capabilities: { presentations: ["embedded", "hosted"], canResume: true, canRetry: true, canCancel: true, canRedact: true },
|
|
375
|
+
launcherKeys: ["test_embedded", "hosted"],
|
|
376
|
+
launchPresentations: ["embedded", "hosted"],
|
|
377
|
+
configurationSchemaVersion: "urn:splitin:verification:config:test-fake:v1",
|
|
378
|
+
configurationSchema: emptyConfigurationSchema,
|
|
379
|
+
webhook: { protocol: "none", eventFamilies: ["test"] },
|
|
380
|
+
dataPolicy: {
|
|
381
|
+
classifications: ["normalized_status"],
|
|
382
|
+
prohibitedPersistence: ["raw_webhook", "launch_secret", "document", "selfie"],
|
|
383
|
+
rawPayloadPersistence: false,
|
|
384
|
+
browserSecretPersistence: false,
|
|
385
|
+
governmentIdentifierPersistence: false
|
|
386
|
+
},
|
|
387
|
+
retry: { sameResourceWhenResumable: true, newAttemptAfterTerminal: true },
|
|
388
|
+
cancellation: { supported: true, terminal: true },
|
|
389
|
+
redaction: { supported: true, asynchronous: true },
|
|
390
|
+
apiHosts: ["127.0.0.1"],
|
|
391
|
+
testedApiVersions: ["fake-1"]
|
|
392
|
+
});
|
|
393
|
+
var SCENARIO_STATUS = {
|
|
394
|
+
success: "verified",
|
|
395
|
+
verified: "verified",
|
|
396
|
+
input_required: "pending_user_input",
|
|
397
|
+
processing: "processing",
|
|
398
|
+
decline: "declined",
|
|
399
|
+
failure: "failed",
|
|
400
|
+
manual_review: "manual_review_required",
|
|
401
|
+
timeout: "processing",
|
|
402
|
+
rate_limit: "processing",
|
|
403
|
+
malformed_response: "processing",
|
|
404
|
+
unknown_state: "processing",
|
|
405
|
+
cancellation: "canceled",
|
|
406
|
+
resume: "pending_user_input",
|
|
407
|
+
asynchronous_redaction: "processing",
|
|
408
|
+
retryable_provider_failure: "processing"
|
|
409
|
+
};
|
|
410
|
+
var FakeVerificationAdapter = class {
|
|
411
|
+
contractVersion = VERIFICATION_ADAPTER_CONTRACT_VERSION;
|
|
412
|
+
manifest = fakeProviderManifest;
|
|
413
|
+
provider = "test_fake";
|
|
414
|
+
environment = "sandbox";
|
|
415
|
+
runtime;
|
|
416
|
+
scenario;
|
|
417
|
+
resources = /* @__PURE__ */ new Map();
|
|
418
|
+
constructor(scenario = "input_required", runtime) {
|
|
419
|
+
this.scenario = scenario;
|
|
420
|
+
this.runtime = runtime ?? createDefaultRuntime("sandbox", {}, { allowedHosts: ["127.0.0.1"] });
|
|
421
|
+
}
|
|
422
|
+
validateConfiguration() {
|
|
423
|
+
if (this.runtime.environment !== "sandbox") {
|
|
424
|
+
throw new ProviderError("INVALID_CONFIGURATION", "The fake provider is sandbox-only.");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async createAttempt(command) {
|
|
428
|
+
this.runtime.idempotency.keyFor("create", command.attemptId, command.idempotencyKey);
|
|
429
|
+
const existing = this.resources.get(command.idempotencyKey);
|
|
430
|
+
const providerResourceId = existing ?? `tfr_${command.attemptId.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
431
|
+
this.resources.set(command.idempotencyKey, providerResourceId);
|
|
432
|
+
const canonicalStatus = this.scenario === "success" || this.scenario === "verified" ? "verified" : "pending_user_input";
|
|
433
|
+
return {
|
|
434
|
+
attemptId: command.attemptId,
|
|
435
|
+
providerResourceId,
|
|
436
|
+
providerStatus: canonicalStatus,
|
|
437
|
+
canonicalStatus,
|
|
438
|
+
launch: this.launch(command.attemptId, canonicalStatus)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
async resumeAttempt(command) {
|
|
442
|
+
return this.launch(command.attemptId, "pending_user_input");
|
|
443
|
+
}
|
|
444
|
+
async retrieveAttempt(command) {
|
|
445
|
+
this.throwIfErrorScenario();
|
|
446
|
+
return {
|
|
447
|
+
providerResourceId: command.providerResourceId,
|
|
448
|
+
providerStatus: SCENARIO_STATUS[this.scenario],
|
|
449
|
+
canonicalStatus: SCENARIO_STATUS[this.scenario],
|
|
450
|
+
occurredAt: this.runtime.now().toISOString(),
|
|
451
|
+
normalizedReasonCodes: [],
|
|
452
|
+
safeMetadata: { source: "fake", scenario: this.scenario }
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
async retryAttempt(command) {
|
|
456
|
+
return this.createAttempt({ ...command, attemptId: `${command.attemptId}_retry` });
|
|
457
|
+
}
|
|
458
|
+
async cancelAttempt(_command) {
|
|
459
|
+
return { accepted: true, providerStatus: "canceled", canonicalStatus: "canceled" };
|
|
460
|
+
}
|
|
461
|
+
async redactSubject(_command) {
|
|
462
|
+
if (this.scenario === "asynchronous_redaction") {
|
|
463
|
+
return { completed: false, retryable: true, disposition: "processing" };
|
|
464
|
+
}
|
|
465
|
+
return { completed: true, retryable: false, disposition: "redacted" };
|
|
466
|
+
}
|
|
467
|
+
async verifyWebhook() {
|
|
468
|
+
throw new ProviderError("UNSUPPORTED_CAPABILITY", "The fake provider does not receive webhooks.", {
|
|
469
|
+
safeCode: "webhooks_not_supported"
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async normalizeWebhook(_input) {
|
|
473
|
+
throw new ProviderError("UNSUPPORTED_CAPABILITY", "The fake provider does not receive webhooks.", {
|
|
474
|
+
safeCode: "webhooks_not_supported"
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
throwIfErrorScenario() {
|
|
478
|
+
if (this.scenario === "timeout") {
|
|
479
|
+
throw new ProviderError("TIMEOUT", "The fake provider timed out.", { retryable: true, safeCode: "timeout" });
|
|
480
|
+
}
|
|
481
|
+
if (this.scenario === "rate_limit") {
|
|
482
|
+
throw new ProviderError("RATE_LIMITED", "The fake provider is rate limited.", {
|
|
483
|
+
retryable: true,
|
|
484
|
+
safeCode: "rate_limited",
|
|
485
|
+
retryAfterSeconds: 30
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (this.scenario === "retryable_provider_failure") {
|
|
489
|
+
throw new ProviderError("RETRYABLE_PROVIDER_FAILURE", "The fake provider failed retryably.", {
|
|
490
|
+
retryable: true,
|
|
491
|
+
safeCode: "retryable_provider_failure"
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
if (this.scenario === "malformed_response") {
|
|
495
|
+
throw new ProviderError("RETRYABLE_PROVIDER_FAILURE", "The fake provider returned a malformed body.", {
|
|
496
|
+
retryable: true,
|
|
497
|
+
safeCode: "malformed_provider_response"
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (this.scenario === "unknown_state") {
|
|
501
|
+
throw new ProviderError("UNKNOWN_PROVIDER_STATE", "The fake provider returned an unknown state.", {
|
|
502
|
+
retryable: false,
|
|
503
|
+
safeCode: "unknown_provider_status"
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
launch(attemptId, canonicalStatus) {
|
|
508
|
+
return {
|
|
509
|
+
attemptId,
|
|
510
|
+
canonicalStatus,
|
|
511
|
+
launcherKey: "test_embedded",
|
|
512
|
+
presentation: "embedded",
|
|
513
|
+
providerDisclosure: "Test provider",
|
|
514
|
+
transientSecret: `test_launch_${attemptId.replace(/-/g, "")}`,
|
|
515
|
+
transientSecretExpiresAt: new Date(this.runtime.now().getTime() + 5 * 6e4).toISOString(),
|
|
516
|
+
continuationReference: `cont_${attemptId}`
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
function createFakeAdapterForScenario(scenario) {
|
|
521
|
+
return new FakeVerificationAdapter(scenario);
|
|
522
|
+
}
|
|
523
|
+
var IncompleteVerificationAdapter = class {
|
|
524
|
+
provider = "incomplete_fixture";
|
|
525
|
+
environment = "sandbox";
|
|
526
|
+
contractVersion = VERIFICATION_ADAPTER_CONTRACT_VERSION;
|
|
527
|
+
manifest = fakeProviderManifest;
|
|
528
|
+
runtime = createDefaultRuntime("sandbox", {}, { allowedHosts: ["127.0.0.1"] });
|
|
529
|
+
validateConfiguration() {
|
|
530
|
+
throw new ProviderError("INVALID_CONFIGURATION", "Incomplete adapter is missing createAttempt, resumeAttempt, retrieveAttempt, retryAttempt, cancelAttempt, redactSubject, verifyWebhook, and normalizeWebhook.", {
|
|
531
|
+
safeCode: "missing_method_create_attempt"
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export { FakeVerificationAdapter, IncompleteVerificationAdapter, createFakeAdapterForScenario, fakeProviderManifest };
|
|
537
|
+
//# sourceMappingURL=fakes.js.map
|
|
538
|
+
//# sourceMappingURL=fakes.js.map
|