@opencrvs/mosip 1.7.0-alpha.8 → 1.8.0-alpha.1

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/build/forms.js CHANGED
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- // @TODO: Yet to be implemented! Placeholders for NPM publish.
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.idVerificationFields = exports.idPendingVerificationBanner = exports.verified = exports.qr = exports.idReader = exports.esignetHidden = exports.esignetCallback = exports.popupButton = exports.esignet = void 0;
5
2
  /**
6
3
  * E-Signet popup button and hidden field form definitions
7
4
  * @example
@@ -12,184 +9,279 @@ exports.idVerificationFields = exports.idPendingVerificationBanner = exports.ver
12
9
  * ]
13
10
  * ```
14
11
  */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.idVerificationFields = exports.idReaderFields = exports.getInitialValueFromIDReader = exports.idVerificationBanner = exports.verified = exports.qr = exports.idReader = exports.esignetHidden = exports.esignetCallback = exports.popupButton = exports.esignet = void 0;
14
+ exports.esignetLoaderField = esignetLoaderField;
15
15
  /**
16
16
  *
17
17
  * @description E-Signet REDIRECT button form definition. Calls E-Signet /authorize (this field may not be supported in the latest release of OpenCRVS yet)
18
18
  *
19
19
  */
20
- const esignet = (event, sectionId, esignetAuthUrl, openIdProviderClientId, openIdProviderClaims = 'name,family_name,given_name,middle_name,birthdate,address', fieldName, callbackFieldName) => {
20
+ const esignet = (esignetAuthUrl, openIdProviderClientId, openIdProviderClaims = "name,family_name,given_name,middle_name,birthdate,address", fieldName, callbackFieldName) => {
21
21
  const url = new URL(esignetAuthUrl);
22
- url.searchParams.append('client_id', openIdProviderClientId || 'mock-client_id');
23
- url.searchParams.append('response_type', 'code');
24
- url.searchParams.append('scope', 'openid profile');
25
- url.searchParams.append('acr_values', 'mosip:idp:acr:static-code');
26
- url.searchParams.append('claims', openIdProviderClaims);
27
- url.searchParams.append('state', 'fetch-on-mount');
28
- url.searchParams.append('redirect_uri', '${window.location.origin}/drafts/${window.location.pathname.split("/")[2]}/events/${event}/${sectionId}/group/${sectionId}');
22
+ url.searchParams.append("client_id", openIdProviderClientId || "mock-client_id");
23
+ url.searchParams.append("response_type", "code");
24
+ url.searchParams.append("scope", "openid profile");
25
+ url.searchParams.append("acr_values", "mosip:idp:acr:static-code");
26
+ url.searchParams.append("claims", openIdProviderClaims);
27
+ url.searchParams.append("state", "fetch-on-mount");
28
+ url.searchParams.append("redirect_uri", "${window.location.href}");
29
29
  return {
30
30
  name: fieldName,
31
31
  validator: [],
32
32
  icon: {
33
- desktop: 'Globe',
34
- mobile: 'Fingerprint'
33
+ desktop: "Globe",
34
+ mobile: "Fingerprint",
35
35
  },
36
- type: 'LINK_BUTTON',
36
+ type: "LINK_BUTTON",
37
37
  custom: true,
38
38
  label: {
39
- id: 'views.idReader.label.eSignet',
40
- defaultMessage: 'E-signet'
39
+ id: "views.idReader.label.eSignet",
40
+ defaultMessage: "E-signet",
41
41
  },
42
42
  hideInPreview: true,
43
43
  conditionals: [
44
44
  {
45
- action: 'disable',
46
- expression: '!!$form.redirectCallbackFetch'
47
- }
45
+ action: "disable",
46
+ expression: "!!$form.redirectCallbackFetch",
47
+ },
48
48
  ],
49
+ mapping: {
50
+ mutation: {
51
+ operation: "ignoreFieldTransformer",
52
+ },
53
+ },
49
54
  options: {
50
55
  url: url.toString(),
51
56
  callback: {
52
57
  params: {
53
- state: 'fetch-on-mount'
58
+ state: "fetch-on-mount",
54
59
  },
55
- trigger: callbackFieldName
56
- }
57
- }
60
+ trigger: callbackFieldName,
61
+ },
62
+ },
58
63
  };
59
64
  };
60
65
  exports.esignet = esignet;
61
66
  const popupButton = ({
62
67
  /** URL to OpenCRVS-MOSIP gateway (e.g. https://opencrvs-mosip-gateway.farajaland.opencrvs.org) */
63
- url }) => {
68
+ url, }) => {
64
69
  return {
65
- name: 'INFORMANT_AUTHENTICATION_POPUP_BUTTON',
66
- type: 'POPUP_BUTTON',
67
- url
70
+ name: "INFORMANT_AUTHENTICATION_POPUP_BUTTON",
71
+ type: "POPUP_BUTTON",
72
+ url,
68
73
  };
69
74
  };
70
75
  exports.popupButton = popupButton;
71
76
  /**
72
77
  *
73
- * @description esignet callback button form definition. Calls server/esignet-api /esignet/get-oidp-user-info (this field may not be supported in the latest release of OpenCRVS yet)
78
+ * @description esignet callback button form definition. Calls https://mosip-api.{{hostname}}/esignet/get-oidp-user-info
74
79
  *
75
80
  */
76
- const esignetCallback = ({ fieldName, event, sectionId, getOIDPUserInfoUrl, openIdProviderClientId }) => ({
81
+ const esignetCallback = ({ fieldName, mosipAPIUserInfoUrl, openIdProviderClientId, }) => ({
77
82
  name: fieldName,
78
- type: 'HTTP',
83
+ type: "HTTP",
79
84
  custom: true,
80
85
  label: {
81
- id: 'form.field.label.empty',
82
- defaultMessage: ''
86
+ id: "form.field.label.empty",
87
+ defaultMessage: "",
83
88
  },
84
89
  validator: [],
90
+ hideInPreview: true,
91
+ mapping: {
92
+ mutation: {
93
+ operation: "ignoreFieldTransformer",
94
+ },
95
+ },
85
96
  options: {
86
- getOIDPUserInfoUrl,
97
+ url: mosipAPIUserInfoUrl,
87
98
  headers: {
88
- 'Content-type': 'application/json'
99
+ "Content-type": "application/json",
89
100
  },
90
101
  body: {
91
102
  clientId: openIdProviderClientId,
92
- redirectUri: '${window.location.origin}/drafts/${window.location.pathname.split("/")[2]}/events/${event}/${sectionId}/group/${sectionId}'
103
+ redirectUri: "${window.location.href}",
93
104
  },
94
- method: 'POST'
95
- }
105
+ method: "POST",
106
+ },
96
107
  });
97
108
  exports.esignetCallback = esignetCallback;
98
109
  const esignetHidden = () => {
99
110
  return {
100
- name: 'INFORMANT_PSUT_TOKEN',
101
- type: 'HIDDEN'
111
+ name: "INFORMANT_PSUT_TOKEN",
112
+ type: "HIDDEN",
102
113
  };
103
114
  };
104
115
  exports.esignetHidden = esignetHidden;
105
- /**
106
- *
107
- * @description QR reader type definition (this field may not be supported in the latest release of OpenCRVS yet)
108
- *
109
- */
110
116
  const idReader = (event, sectionId, conditionals, readers) => {
111
- const fieldName = 'idReader';
117
+ const fieldName = "idReader";
112
118
  const fieldId = `${event}.${sectionId}.${sectionId}-view-group.${fieldName}`;
113
119
  return {
114
120
  name: fieldName,
115
121
  customQuestionMappingId: fieldId,
116
122
  custom: true,
117
123
  required: false,
118
- type: 'ID_READER',
124
+ type: "ID_READER",
119
125
  label: {
120
- id: 'form.field.label.empty',
121
- defaultMessage: ''
126
+ id: "form.field.label.idReader",
127
+ defaultMessage: "ID verification",
122
128
  },
123
129
  hideInPreview: true,
124
- initialValue: '',
130
+ initialValue: "",
125
131
  validator: [],
126
132
  conditionals,
127
133
  dividerLabel: {
128
- id: 'views.idReader.label.or',
129
- defaultMessage: 'Or'
134
+ id: "views.idReader.label.or",
135
+ defaultMessage: "Or",
130
136
  },
131
137
  manualInputInstructionLabel: {
132
- id: 'views.idReader.label.manualInput',
133
- defaultMessage: 'Complete fields below'
138
+ id: "views.idReader.label.manualInput",
139
+ defaultMessage: "Complete fields below",
134
140
  },
135
- readers
141
+ mapping: {
142
+ mutation: {
143
+ operation: "ignoreFieldTransformer",
144
+ },
145
+ },
146
+ readers,
136
147
  };
137
148
  };
138
149
  exports.idReader = idReader;
139
- const qr = () => ({
140
- type: 'QR'
150
+ const qr = ({ validation }) => ({
151
+ type: "QR",
152
+ validation,
141
153
  });
142
154
  exports.qr = qr;
143
- const verified = (event, sectionId) => {
144
- const fieldName = 'verified';
155
+ const verified = (event, sectionId, mapping, esignetConfig) => {
156
+ const fieldName = "verified";
145
157
  const fieldId = `${event}.${sectionId}.${sectionId}-view-group.${fieldName}`;
146
158
  return {
147
- name: 'verified',
159
+ name: "verified",
148
160
  fieldId,
149
161
  customQuestionMappingId: fieldId,
150
- type: 'HIDDEN',
162
+ type: "HIDDEN",
151
163
  custom: true,
152
164
  label: {
153
- id: 'messages.empty',
154
- defaultMessage: ''
155
- },
156
- initialValue: {
157
- dependsOn: ['idReader'],
158
- expression: 'Boolean($form?.idReader)? "pending":""'
165
+ id: "form.field.label.empty",
166
+ defaultMessage: "",
159
167
  },
160
- validator: []
168
+ initialValue: esignetConfig
169
+ ? {
170
+ dependsOn: ["idReader", esignetConfig.callback.fieldName],
171
+ expression: `Boolean($form?.idReader)? "pending"
172
+ : Boolean($form?.${esignetConfig.callback.fieldName}?.data)? "authenticated"
173
+ : Boolean($form?.${esignetConfig.callback.fieldName}?.error)? "failedFetchIdDetails"
174
+ : ""`,
175
+ }
176
+ : {
177
+ dependsOn: ["idReader"],
178
+ expression: 'Boolean($form?.idReader)? "pending":""',
179
+ },
180
+ validator: [],
181
+ mapping,
161
182
  };
162
183
  };
163
184
  exports.verified = verified;
164
- const idPendingVerificationBanner = (event, sectionId) => {
165
- const fieldName = 'verified';
185
+ function capitalize(str) {
186
+ return str.charAt(0).toUpperCase() + str.slice(1);
187
+ }
188
+ const idVerificationBanner = (event, sectionId, status, conditionals = []) => {
189
+ const fieldName = "verified";
166
190
  const fieldId = `${event}.${sectionId}.${sectionId}-view-group.${fieldName}`;
167
191
  return {
168
- name: 'idPending',
169
- type: 'ID_VERIFICATION_BANNER',
192
+ name: `id${capitalize(status)}.`,
193
+ type: "ID_VERIFICATION_BANNER",
170
194
  fieldId,
171
195
  hideInPreview: true,
172
196
  custom: true,
173
- bannerType: 'pending',
174
- idFieldName: 'idReader',
197
+ bannerType: status,
198
+ idFieldName: status === "authenticated" || "failedFetchIdDetails"
199
+ ? "esignetCallback"
200
+ : "idReader",
175
201
  label: {
176
- id: 'messages.empty',
177
- defaultMessage: ''
202
+ id: "form.field.label.empty",
203
+ defaultMessage: "",
178
204
  },
179
205
  validator: [],
206
+ conditionals: conditionals.concat([
207
+ {
208
+ action: "hide",
209
+ expression: `$form?.verified !== "${status}"`,
210
+ },
211
+ ]),
212
+ };
213
+ };
214
+ exports.idVerificationBanner = idVerificationBanner;
215
+ function esignetLoaderField({ event, section, fieldName, esignetCallbackFieldName, }) {
216
+ const fieldId = `${event}.${section}.${section}-view-group.${fieldName}`;
217
+ return {
218
+ name: fieldName,
219
+ type: "LOADER",
220
+ fieldId,
221
+ hideInPreview: true,
222
+ custom: true,
223
+ label: {
224
+ id: "form.field.label.idReader",
225
+ defaultMessage: "ID verification",
226
+ },
227
+ loadingText: {
228
+ id: "form.field.label.authenticating.nid",
229
+ defaultMessage: "Fetching the person's data from E-Signet",
230
+ },
180
231
  conditionals: [
181
232
  {
182
- action: 'hide',
183
- expression: '$form?.verified !== "pending"'
184
- }
185
- ]
233
+ action: "hide",
234
+ expression: `!$form?.${esignetCallbackFieldName}?.loading`,
235
+ },
236
+ ],
186
237
  };
238
+ }
239
+ const getInitialValueFromIDReader = (fieldNameInReader) => ({
240
+ dependsOn: ["idReader", "esignetCallback"],
241
+ expression: `$form?.idReader?.${fieldNameInReader} || $form?.esignetCallback?.data?.${fieldNameInReader} || ""`,
242
+ });
243
+ exports.getInitialValueFromIDReader = getInitialValueFromIDReader;
244
+ const idReaderFields = (event, section, qrConfig, esignetConfig, verifiedCustomFieldMapping, conditionals = []) => {
245
+ const readers = [(0, exports.qr)(qrConfig)];
246
+ const fields = [
247
+ (0, exports.idReader)(event, section, conditionals.concat({
248
+ action: "hide",
249
+ expression: "$form?.verified === 'verified' || $form?.verified === 'authenticated' || $form?.verified === 'failed' || $form?.verified === 'failedFetchIdDetails' || !!$form?.esignetCallback?.loading",
250
+ }), readers),
251
+ ];
252
+ if (esignetConfig) {
253
+ readers.push((0, exports.esignet)(esignetConfig.esignetAuthUrl, esignetConfig.openIdProviderClientId, esignetConfig.openIdProviderClaims, esignetConfig.fieldName, esignetConfig.callback.fieldName));
254
+ fields.push((0, exports.esignetCallback)({
255
+ fieldName: esignetConfig.callback.fieldName,
256
+ mosipAPIUserInfoUrl: esignetConfig.callback.mosipAPIUserInfoUrl,
257
+ openIdProviderClientId: esignetConfig.openIdProviderClientId,
258
+ }));
259
+ if (esignetConfig.loaderFieldName) {
260
+ fields.push(esignetLoaderField({
261
+ event,
262
+ section,
263
+ fieldName: esignetConfig.loaderFieldName,
264
+ esignetCallbackFieldName: esignetConfig.callback.fieldName,
265
+ }));
266
+ }
267
+ return [
268
+ ...fields,
269
+ ...(0, exports.idVerificationFields)(event, section, verifiedCustomFieldMapping, conditionals, esignetConfig),
270
+ ];
271
+ }
272
+ return [
273
+ ...fields,
274
+ ...(0, exports.idVerificationFields)(event, section, verifiedCustomFieldMapping, conditionals),
275
+ ];
187
276
  };
188
- exports.idPendingVerificationBanner = idPendingVerificationBanner;
189
- const idVerificationFields = (event, sectionId) => {
277
+ exports.idReaderFields = idReaderFields;
278
+ const idVerificationFields = (event, sectionId, mapping, conditionals = [], esignetConfig) => {
190
279
  return [
191
- (0, exports.verified)(event, sectionId),
192
- (0, exports.idPendingVerificationBanner)(event, sectionId)
280
+ (0, exports.verified)(event, sectionId, mapping, esignetConfig),
281
+ (0, exports.idVerificationBanner)(event, sectionId, "verified", conditionals),
282
+ (0, exports.idVerificationBanner)(event, sectionId, "failed", conditionals),
283
+ (0, exports.idVerificationBanner)(event, sectionId, "authenticated", conditionals),
284
+ (0, exports.idVerificationBanner)(event, sectionId, "failedFetchIdDetails", conditionals),
193
285
  ];
194
286
  };
195
287
  exports.idVerificationFields = idVerificationFields;
package/build/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./forms";
2
2
  export * from "./events";
3
+ export * from "./transform";
package/build/index.js CHANGED
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./forms"), exports);
18
18
  __exportStar(require("./events"), exports);
19
+ __exportStar(require("./transform"), exports);
@@ -0,0 +1,69 @@
1
+ type IdentityInfo = {
2
+ value: string;
3
+ language: string;
4
+ };
5
+ export interface MOSIPPayload {
6
+ compositionId: string;
7
+ trackingId: string;
8
+ notification: {
9
+ recipientFullName: string;
10
+ recipientEmail: string;
11
+ recipientPhone: string;
12
+ };
13
+ requestFields: {
14
+ fullName: string;
15
+ dateOfBirth: string;
16
+ gender: string;
17
+ guardianOrParentName: string;
18
+ nationalIdNumber: string;
19
+ passportNumber: string;
20
+ drivingLicenseNumber: string;
21
+ deceasedStatus: boolean;
22
+ residenceStatus: string;
23
+ vid: string;
24
+ email: string;
25
+ phone: string;
26
+ guardianOrParentBirthCertificateNumber: string;
27
+ birthCertificateNumber: string;
28
+ addressLine1: string;
29
+ addressLine2: string;
30
+ addressLine3: string;
31
+ district: string;
32
+ village: string;
33
+ birthRegistrationCertificate: string;
34
+ passportId: string;
35
+ nationalId: string;
36
+ drivingLicenseId: string;
37
+ addressProof: string;
38
+ selectedHandles: string;
39
+ UIN: string;
40
+ deathDeclared: string;
41
+ dateOfDeath: string;
42
+ };
43
+ audit: {
44
+ uuid: string;
45
+ createdAt: string;
46
+ eventId: string;
47
+ eventName: string;
48
+ eventType: string;
49
+ hostName: string;
50
+ hostIp: string;
51
+ applicationId: string;
52
+ applicationName: string;
53
+ sessionUserId: string;
54
+ sessionUserName: string;
55
+ id: string;
56
+ idType: string;
57
+ createdBy: string;
58
+ moduleName: string;
59
+ moduleId: string;
60
+ description: string;
61
+ actionTimeStamp: string;
62
+ };
63
+ }
64
+ type Resolver<T> = (bundle: fhir3.Bundle) => T;
65
+ type ResolverMap<T> = {
66
+ [K in keyof T]?: T[K] extends IdentityInfo[] | string | boolean | number ? Resolver<T[K]> : ResolverMap<T[K]>;
67
+ };
68
+ export declare function fhirBundleToMOSIPPayload(bundle: fhir3.Bundle, resolverMap: ResolverMap<MOSIPPayload>): MOSIPPayload;
69
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fhirBundleToMOSIPPayload = fhirBundleToMOSIPPayload;
4
+ function fhirBundleToMOSIPPayload(bundle, resolverMap) {
5
+ const assignResolvers = (resolvers) => {
6
+ const result = {};
7
+ for (const key in resolvers) {
8
+ const resolver = resolvers[key];
9
+ if (typeof resolver === "function") {
10
+ result[key] = resolver(bundle);
11
+ }
12
+ else if (resolver && typeof resolver === "object") {
13
+ result[key] = assignResolvers(resolver);
14
+ }
15
+ }
16
+ return result;
17
+ };
18
+ return assignResolvers(resolverMap);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencrvs/mosip",
3
- "version": "1.7.0-alpha.8",
3
+ "version": "1.8.0-alpha.1",
4
4
  "license": "MPL-2.0",
5
5
  "main": "./build/index.js",
6
6
  "exports": {
@@ -16,9 +16,10 @@
16
16
  "devDependencies": {
17
17
  "@types/hapi__hapi": "^20.0.0",
18
18
  "@types/node-fetch": "^2.6.11",
19
+ "node-fetch": "2.6.7",
19
20
  "typescript": "^5.6.3"
20
21
  },
21
22
  "peerDependencies": {
22
- "node-fetch": "^3.3.2"
23
+ "node-fetch": "2.6.7"
23
24
  }
24
25
  }
package/src/events.ts CHANGED
@@ -1,20 +1,44 @@
1
1
  import type * as Hapi from "@hapi/hapi";
2
2
  import fetch from "node-fetch";
3
+ import { MOSIPPayload } from "./transform";
3
4
 
4
- export const mosipRegistrationHandler = ({ url }: { url: string }) =>
5
+ interface VerificationStatus {
6
+ father: boolean;
7
+ mother: boolean;
8
+ informant: boolean;
9
+ }
10
+
11
+ /**
12
+ * Replaces event registration handler in country config
13
+ */
14
+ export const mosipRegistrationHandler = ({
15
+ url,
16
+ headers,
17
+ payload,
18
+ }: {
19
+ url: string;
20
+ headers: Record<string, string>;
21
+ payload: MOSIPPayload;
22
+ }) =>
5
23
  (async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
6
- const OPENCRVS_MOSIP_GATEWAY_URL = new URL("./webhooks/opencrvs", url);
24
+ const MOSIP_API_REGISTRATION_EVENT_URL = new URL(
25
+ "./events/registration",
26
+ url,
27
+ );
7
28
 
8
- const response = await fetch(OPENCRVS_MOSIP_GATEWAY_URL, {
29
+ const response = await fetch(MOSIP_API_REGISTRATION_EVENT_URL, {
9
30
  method: "POST",
10
- body: JSON.stringify(request.payload),
11
- headers: request.headers,
31
+ body: JSON.stringify(payload),
32
+ headers: {
33
+ ...headers,
34
+ "content-type": "application/json",
35
+ },
12
36
  });
13
37
 
14
38
  if (!response.ok) {
15
39
  return h
16
40
  .response({
17
- error: "OpenCRVS-MOSIP gateway did not return a 200",
41
+ error: "OpenCRVS-MOSIP event registration route did not return a 200",
18
42
  response: await response.text(),
19
43
  })
20
44
  .code(500);
@@ -22,3 +46,56 @@ export const mosipRegistrationHandler = ({ url }: { url: string }) =>
22
46
 
23
47
  return h.response({ success: true }).code(200);
24
48
  }) satisfies Hapi.ServerRoute["handler"];
49
+
50
+ /**
51
+ * Replaces `/events/{event}/actions/sent-notification-for-review` handler in country config
52
+ */
53
+ export const mosipRegistrationForReviewHandler = ({ url }: { url: string }) =>
54
+ (async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
55
+ // Corresponds to `packages/mosip-api` /events/review -route
56
+ let response;
57
+ try {
58
+ response = await verify({ url, request });
59
+ } catch (error) {
60
+ return h
61
+ .response({
62
+ error: "OpenCRVS-MOSIP event review route did not return a 200",
63
+ response: error instanceof Error ? error.message : "Unknown error",
64
+ })
65
+ .code(500);
66
+ }
67
+
68
+ return h.response(response).code(200);
69
+ }) satisfies Hapi.ServerRoute["handler"];
70
+
71
+ /**
72
+ * Replaces `/events/{event}/actions/sent-for-approval` handler in country config
73
+ * Currently the same as `/events/{event}/actions/sent-notification-for-review`
74
+ */
75
+ export const mosipRegistrationForApprovalHandler =
76
+ mosipRegistrationForReviewHandler;
77
+
78
+ export const verify = async ({
79
+ url,
80
+ request,
81
+ }: {
82
+ url: string;
83
+ request: Hapi.Request;
84
+ }): Promise<Partial<VerificationStatus>> => {
85
+ const MOSIP_API_REVIEW_EVENT_URL = new URL("./events/review", url);
86
+
87
+ const response = await fetch(MOSIP_API_REVIEW_EVENT_URL, {
88
+ method: "POST",
89
+ body: JSON.stringify(request.payload),
90
+ headers: {
91
+ ...request.headers,
92
+ "content-type": "application/json",
93
+ },
94
+ });
95
+
96
+ if (!response.ok) {
97
+ throw new Error(await response.text());
98
+ }
99
+
100
+ return (await response.json()) satisfies Partial<VerificationStatus>;
101
+ };