@nexus-cross/kyc 1.3.4-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -0
- package/dist/chunk-2477RDFG.js +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +1 -0
- package/dist/launchVerification-oM0NtTLd.d.ts +292 -0
- package/dist/react/index.d.ts +72 -0
- package/dist/react/index.js +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @nexus-cross/kyc
|
|
2
|
+
|
|
3
|
+
KYC integration for **cross-auth**. A framework-agnostic core plus a thin React
|
|
4
|
+
adapter that wrap the cross-auth `/kyc` endpoints:
|
|
5
|
+
|
|
6
|
+
- **`GET /kyc`** — read-only. Returns the caller's identity (SIWE or social) and
|
|
7
|
+
the identity-core-api KYC status. No side effects.
|
|
8
|
+
- **`POST /kyc`** — links the caller's wallets to the project (idempotent) and,
|
|
9
|
+
when KYC isn't yet approved, starts/resumes verification (idempotent) and
|
|
10
|
+
surfaces either a hosted `verification_url` (preferred) or a Sumsub SDK token.
|
|
11
|
+
The package enters the flow accordingly — see [Launching verification](#launching-verification).
|
|
12
|
+
|
|
13
|
+
The package follows the same hexagonal shape and env-resolution style as
|
|
14
|
+
[`@nexus-cross/onramp`](../onramp): pure `core` (ports/types/use cases) →
|
|
15
|
+
`adapters` (HTTP repository + env-based endpoints) → `react` (Provider + hooks),
|
|
16
|
+
with a `createKyc()` facade on top.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pnpm add @nexus-cross/kyc
|
|
22
|
+
# React Provider/hooks (the "./react" entry) additionally need:
|
|
23
|
+
pnpm add react react-dom
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`react` is an **optional** peer — only the `@nexus-cross/kyc/react` sub-entry
|
|
27
|
+
requires it. The main entry (`createKyc`, `HttpKycRepository`, …) is React-free
|
|
28
|
+
and works in any framework or vanilla JS.
|
|
29
|
+
|
|
30
|
+
## Environment / base URL
|
|
31
|
+
|
|
32
|
+
Base URL resolution mirrors `@nexus-cross/onramp`. You normally don't set
|
|
33
|
+
anything — it picks the cross-auth host from the environment identifier:
|
|
34
|
+
|
|
35
|
+
| Environment | Base URL |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `production` (default) | `https://cross-auth.crosstoken.io/cross-auth` |
|
|
38
|
+
| `stage` | `https://stg-cross-auth.crosstoken.io/cross-auth` |
|
|
39
|
+
| `dev` | `https://dev-cross-auth.crosstoken.io/cross-auth` |
|
|
40
|
+
|
|
41
|
+
Resolution priority:
|
|
42
|
+
|
|
43
|
+
1. Explicit `baseUrl` option passed to `createKyc` / `HttpKycRepository`.
|
|
44
|
+
2. `VITE_CROSSX_AUTH_BASE_URL` (Vite) or `NEXT_PUBLIC_CROSSX_AUTH_BASE_URL` (Next.js).
|
|
45
|
+
3. Environment identifier `VITE_CROSSX_ENVIRONMENT` / `NEXT_PUBLIC_CROSSX_ENVIRONMENT`
|
|
46
|
+
/ `CROSSX_ENVIRONMENT` (`dev` | `stage` | `production`) → the table above.
|
|
47
|
+
|
|
48
|
+
Non-`https` URLs are rejected (except `http://localhost` for local dev).
|
|
49
|
+
|
|
50
|
+
## Authentication
|
|
51
|
+
|
|
52
|
+
The `/kyc` endpoints require auth. Two ways, used together:
|
|
53
|
+
|
|
54
|
+
- **Bearer token** — pass `getAccessToken`; the repository sends
|
|
55
|
+
`Authorization: Bearer <token>`. It's called on every request, so a token
|
|
56
|
+
refresh is picked up automatically without re-creating the client (don't bake
|
|
57
|
+
a stale token in). A `Bearer ` prefix in the returned value is de-duplicated.
|
|
58
|
+
- **Cookie session** — every request is sent with `credentials: 'include'`, so an
|
|
59
|
+
HttpOnly cross-auth session cookie is attached automatically. If you rely only
|
|
60
|
+
on cookies, you can omit `getAccessToken`.
|
|
61
|
+
|
|
62
|
+
### Social login extra headers
|
|
63
|
+
|
|
64
|
+
For **social** logins the backend additionally requires `X-Project-Id` **plus a
|
|
65
|
+
client identifier**, otherwise it returns **401**:
|
|
66
|
+
|
|
67
|
+
- **Web** — the client identifier is the `Origin` header, which the browser sends
|
|
68
|
+
automatically on cross-origin requests (it's a forbidden header, so this
|
|
69
|
+
package never sets it). Just make sure `projectId` is set.
|
|
70
|
+
- **Native SDK** — pass `appId` (`X-App-Id`) and `appType` (`X-App-Type`) instead.
|
|
71
|
+
|
|
72
|
+
`projectId` is sent as `X-Project-Id`. Use your embedded project id (falling back
|
|
73
|
+
to the cross project id), the same value connect-kit uses for the embedded wallet.
|
|
74
|
+
SIWE logins are authenticated by the token/cookie alone and don't need the
|
|
75
|
+
client identifier.
|
|
76
|
+
|
|
77
|
+
## Quick start — with connect-kit (recommended)
|
|
78
|
+
|
|
79
|
+
If you use `@nexus-cross/connect-kit-react`, KYC is auto-wired. Set
|
|
80
|
+
`kycEnabled: true` in the kit config; connect-kit mounts the provider and injects
|
|
81
|
+
the access token from the connected **crossx 2.0 SDK** session — **no token
|
|
82
|
+
plumbing, no `KycProvider`**:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// config
|
|
86
|
+
createConnectKitConfig({ crossProjectId, kycEnabled: true /* … */ });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { useKyc } from '@nexus-cross/connect-kit-react';
|
|
91
|
+
|
|
92
|
+
function KycButton() {
|
|
93
|
+
const { verified, status, isLoading, isStarting, refresh, startVerification } =
|
|
94
|
+
useKyc();
|
|
95
|
+
if (verified) return <span>KYC verified ✓</span>;
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<button onClick={() => void refresh()} disabled={isLoading}>Check KYC</button>
|
|
99
|
+
<button onClick={() => void startVerification()} disabled={isStarting}>
|
|
100
|
+
Start KYC
|
|
101
|
+
</button>
|
|
102
|
+
<p>status: {status ?? '—'}</p>
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`X-Project-Id` uses `embeddedProjectId` (falling back to `crossProjectId`). The
|
|
109
|
+
token is read per request, so refreshes are picked up automatically.
|
|
110
|
+
|
|
111
|
+
## Quick start — standalone (without connect-kit)
|
|
112
|
+
|
|
113
|
+
Mount `<KycProvider>` yourself and supply `getAccessToken`:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// App.tsx
|
|
117
|
+
import { KycProvider } from '@nexus-cross/kyc/react';
|
|
118
|
+
|
|
119
|
+
export function App() {
|
|
120
|
+
return (
|
|
121
|
+
<KycProvider
|
|
122
|
+
config={{
|
|
123
|
+
projectId: EMBEDDED_PROJECT_ID,
|
|
124
|
+
getAccessToken: () => accessToken, // read latest token here
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<KycButton />
|
|
128
|
+
</KycProvider>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
// KycButton.tsx
|
|
135
|
+
import { useKyc } from '@nexus-cross/kyc/react';
|
|
136
|
+
|
|
137
|
+
function KycButton() {
|
|
138
|
+
const { status, verified, isLoading, isStarting, error, refresh, startVerification } =
|
|
139
|
+
useKyc(); // autoFetch: true → GET /kyc on mount
|
|
140
|
+
|
|
141
|
+
if (verified) return <span>KYC verified ✓</span>;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div>
|
|
145
|
+
<button onClick={() => void refresh()} disabled={isLoading}>
|
|
146
|
+
{isLoading ? 'Checking…' : 'Check KYC'}
|
|
147
|
+
</button>
|
|
148
|
+
<button onClick={() => void startVerification()} disabled={isStarting}>
|
|
149
|
+
{isStarting ? 'Starting…' : 'Start KYC'}
|
|
150
|
+
</button>
|
|
151
|
+
<p>status: {status ?? '—'}</p>
|
|
152
|
+
{error && <p>error: {error.message}</p>}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`startVerification()` runs `POST /kyc` and then enters the verification flow
|
|
159
|
+
automatically (see [Launching verification](#launching-verification)). Use the
|
|
160
|
+
lower-level `start()` if you want the raw `KycIdentity` back without launching.
|
|
161
|
+
|
|
162
|
+
Pass `useKyc({ autoFetch: false })` to skip the on-mount `GET /kyc` and trigger
|
|
163
|
+
it manually via `refresh()`. `useOptionalKyc()` returns the facade or `null`
|
|
164
|
+
outside a Provider (for "enable only if mounted" UIs).
|
|
165
|
+
|
|
166
|
+
## Launching verification
|
|
167
|
+
|
|
168
|
+
`POST /kyc` (via `start()` / `startVerification()`) returns a `KycIdentity`. How
|
|
169
|
+
the verification flow is entered depends on what the backend put in it, and the
|
|
170
|
+
package picks the right path automatically:
|
|
171
|
+
|
|
172
|
+
| Response field | Behavior | Notes |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `verificationUrl` (`verification_url`) | **default** — opens the hosted URL in a new tab (`window.open`) | vendor-neutral; no CDN/CSP. This is the [recommended backend contract](../../docs/kyc/01-verification-link-proposal.md). |
|
|
175
|
+
| `sdkToken` (`kyc_token`), no URL | **fallback** — loads the Sumsub WebSDK and launches it in a fullscreen modal | Sumsub CDN coupling is isolated to the browser adapter; token refresh re-calls `POST /kyc`. |
|
|
176
|
+
| neither | throws `KycError('LAUNCH_FAILED')` | — |
|
|
177
|
+
|
|
178
|
+
- **New tab vs current window**: pass `target` (URL mode only).
|
|
179
|
+
`'newWindow'` (default) opens the URL in a new tab; `'currentWindow'`
|
|
180
|
+
navigates the current tab via `location.assign` (rely on the backend
|
|
181
|
+
`redirect` to return). The WebSDK fallback always renders as a modal in the
|
|
182
|
+
current window.
|
|
183
|
+
```ts
|
|
184
|
+
await startVerification({ target: 'currentWindow' });
|
|
185
|
+
```
|
|
186
|
+
- React: `useKyc().startVerification(opts?)` = `start()` + launch. Returns
|
|
187
|
+
`{ identity, handle }`; `handle.close()` dismisses the WebSDK modal (no-op in
|
|
188
|
+
URL mode).
|
|
189
|
+
- Framework-agnostic: `launchKycVerification(identity, opts)` from
|
|
190
|
+
`@nexus-cross/kyc`.
|
|
191
|
+
- Completion is authoritative via **webhook**; poll `GET /kyc` (`refresh()`) for
|
|
192
|
+
the final status regardless of which path was taken.
|
|
193
|
+
|
|
194
|
+
## Quick start — framework-agnostic
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { createKyc, launchKycVerification } from '@nexus-cross/kyc';
|
|
198
|
+
|
|
199
|
+
const kyc = createKyc({
|
|
200
|
+
projectId: EMBEDDED_PROJECT_ID,
|
|
201
|
+
getAccessToken: () => accessToken,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const me = await kyc.getStatus(); // GET /kyc
|
|
205
|
+
if (!me.verified) {
|
|
206
|
+
const started = await kyc.start(); // POST /kyc
|
|
207
|
+
// verificationUrl → new tab, else sdkToken → Sumsub WebSDK modal
|
|
208
|
+
await launchKycVerification(started);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## API
|
|
213
|
+
|
|
214
|
+
### `createKyc(options): Kyc`
|
|
215
|
+
|
|
216
|
+
`options` (`CreateKycOptions`):
|
|
217
|
+
|
|
218
|
+
| Option | Type | Notes |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `projectId` | `string?` | Sent as `X-Project-Id`. Required for social login. |
|
|
221
|
+
| `getAccessToken` | `() => string \| null \| undefined \| Promise<…>` | Bearer token getter, called per request. |
|
|
222
|
+
| `appId` | `string?` | Native SDK flow only → `X-App-Id`. |
|
|
223
|
+
| `appType` | `string?` | `'android' \| 'ios' \| 'windows'` → `X-App-Type`. |
|
|
224
|
+
| `baseUrl` | `string?` | Override the resolved cross-auth base URL. |
|
|
225
|
+
| `repository` | `KycRepository?` | Inject a custom/mock transport. |
|
|
226
|
+
|
|
227
|
+
Returns `{ port, getStatus(), start() }`.
|
|
228
|
+
|
|
229
|
+
### `KycIdentity`
|
|
230
|
+
|
|
231
|
+
Normalized (camelCase) form of cross-auth `KYCResp`:
|
|
232
|
+
|
|
233
|
+
| Field | Type | Source |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `status` | `'none' \| 'pending' \| 'approved' \| 'rejected_retry' \| 'rejected_final' \| 'wallet_required'` | `status` |
|
|
236
|
+
| `verified` | `boolean` | `kyc_verified` |
|
|
237
|
+
| `loginType` | `'siwe' \| 'social' \| undefined` | `login_type` |
|
|
238
|
+
| `walletAddress` | `string?` | `wallet_address` |
|
|
239
|
+
| `rejectType` | `'RETRY' \| 'FINAL' \| undefined` | `reject_type` (rejected states) |
|
|
240
|
+
| `provider` | `string?` | `provider` (e.g. `"sumsub"`) |
|
|
241
|
+
| `verificationUrl` | `string?` | `verification_url` (hosted link → opened directly) |
|
|
242
|
+
| `sdkToken` | `string?` | `kyc_token` (Sumsub SDK token, WebSDK fallback) |
|
|
243
|
+
| `sdkTokenExpiresAt` | `string?` | `kyc_expires_at` (ISO) |
|
|
244
|
+
| `email` / `nickname` / `sub` / `uuid` | `string?` | social only |
|
|
245
|
+
|
|
246
|
+
### Errors
|
|
247
|
+
|
|
248
|
+
Failures throw a `KycError` with a `code`: `MISSING_PROJECT_ID`,
|
|
249
|
+
`UNAUTHORIZED` (401/403), `STATUS_FAILED`, `START_FAILED`, `INVALID_RESPONSE`,
|
|
250
|
+
`NETWORK_ERROR`, `LAUNCH_FAILED` (verification could not be opened — popup
|
|
251
|
+
blocked, SSR, or neither `verificationUrl` nor `sdkToken` present). In React, the
|
|
252
|
+
last error is surfaced via `useKyc().error` (reads/`refresh()` capture it;
|
|
253
|
+
`start()`/`startVerification()` also throw so you can `try/catch`).
|
|
254
|
+
|
|
255
|
+
## Advanced — custom transport
|
|
256
|
+
|
|
257
|
+
Implement `KycRepository` to talk to a different gateway or to mock in tests,
|
|
258
|
+
then inject it:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { createKyc, type KycRepository } from '@nexus-cross/kyc';
|
|
262
|
+
|
|
263
|
+
const mock: KycRepository = {
|
|
264
|
+
fetchStatus: async () => ({ status: 'approved', verified: true }),
|
|
265
|
+
initVerification: async () => ({ status: 'approved', verified: true }),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const kyc = createKyc({ repository: mock });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=class extends Error{constructor(e,n,r){super(n),this.name="KycError",this.code=e,this.details=r}},I=["none","pending","approved","rejected_retry","rejected_final","wallet_required"];function g(t){return I.includes(t)?t:"none"}function m(t){return t==="siwe"||t==="social"?t:void 0}function S(t){let e=typeof t=="string"?t.toUpperCase():"";return e==="RETRY"||e==="FINAL"?e:void 0}var d=class{constructor(e){this.port=e}execute(){return this.port.getStatus()}};var p=class{constructor(e){this.port=e}execute(){return this.port.startVerification()}};var b={dev:"https://dev-cross-auth.crosstoken.io/cross-auth",stage:"https://stg-cross-auth.crosstoken.io/cross-auth",production:"https://cross-auth.crosstoken.io/cross-auth"},h={kyc:"/kyc"};function w(t){try{return import.meta.env?.[t]}catch{return}}function f(t){if(!(typeof process>"u"||!process.env))switch(t){case"NEXT_PUBLIC_CROSSX_ENVIRONMENT":return process.env.NEXT_PUBLIC_CROSSX_ENVIRONMENT;case"CROSSX_ENVIRONMENT":return process.env.CROSSX_ENVIRONMENT;case"NEXT_PUBLIC_CROSSX_AUTH_BASE_URL":return process.env.NEXT_PUBLIC_CROSSX_AUTH_BASE_URL;default:return}}function R(){switch((w("VITE_CROSSX_ENVIRONMENT")??f("NEXT_PUBLIC_CROSSX_ENVIRONMENT")??f("CROSSX_ENVIRONMENT"))?.toLowerCase()){case"dev":case"development":return"dev";case"stage":case"staging":case"stg":return"stage";default:return"production"}}function T(){let t=w("VITE_CROSSX_AUTH_BASE_URL")??f("NEXT_PUBLIC_CROSSX_AUTH_BASE_URL"),e=R(),n=t??b[e];return _(n),n}function _(t){let e;try{e=new URL(t)}catch{throw new Error(`[kyc] Invalid base URL: ${t}`)}if(e.protocol==="https:")return;let n=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname.endsWith(".local");if(!(e.protocol==="http:"&&n))throw new Error(`[kyc] base URL must be https (or http://localhost for dev). Got: ${t}`)}var A=1e4,y=class{constructor(e={}){this.projectId=e.projectId?.trim()||void 0,this.getAccessToken=e.getAccessToken,this.appId=e.appId,this.appType=e.appType,this.baseUrl=(e.baseUrl??T()).replace(/\/+$/,""),this.paths={kyc:e.paths?.kyc??h.kyc},this.timeoutMs=e.timeoutMs??A}fetchStatus(){return this.request("GET","STATUS_FAILED")}initVerification(){return this.request("POST","START_FAILED")}async request(e,n){let r=this.baseUrl+this.paths.kyc,o=new AbortController,a=setTimeout(()=>o.abort(),this.timeoutMs),i;try{i=await fetch(r,{method:e,signal:o.signal,headers:await this.buildHeaders(),credentials:"include"})}catch(l){throw new s("NETWORK_ERROR",l instanceof Error?l.message:`KYC ${e} request failed`,{cause:String(l)})}finally{clearTimeout(a)}if(i.status===401||i.status===403)throw new s("UNAUTHORIZED",`KYC ${e} returned ${i.status}`);if(!i.ok)throw new s(n,`KYC ${e} returned ${i.status}`);let c;try{c=await i.json()}catch{throw new s("INVALID_RESPONSE",`KYC ${e} response was not JSON`)}return this.parseIdentity(c)}async buildHeaders(){let e={Accept:"application/json"};if(this.projectId&&(e["X-Project-Id"]=this.projectId),this.appId&&(e["X-App-Id"]=this.appId),this.appType&&(e["X-App-Type"]=this.appType),this.getAccessToken){let n=await this.getAccessToken();n&&n.trim()&&(e.Authorization=/^bearer\s/i.test(n)?n:`Bearer ${n}`)}return e}parseIdentity(e){if(!e||typeof e!="object")throw new s("INVALID_RESPONSE","KYC response was not an object");let n=e,r=n.data&&typeof n.data=="object"?n.data:n,o=i=>typeof i=="string"&&i.trim()?i:void 0;return{status:g(r.status),verified:r.kyc_verified===!0,loginType:m(r.login_type),walletAddress:o(r.wallet_address),rejectType:S(r.reject_type),provider:o(r.provider),verificationUrl:o(r.verification_url),sdkToken:o(r.kyc_token),sdkTokenExpiresAt:o(r.kyc_expires_at),email:o(r.email),nickname:o(r.nickname),sub:o(r.sub),uuid:o(r.uuid)}}};var u=class{constructor(e={}){this.repository=e.repository??new y({projectId:e.projectId,getAccessToken:e.getAccessToken,appId:e.appId,appType:e.appType})}getStatus(){return this.repository.fetchStatus()}startVerification(){return this.repository.initVerification()}};function F(t={}){let e=new u(t),n=new d(e),r=new p(e);return{port:e,getStatus:()=>n.execute(),start:()=>r.execute()}}var E="https://static.sumsub.com/idensic/static/sns-websdk-builder.js";function v(){return new Promise((t,e)=>{if(typeof document>"u"){e(new Error("[kyc/sumsub] document is not available (SSR)"));return}if(window.snsWebSdk){t();return}let n=document.querySelector(`script[src="${E}"]`);if(n){n.addEventListener("load",()=>t()),n.addEventListener("error",()=>e(new Error("[kyc/sumsub] script failed to load")));return}let r=document.createElement("script");r.src=E,r.async=!0,r.onload=()=>t(),r.onerror=()=>e(new Error("[kyc/sumsub] script failed to load (CSP/network?)")),document.head.appendChild(r)})}async function k(t){if(await v(),!window.snsWebSdk)throw new Error("[kyc/sumsub] snsWebSdk global is not available");window.snsWebSdk.init(t.token,t.getNewToken).withConf({lang:t.lang??"ko"}).withOptions({addViewportTag:!1,adaptIosWebView:!0}).on("idCheck.onError",n=>t.onError?.(n)).on("idCheck.onApplicantStatusChanged",n=>t.onStatusChange?.(n)).build().launch(t.container)}var K={mode:"url",close:()=>{}};async function z(t,e={}){if(typeof window>"u")throw new s("LAUNCH_FAILED","window is not available (SSR)");if(t.verificationUrl){if((e.target??"newWindow")==="currentWindow")return window.location.assign(t.verificationUrl),K;if(!window.open(t.verificationUrl,"_blank",e.windowFeatures??"noopener,noreferrer"))throw new s("LAUNCH_FAILED","Popup blocked opening verification URL");return K}if(t.sdkToken)return C(t.sdkToken,e);throw new s("LAUNCH_FAILED","KYC response has neither verificationUrl nor sdkToken")}async function C(t,e){let n=document.createElement("div");n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),Object.assign(n.style,{position:"fixed",inset:"0",zIndex:"2147483000",background:"#0b0d14",display:"flex",flexDirection:"column"});let r=document.createElement("div");Object.assign(r.style,{display:"flex",alignItems:"center",justifyContent:"flex-end",padding:"10px 14px",borderBottom:"1px solid rgba(255,255,255,0.12)"});let o=document.createElement("button");o.type="button",o.textContent="\u2715",Object.assign(o.style,{border:"none",background:"transparent",color:"#fff",fontSize:"18px",cursor:"pointer",lineHeight:"1"});let a=document.createElement("div");Object.assign(a.style,{flex:"1",overflow:"auto",background:"#fff"});let i=()=>{n.remove()};o.addEventListener("click",i),r.appendChild(o),n.appendChild(r),n.appendChild(a),document.body.appendChild(n);try{await k({token:t,container:a,lang:e.lang,getNewToken:e.getFreshToken??(async()=>t),onError:e.onError,onStatusChange:e.onStatusChange})}catch(c){throw i(),new s("LAUNCH_FAILED",c instanceof Error?c.message:"Sumsub WebSDK launch failed",{cause:String(c)})}return{mode:"websdk",close:i}}export{s as a,g as b,m as c,S as d,d as e,p as f,h as g,T as h,y as i,u as j,F as k,z as l};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { K as KycPort, a as KycIdentity } from './launchVerification-oM0NtTLd.js';
|
|
2
|
+
export { A as AccessTokenGetter, B as BrowserKycAdapter, b as BrowserKycAdapterOptions, C as CreateKycOptions, D as DEFAULT_KYC_PATHS, H as HttpKycRepository, c as HttpKycRepositoryOptions, d as Kyc, e as KycEndpointPaths, f as KycEnvironment, g as KycError, h as KycErrorCode, i as KycLaunchTarget, j as KycLoginType, k as KycRejectType, l as KycRepository, m as KycStatus, n as KycVerificationHandle, o as KycVerificationMode, L as LaunchKycVerificationOptions, U as Unsubscribe, p as createKyc, q as getKycBaseUrl, r as launchKycVerification, s as normalizeKycStatus, t as normalizeLoginType, u as normalizeRejectType } from './launchVerification-oM0NtTLd.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Port 위임 + 향후 정책(캐시 무효화, 파생 상태 계산 등)이 들어갈 자리.
|
|
6
|
+
* 현 1차에서는 얇은 래퍼지만, react 훅이 use case에 의존하도록 두면 추후 확장 무중단.
|
|
7
|
+
*/
|
|
8
|
+
declare class GetKycStatusUseCase {
|
|
9
|
+
private readonly port;
|
|
10
|
+
constructor(port: KycPort);
|
|
11
|
+
execute(): Promise<KycIdentity>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 지갑 링크 + KYC 검증 시작/재개. POST /kyc는 idempotent하므로 이미 진행 중/
|
|
16
|
+
* 승인 상태에서 다시 호출해도 안전하다.
|
|
17
|
+
*/
|
|
18
|
+
declare class StartKycUseCase {
|
|
19
|
+
private readonly port;
|
|
20
|
+
constructor(port: KycPort);
|
|
21
|
+
execute(): Promise<KycIdentity>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { GetKycStatusUseCase, KycIdentity, KycPort, StartKycUseCase };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,b as r,c as t,d as o,e as c,f as p,g as y,h as s,i,j as a,k as K,l as n}from"./chunk-2477RDFG.js";export{a as BrowserKycAdapter,y as DEFAULT_KYC_PATHS,c as GetKycStatusUseCase,i as HttpKycRepository,e as KycError,p as StartKycUseCase,K as createKyc,s as getKycBaseUrl,n as launchKycVerification,r as normalizeKycStatus,t as normalizeLoginType,o as normalizeRejectType};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/** 구독 해제 함수. core(connect-kit) 의존을 피하려고 kyc 안에 자체 정의. */
|
|
2
|
+
type Unsubscribe = () => void;
|
|
3
|
+
/**
|
|
4
|
+
* 백엔드 KYC 진행 상태 (cross-auth `KYCResp.status`).
|
|
5
|
+
* - none : 아직 시작 전
|
|
6
|
+
* - pending : 심사 진행 중
|
|
7
|
+
* - approved : 승인 완료 (kyc_verified=true)
|
|
8
|
+
* - rejected_retry : 거절됐지만 재시도 가능 (reject_type=RETRY)
|
|
9
|
+
* - rejected_final : 최종 거절 (reject_type=FINAL)
|
|
10
|
+
* - wallet_required : 검증 전 지갑 링크가 필요
|
|
11
|
+
*/
|
|
12
|
+
type KycStatus = 'none' | 'pending' | 'approved' | 'rejected_retry' | 'rejected_final' | 'wallet_required';
|
|
13
|
+
/** 로그인 방식 (cross-auth `KYCResp.login_type`). */
|
|
14
|
+
type KycLoginType = 'siwe' | 'social';
|
|
15
|
+
/** 거절 사유 종류 (rejected_* 상태에서만 존재, `KYCResp.reject_type`). */
|
|
16
|
+
type KycRejectType = 'RETRY' | 'FINAL';
|
|
17
|
+
/**
|
|
18
|
+
* 호출자 신원 + KYC 상태. cross-auth `KYCResp`를 도메인 친화(camelCase)로
|
|
19
|
+
* 정규화한 형태. 외부 스키마(snake_case)는 어댑터가 이 타입으로 변환한다.
|
|
20
|
+
*/
|
|
21
|
+
interface KycIdentity {
|
|
22
|
+
/** none|pending|approved|rejected_retry|rejected_final|wallet_required */
|
|
23
|
+
readonly status: KycStatus;
|
|
24
|
+
/** identity-core-api 기준 승인 여부 (`kyc_verified`). */
|
|
25
|
+
readonly verified: boolean;
|
|
26
|
+
/** "siwe" | "social". 미상이면 undefined. */
|
|
27
|
+
readonly loginType?: KycLoginType;
|
|
28
|
+
/** 액세스 토큰에서 파생된 지갑 주소 (`wallet_address`). */
|
|
29
|
+
readonly walletAddress?: string;
|
|
30
|
+
/** rejected_* 상태에서만 존재 (`reject_type`). */
|
|
31
|
+
readonly rejectType?: KycRejectType;
|
|
32
|
+
/** KYC 공급자 식별자 (`provider`). 예: "sumsub". 멀티 벤더 대비. */
|
|
33
|
+
readonly provider?: string;
|
|
34
|
+
/**
|
|
35
|
+
* 호스티드 검증 URL (`verification_url`). 백엔드가 Sumsub websdkLink 등으로
|
|
36
|
+
* 생성해 내려주면, 프론트는 이 URL을 새 창으로 열어 검증을 진행한다(기본 경로).
|
|
37
|
+
*/
|
|
38
|
+
readonly verificationUrl?: string;
|
|
39
|
+
/** Sumsub SDK 토큰 (`kyc_token`). POST /kyc의 InitKYC 발급 시에만 존재. */
|
|
40
|
+
readonly sdkToken?: string;
|
|
41
|
+
/** Sumsub SDK 토큰 만료 시각 ISO 문자열 (`kyc_expires_at`). */
|
|
42
|
+
readonly sdkTokenExpiresAt?: string;
|
|
43
|
+
/** social 로그인 전용 — 이메일. */
|
|
44
|
+
readonly email?: string;
|
|
45
|
+
/** social 로그인 전용 — 닉네임. */
|
|
46
|
+
readonly nickname?: string;
|
|
47
|
+
/** social 로그인 전용 — IdP subject (`sub`). */
|
|
48
|
+
readonly sub?: string;
|
|
49
|
+
/** social 로그인 전용 — 내부 uuid. */
|
|
50
|
+
readonly uuid?: string;
|
|
51
|
+
}
|
|
52
|
+
type KycErrorCode = 'MISSING_PROJECT_ID' | 'UNAUTHORIZED' | 'STATUS_FAILED' | 'START_FAILED' | 'INVALID_RESPONSE' | 'NETWORK_ERROR' | 'LAUNCH_FAILED';
|
|
53
|
+
declare class KycError extends Error {
|
|
54
|
+
readonly code: KycErrorCode;
|
|
55
|
+
readonly details?: Record<string, unknown>;
|
|
56
|
+
constructor(code: KycErrorCode, message: string, details?: Record<string, unknown>);
|
|
57
|
+
}
|
|
58
|
+
/** 알 수 없는 status 문자열은 'none'으로 폴백. */
|
|
59
|
+
declare function normalizeKycStatus(value: unknown): KycStatus;
|
|
60
|
+
/** "siwe" | "social" 외 값은 undefined. */
|
|
61
|
+
declare function normalizeLoginType(value: unknown): KycLoginType | undefined;
|
|
62
|
+
/** "RETRY" | "FINAL" 외 값은 undefined. 대소문자 무관. */
|
|
63
|
+
declare function normalizeRejectType(value: unknown): KycRejectType | undefined;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* cross-auth KYC 추상화. 호출자 신원 + KYC 상태 조회와 검증 시작을 담당한다.
|
|
67
|
+
*
|
|
68
|
+
* 구현체는 어댑터 레이어가 담당 — 이 Port는 도메인 계약만 가진다.
|
|
69
|
+
*/
|
|
70
|
+
interface KycPort {
|
|
71
|
+
/**
|
|
72
|
+
* GET /kyc — 읽기 전용. 호출자의 신원(SIWE/social) + KYC 상태를 반환한다.
|
|
73
|
+
* 부수효과 없음: 지갑 링크나 검증 시작을 하지 않는다 (그건 startVerification).
|
|
74
|
+
*/
|
|
75
|
+
getStatus(): Promise<KycIdentity>;
|
|
76
|
+
/**
|
|
77
|
+
* POST /kyc — 호출자의 지갑을 프로젝트에 링크(idempotent)하고, KYC가 아직
|
|
78
|
+
* 승인되지 않았다면 검증을 시작/재개(idempotent)하며 Sumsub SDK 토큰
|
|
79
|
+
* (`sdkToken`)을 surfacing 한다. 이미 승인된 경우 토큰 없이 상태만 반환.
|
|
80
|
+
*/
|
|
81
|
+
startVerification(): Promise<KycIdentity>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* cross-auth 백엔드 엔드포인트 — 패키지 내부 상수. DApp 개발자는 이 파일을
|
|
86
|
+
* 보지도, 수정할 일도 없다. 환경별 base URL은 빌드 시 환경변수로 override
|
|
87
|
+
* 가능하다 (env 처리 방식은 `@nexus-cross/onramp`의 endpoints와 동일).
|
|
88
|
+
*
|
|
89
|
+
* 우선순위 (override):
|
|
90
|
+
* 1) `VITE_CROSSX_AUTH_BASE_URL` (Vite 빌드)
|
|
91
|
+
* 2) `NEXT_PUBLIC_CROSSX_AUTH_BASE_URL` (Next.js)
|
|
92
|
+
* 3) 환경 식별(`VITE_CROSSX_ENVIRONMENT` / `NEXT_PUBLIC_CROSSX_ENVIRONMENT`)
|
|
93
|
+
* 에 따라 DEFAULT_BASE_URL의 dev/stage/production
|
|
94
|
+
*/
|
|
95
|
+
type KycEnvironment = 'dev' | 'stage' | 'production';
|
|
96
|
+
interface KycEndpointPaths {
|
|
97
|
+
/**
|
|
98
|
+
* 신원/KYC 상태 — GET은 읽기 전용 조회, POST는 지갑 링크 + 검증 시작.
|
|
99
|
+
* cross-auth는 같은 경로(`/kyc`)에 두 메서드를 둔다.
|
|
100
|
+
*/
|
|
101
|
+
kyc: string;
|
|
102
|
+
}
|
|
103
|
+
declare const DEFAULT_KYC_PATHS: KycEndpointPaths;
|
|
104
|
+
declare function getKycBaseUrl(): string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 외부 통신을 추상화한 리포지토리. BrowserKycAdapter는 fetch를 직접 호출하지
|
|
108
|
+
* 않고 이 인터페이스를 통한다.
|
|
109
|
+
*
|
|
110
|
+
* 구현체:
|
|
111
|
+
* - HttpKycRepository: cross-auth `/kyc` 호출 (GET 상태 / POST 시작)
|
|
112
|
+
*
|
|
113
|
+
* DApp이 직접 구현해서 주입할 수도 있다 (자체 게이트웨이가 다른 응답 스키마를
|
|
114
|
+
* 쓰는 경우, 테스트 mock 등). 식별자(projectId, accessToken getter 등)는 구현체
|
|
115
|
+
* 생성자에서 주입하므로 메서드 인자에 포함하지 않는다.
|
|
116
|
+
*/
|
|
117
|
+
interface KycRepository {
|
|
118
|
+
/** GET /kyc — 읽기 전용 신원 + KYC 상태. */
|
|
119
|
+
fetchStatus(): Promise<KycIdentity>;
|
|
120
|
+
/** POST /kyc — 지갑 링크 + 검증 시작/재개. */
|
|
121
|
+
initVerification(): Promise<KycIdentity>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 매 호출마다 최신 access token을 읽어오는 getter. 동기/비동기 모두 허용. */
|
|
125
|
+
type AccessTokenGetter = () => string | undefined | null | Promise<string | undefined | null>;
|
|
126
|
+
interface HttpKycRepositoryOptions {
|
|
127
|
+
/**
|
|
128
|
+
* social 로그인 시 `X-Project-Id` 헤더로 전송 (embedded-wallet-gateway
|
|
129
|
+
* whitelist). SIWE-only DApp은 생략 가능하지만, 두 흐름 모두 지원하려면
|
|
130
|
+
* embeddedProjectId(미설정 시 crossProjectId)를 넘기는 걸 권장.
|
|
131
|
+
*/
|
|
132
|
+
projectId?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Bearer access token getter. 반환값이 있으면 `Authorization: Bearer <token>`
|
|
135
|
+
* 헤더를 붙인다. 쿠키 기반 세션(HttpOnly)만 쓰는 경우 생략 — 요청은 항상
|
|
136
|
+
* `credentials: 'include'`로 전송되므로 쿠키가 자동 첨부된다.
|
|
137
|
+
*/
|
|
138
|
+
getAccessToken?: AccessTokenGetter;
|
|
139
|
+
/** native SDK 흐름에서만 사용. `X-App-Id`로 전송. 웹은 생략. */
|
|
140
|
+
appId?: string;
|
|
141
|
+
/** `X-App-Id`와 함께 전송. 'android' | 'ios' | 'windows'. 웹은 생략. */
|
|
142
|
+
appType?: string;
|
|
143
|
+
/** override 안 하면 getKycBaseUrl() 사용. */
|
|
144
|
+
baseUrl?: string;
|
|
145
|
+
paths?: Partial<KycEndpointPaths>;
|
|
146
|
+
/** GET/POST 공통 타임아웃. 기본 10초. */
|
|
147
|
+
timeoutMs?: number;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* cross-auth `/kyc` 호출 리포지토리.
|
|
151
|
+
*
|
|
152
|
+
* 엔드포인트 (cross-auth swagger 기준):
|
|
153
|
+
* - GET /kyc → 읽기 전용. 신원(SIWE/social) + identity-core-api KYC 상태.
|
|
154
|
+
* - POST /kyc → 지갑 링크(idempotent) + 검증 시작/재개(idempotent). 미승인 시
|
|
155
|
+
* Sumsub SDK 토큰(kyc_token) surfacing.
|
|
156
|
+
* GET/POST 모두 요청 바디가 없다 (식별은 토큰/헤더로만).
|
|
157
|
+
* 응답 envelope: { code, message, data: KYCResp }.
|
|
158
|
+
*
|
|
159
|
+
* 인증:
|
|
160
|
+
* - BearerAuth(Authorization) 또는 HttpOnly 쿠키(credentials: include).
|
|
161
|
+
* - social login은 추가로 `X-Project-Id` + client identifier가 필요하다:
|
|
162
|
+
* web은 `Origin`(브라우저가 cross-origin 요청에 자동 첨부 — 수동 설정 불가),
|
|
163
|
+
* native는 `X-App-Id` + `X-App-Type`. 이 조합이 없으면 백엔드가 401을 준다.
|
|
164
|
+
*
|
|
165
|
+
* 실패는 throw(KycError) — 상태 읽기는 호출자가 알아야 하므로 fail-closed로
|
|
166
|
+
* 숨기지 않는다. 401/403은 UNAUTHORIZED, 그 외는 STATUS_FAILED/START_FAILED.
|
|
167
|
+
*/
|
|
168
|
+
declare class HttpKycRepository implements KycRepository {
|
|
169
|
+
private readonly projectId?;
|
|
170
|
+
private readonly getAccessToken?;
|
|
171
|
+
private readonly appId?;
|
|
172
|
+
private readonly appType?;
|
|
173
|
+
private readonly baseUrl;
|
|
174
|
+
private readonly paths;
|
|
175
|
+
private readonly timeoutMs;
|
|
176
|
+
constructor(opts?: HttpKycRepositoryOptions);
|
|
177
|
+
fetchStatus(): Promise<KycIdentity>;
|
|
178
|
+
initVerification(): Promise<KycIdentity>;
|
|
179
|
+
private request;
|
|
180
|
+
private buildHeaders;
|
|
181
|
+
private parseIdentity;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 브라우저 환경용 KycPort 구현.
|
|
186
|
+
*
|
|
187
|
+
* 외부 통신은 KycRepository로 위임 — 기본은 HttpKycRepository(fetch),
|
|
188
|
+
* 테스트/데모에서는 mock repository를 주입할 수 있다. KYC는 온램프와 달리
|
|
189
|
+
* popup/postMessage 같은 브라우저 부수효과가 없다 (Sumsub websdk 실행은
|
|
190
|
+
* DApp이 `sdkToken`으로 직접 처리). 따라서 이 어댑터는 얇은 위임 레이어로,
|
|
191
|
+
* 추후 status 캐싱/이벤트 같은 정책의 확장 지점만 확보해 둔다.
|
|
192
|
+
*/
|
|
193
|
+
interface BrowserKycAdapterOptions {
|
|
194
|
+
/**
|
|
195
|
+
* social 로그인 시 `X-Project-Id`로 전달. kitConfig.embeddedProjectId
|
|
196
|
+
* (미설정 시 crossProjectId)를 넘기는 걸 권장. `repository`를 직접 주입하는
|
|
197
|
+
* 경우 무시되어도 무방.
|
|
198
|
+
*/
|
|
199
|
+
projectId?: string;
|
|
200
|
+
/** Bearer access token getter. 쿠키 세션만 쓰면 생략 가능. */
|
|
201
|
+
getAccessToken?: AccessTokenGetter;
|
|
202
|
+
/** native SDK 흐름에서만 사용. 웹은 생략. */
|
|
203
|
+
appId?: string;
|
|
204
|
+
/** `X-App-Id`와 함께 전송. 'android' | 'ios' | 'windows'. 웹은 생략. */
|
|
205
|
+
appType?: string;
|
|
206
|
+
/**
|
|
207
|
+
* 외부 통신 리포지토리. 미주입 시 옵션으로 HttpKycRepository를 자동 구성.
|
|
208
|
+
* 테스트/데모에서는 mock 등을 주입.
|
|
209
|
+
*/
|
|
210
|
+
repository?: KycRepository;
|
|
211
|
+
}
|
|
212
|
+
declare class BrowserKycAdapter implements KycPort {
|
|
213
|
+
private readonly repository;
|
|
214
|
+
constructor(opts?: BrowserKycAdapterOptions);
|
|
215
|
+
getStatus(): Promise<KycIdentity>;
|
|
216
|
+
startVerification(): Promise<KycIdentity>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 프레임워크 무관 함수형 진입점.
|
|
221
|
+
*
|
|
222
|
+
* React/Vue/Svelte/vanilla 어디서든:
|
|
223
|
+
* const kyc = createKyc({ projectId, getAccessToken: () => token });
|
|
224
|
+
* const me = await kyc.getStatus(); // GET /kyc
|
|
225
|
+
* if (!me.verified) {
|
|
226
|
+
* const started = await kyc.start(); // POST /kyc
|
|
227
|
+
* // started.sdkToken 으로 Sumsub websdk 실행 (DApp 책임)
|
|
228
|
+
* }
|
|
229
|
+
*
|
|
230
|
+
* 내부적으로 BrowserKycAdapter + GetKycStatusUseCase/StartKycUseCase 조립.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
interface CreateKycOptions extends BrowserKycAdapterOptions {
|
|
234
|
+
}
|
|
235
|
+
interface Kyc {
|
|
236
|
+
/** 내부 어댑터. 고급 사용자가 직접 다뤄야 할 때 노출. */
|
|
237
|
+
readonly port: KycPort;
|
|
238
|
+
/** GET /kyc — 읽기 전용 신원 + KYC 상태. */
|
|
239
|
+
getStatus(): Promise<KycIdentity>;
|
|
240
|
+
/** POST /kyc — 지갑 링크 + 검증 시작/재개. Sumsub SDK 토큰 surfacing. */
|
|
241
|
+
start(): Promise<KycIdentity>;
|
|
242
|
+
}
|
|
243
|
+
declare function createKyc(options?: CreateKycOptions): Kyc;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* KYC 검증 진입 — 응답 형태에 따라 자동 분기한다.
|
|
247
|
+
*
|
|
248
|
+
* 1) 기본: `identity.verificationUrl`(백엔드 hosted URL)이 있으면 새 탭으로 연다.
|
|
249
|
+
* 벤더 중립 — CDN·CSP 불필요. (제안: docs/kyc/01-verification-link-proposal.md)
|
|
250
|
+
* 2) fallback: URL이 없고 `identity.sdkToken`만 있으면 Sumsub WebSDK를
|
|
251
|
+
* 전체화면 모달에 launch한다 (벤더 결합은 여기서만).
|
|
252
|
+
*
|
|
253
|
+
* 완료 판정의 진실의 소스는 webhook이며, 호출자는 `GET /kyc` 폴링으로 최종
|
|
254
|
+
* 상태를 확인해야 한다 (두 경로 공통).
|
|
255
|
+
*/
|
|
256
|
+
type KycVerificationMode = 'url' | 'websdk';
|
|
257
|
+
/**
|
|
258
|
+
* 검증 URL을 어디서 열지 (verificationUrl 모드에만 적용).
|
|
259
|
+
* - 'newWindow' (기본): 새 탭/창 (`window.open`)
|
|
260
|
+
* - 'currentWindow': 현재 창을 URL로 이동 (`location.assign`) — 검증 후 백엔드
|
|
261
|
+
* `redirect`로 앱에 복귀
|
|
262
|
+
* WebSDK fallback(sdkToken)은 창 개념이 없어 항상 현재 창의 모달로 렌더된다.
|
|
263
|
+
*/
|
|
264
|
+
type KycLaunchTarget = 'newWindow' | 'currentWindow';
|
|
265
|
+
interface KycVerificationHandle {
|
|
266
|
+
/** 'url' = 새 탭으로 열림, 'websdk' = 인페이지 모달 launch. */
|
|
267
|
+
readonly mode: KycVerificationMode;
|
|
268
|
+
/** websdk 모드: 모달을 닫는다. url 모드: no-op. */
|
|
269
|
+
close(): void;
|
|
270
|
+
}
|
|
271
|
+
interface LaunchKycVerificationOptions {
|
|
272
|
+
/**
|
|
273
|
+
* verificationUrl 모드에서 URL을 새 창/현재 창 중 어디서 열지. 기본 'newWindow'.
|
|
274
|
+
* WebSDK fallback에는 적용되지 않는다(항상 현재 창 모달).
|
|
275
|
+
*/
|
|
276
|
+
target?: KycLaunchTarget;
|
|
277
|
+
/**
|
|
278
|
+
* websdk fallback에서 토큰 만료 시 새 SDK 토큰을 받아오는 콜백.
|
|
279
|
+
* 보통 `POST /kyc`를 다시 호출(idempotent)해 새 kyc_token을 반환한다.
|
|
280
|
+
* 미지정 시 최초 sdkToken을 재사용한다(세션이 짧으면 만료될 수 있음).
|
|
281
|
+
*/
|
|
282
|
+
getFreshToken?: () => Promise<string>;
|
|
283
|
+
/** websdk 언어. 기본 'ko'. */
|
|
284
|
+
lang?: string;
|
|
285
|
+
/** url + newWindow 모드 window.open features. 기본 'noopener,noreferrer'. */
|
|
286
|
+
windowFeatures?: string;
|
|
287
|
+
onError?: (payload: unknown) => void;
|
|
288
|
+
onStatusChange?: (payload: unknown) => void;
|
|
289
|
+
}
|
|
290
|
+
declare function launchKycVerification(identity: KycIdentity, opts?: LaunchKycVerificationOptions): Promise<KycVerificationHandle>;
|
|
291
|
+
|
|
292
|
+
export { type AccessTokenGetter as A, BrowserKycAdapter as B, type CreateKycOptions as C, DEFAULT_KYC_PATHS as D, HttpKycRepository as H, type KycPort as K, type LaunchKycVerificationOptions as L, type Unsubscribe as U, type KycIdentity as a, type BrowserKycAdapterOptions as b, type HttpKycRepositoryOptions as c, type Kyc as d, type KycEndpointPaths as e, type KycEnvironment as f, KycError as g, type KycErrorCode as h, type KycLaunchTarget as i, type KycLoginType as j, type KycRejectType as k, type KycRepository as l, type KycStatus as m, type KycVerificationHandle as n, type KycVerificationMode as o, createKyc as p, getKycBaseUrl as q, launchKycVerification as r, normalizeKycStatus as s, normalizeLoginType as t, normalizeRejectType as u };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { C as CreateKycOptions, a as KycIdentity, m as KycStatus, g as KycError, L as LaunchKycVerificationOptions, n as KycVerificationHandle, d as Kyc } from '../launchVerification-oM0NtTLd.js';
|
|
4
|
+
export { h as KycErrorCode, i as KycLaunchTarget, j as KycLoginType, k as KycRejectType, o as KycVerificationMode, r as launchKycVerification } from '../launchVerification-oM0NtTLd.js';
|
|
5
|
+
|
|
6
|
+
interface KycProviderProps {
|
|
7
|
+
/** createKyc options. 흔한 케이스는 { projectId, getAccessToken } 한두 줄. */
|
|
8
|
+
config: CreateKycOptions;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* KYC Provider — 어떤 React 앱에든 단독으로 마운트 가능.
|
|
13
|
+
* connect-kit-react를 안 쓰는 DApp도 이걸 직접 마운트해서 `useKyc`를 쓸 수 있다.
|
|
14
|
+
*
|
|
15
|
+
* `getAccessToken`은 매 요청 시점에 호출되므로, 토큰이 갱신돼도 Provider를
|
|
16
|
+
* 재마운트할 필요가 없다 — 클로저로 최신 토큰을 읽게 넘기면 된다.
|
|
17
|
+
*/
|
|
18
|
+
declare function KycProvider({ config, children }: KycProviderProps): react_jsx_runtime.JSX.Element;
|
|
19
|
+
|
|
20
|
+
interface UseKycResult {
|
|
21
|
+
/** Provider mount 여부 (= 항상 true. 컨벤션 통일 위해 노출). */
|
|
22
|
+
isAvailable: boolean;
|
|
23
|
+
/** GET /kyc 결과. 마운트 직후엔 undefined. */
|
|
24
|
+
identity: KycIdentity | undefined;
|
|
25
|
+
/** identity?.status 단축 접근. */
|
|
26
|
+
status: KycStatus | undefined;
|
|
27
|
+
/** identity?.verified 단축 접근 (미로드 시 false). */
|
|
28
|
+
verified: boolean;
|
|
29
|
+
/** 상태 조회 중 여부. */
|
|
30
|
+
isLoading: boolean;
|
|
31
|
+
/** start() 진행 중 여부 (조회의 isLoading과 별개). */
|
|
32
|
+
isStarting: boolean;
|
|
33
|
+
/** 마지막 에러 (조회/시작 공통). 새 호출 시 null로 리셋. */
|
|
34
|
+
error: KycError | null;
|
|
35
|
+
/** 강제 재조회 (GET /kyc). */
|
|
36
|
+
refresh: () => Promise<KycIdentity | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* 검증 시작/재개 (POST /kyc). 성공 시 identity를 갱신하고 반환한다.
|
|
39
|
+
* 저수준 훅 — 진입(UI)까지 한 번에 하려면 `startVerification`을 쓴다.
|
|
40
|
+
*/
|
|
41
|
+
start: () => Promise<KycIdentity>;
|
|
42
|
+
/**
|
|
43
|
+
* 검증 시작 + 진입까지 일괄 처리 (POST /kyc → 검증 진입).
|
|
44
|
+
* - `verificationUrl`이 오면 새 탭으로 연다(기본).
|
|
45
|
+
* - 없고 `sdkToken`만 오면 Sumsub WebSDK를 모달에 launch(fallback).
|
|
46
|
+
* 토큰 만료 시 재발급은 내부에서 `start()`를 재호출해 처리한다.
|
|
47
|
+
*/
|
|
48
|
+
startVerification: (opts?: Omit<LaunchKycVerificationOptions, 'getFreshToken'>) => Promise<{
|
|
49
|
+
identity: KycIdentity;
|
|
50
|
+
handle: KycVerificationHandle;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
interface UseKycOptions {
|
|
54
|
+
/** 마운트 시 자동으로 GET /kyc를 호출할지. 기본 true. */
|
|
55
|
+
autoFetch?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* KYC 메인 훅. 마운트 시 GET /kyc로 상태를 자동 조회하고, start()로 POST /kyc를
|
|
59
|
+
* 호출해 검증을 시작/재개한다. 인증(Bearer 토큰/쿠키)은 Provider에 주입된
|
|
60
|
+
* createKyc 옵션이 담당한다.
|
|
61
|
+
*/
|
|
62
|
+
declare function useKyc(opts?: UseKycOptions): UseKycResult;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* KycProvider 안에서는 Kyc facade를 반환, 밖에서는 null.
|
|
66
|
+
*
|
|
67
|
+
* "있으면 쓰고, 없으면 비활성"으로 동작하고 싶은 컴포넌트가 React 훅 규칙을
|
|
68
|
+
* 어기지 않고 KycProvider 마운트 여부를 분기할 수 있게 한다.
|
|
69
|
+
*/
|
|
70
|
+
declare function useOptionalKyc(): Kyc | null;
|
|
71
|
+
|
|
72
|
+
export { CreateKycOptions, Kyc, KycError, KycIdentity, KycProvider, type KycProviderProps, KycStatus, KycVerificationHandle, LaunchKycVerificationOptions, type UseKycOptions, type UseKycResult, useKyc, useOptionalKyc };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as u,k as C,l as f}from"../chunk-2477RDFG.js";import{useMemo as w}from"react";import{createContext as E,useContext as b}from"react";var i=E(null);function v(){let t=b(i);if(!t)throw new Error("useKyc must be used within <KycProvider>");return t}import{jsx as g}from"react/jsx-runtime";function I({config:t,children:o}){let n=w(()=>({kyc:C(t)}),[t.projectId,t.appId,t.appType,t.getAccessToken,t.repository]);return g(i.Provider,{value:n,children:o})}import{useCallback as d,useEffect as T,useRef as j,useState as l}from"react";function L(t={}){let{kyc:o}=v(),n=t.autoFetch??!0,[s,p]=l(void 0),[V,m]=l(n),[P,x]=l(!1),[k,a]=l(null),c=j(null),h=e=>e instanceof u?e:new u("NETWORK_ERROR",e instanceof Error?e.message:"KYC request failed"),K=d(async()=>{c.current&&(c.current.cancelled=!0);let e={cancelled:!1};c.current=e,a(null),m(!0);try{let r=await o.getStatus();return e.cancelled?void 0:(p(r),r)}catch(r){if(e.cancelled)return;a(h(r));return}finally{e.cancelled||m(!1)}},[o]),y=d(async()=>{a(null),x(!0);try{let e=await o.start();return p(e),e}catch(e){let r=h(e);throw a(r),r}finally{x(!1)}},[o]),O=d(async e=>{let r=await y(),R=await f(r,{...e,getFreshToken:async()=>(await y()).sdkToken??""});return{identity:r,handle:R}},[y]);return T(()=>{if(n)return K(),()=>{c.current&&(c.current.cancelled=!0)}},[n,K]),{isAvailable:!0,identity:s,status:s?.status,verified:s?.verified??!1,isLoading:V,isStarting:P,error:k,refresh:K,start:y,startVerification:O}}import{useContext as S}from"react";function U(){return S(i)?.kyc??null}export{u as KycError,I as KycProvider,f as launchKycVerification,L as useKyc,U as useOptionalKyc};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexus-cross/kyc",
|
|
3
|
+
"version": "1.3.4-beta.10",
|
|
4
|
+
"description": "cross-auth KYC integration (identity + Sumsub status). Framework-agnostic core + React adapter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./react": {
|
|
14
|
+
"types": "./dist/react/index.d.ts",
|
|
15
|
+
"import": "./dist/react/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"registry": "https://registry.npmjs.org",
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"react": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/react": "^19.0.0",
|
|
36
|
+
"react": "^19.0.0",
|
|
37
|
+
"tsup": "^8.4.0",
|
|
38
|
+
"typescript": "^5.7.0"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"test": "echo 'no tests yet'",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
47
|
+
}
|