entity-server-client 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/build.mjs +7 -0
- package/docs/api/alimtalk.md +62 -0
- package/docs/api/auth.md +256 -0
- package/docs/api/email.md +37 -0
- package/docs/api/entity.md +273 -0
- package/docs/api/file.md +80 -0
- package/docs/api/health.md +41 -0
- package/docs/api/identity.md +32 -0
- package/docs/api/import.md +34 -0
- package/docs/api/packet.md +25 -0
- package/docs/api/pg.md +90 -0
- package/docs/api/push.md +107 -0
- package/docs/api/react.md +141 -0
- package/docs/api/setup.md +43 -0
- package/docs/api/sms.md +45 -0
- package/docs/api/smtp.md +33 -0
- package/docs/api/transaction.md +50 -0
- package/docs/api/utils.md +52 -0
- package/docs/apis.md +22 -780
- package/docs/react.md +58 -0
- package/package.json +6 -1
- package/src/EntityServerClient.ts +5 -31
- package/src/client/base.ts +71 -12
- package/src/client/packet.ts +8 -31
- package/src/client/request.ts +52 -6
- package/src/client/utils.ts +5 -6
- package/src/mixins/auth.ts +14 -158
- package/src/mixins/push.ts +0 -23
- package/src/mixins/utils.ts +32 -1
- package/src/packet.ts +84 -0
- package/src/types.ts +15 -125
- package/tests/packet.test.mjs +50 -0
- package/dist/EntityServerClient.d.ts +0 -709
- package/dist/client/base.d.ts +0 -59
- package/dist/client/hmac.d.ts +0 -8
- package/dist/client/packet.d.ts +0 -24
- package/dist/client/request.d.ts +0 -15
- package/dist/client/utils.d.ts +0 -8
- package/dist/hooks/useEntityServer.d.ts +0 -63
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -7
- package/dist/mixins/alimtalk.d.ts +0 -56
- package/dist/mixins/auth.d.ts +0 -167
- package/dist/mixins/email.d.ts +0 -51
- package/dist/mixins/entity.d.ts +0 -119
- package/dist/mixins/file.d.ts +0 -78
- package/dist/mixins/identity.d.ts +0 -52
- package/dist/mixins/pg.d.ts +0 -63
- package/dist/mixins/push.d.ts +0 -110
- package/dist/mixins/sms.d.ts +0 -55
- package/dist/mixins/smtp.d.ts +0 -44
- package/dist/mixins/utils.d.ts +0 -70
- package/dist/react.d.ts +0 -1
- package/dist/react.js +0 -2
- package/dist/react.js.map +0 -7
- package/dist/types.d.ts +0 -329
- package/src/mixins/alimtalk.ts +0 -35
- package/src/mixins/email.ts +0 -46
- package/src/mixins/identity.ts +0 -35
- package/src/mixins/pg.ts +0 -58
- package/src/mixins/sms.ts +0 -46
package/docs/api/file.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 파일 스토리지
|
|
2
|
+
|
|
3
|
+
## `fileUpload(entity, file, opts?)`
|
|
4
|
+
|
|
5
|
+
파일을 업로드합니다 (multipart/form-data). `entity`는 파일이 귀속될 엔티티명입니다.
|
|
6
|
+
|
|
7
|
+
| 옵션 | 타입 | 설명 |
|
|
8
|
+
| ---------- | --------- | ----------------------------------------- |
|
|
9
|
+
| `refSeq` | `number` | 연결할 엔티티 레코드 seq |
|
|
10
|
+
| `isPublic` | `boolean` | `true`이면 인증 없이 URL로 직접 접근 가능 |
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
14
|
+
const file = input.files![0];
|
|
15
|
+
|
|
16
|
+
const res = await client.fileUpload("product", file, {
|
|
17
|
+
refSeq: 10,
|
|
18
|
+
isPublic: false,
|
|
19
|
+
});
|
|
20
|
+
res.uuid; // 파일 고유 UUID
|
|
21
|
+
res.data; // FileMeta 객체
|
|
22
|
+
res.data.name; // 원본 파일명
|
|
23
|
+
res.data.size; // 파일 크기 (bytes)
|
|
24
|
+
res.data.mime; // MIME 타입
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## `fileDownload(entity, uuid)`
|
|
28
|
+
|
|
29
|
+
파일을 다운로드합니다. `ArrayBuffer`를 반환합니다.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const buf = await client.fileDownload("product", "uuid-here");
|
|
33
|
+
const blob = new Blob([buf], { type: "image/png" });
|
|
34
|
+
const url = URL.createObjectURL(blob);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## `fileDelete(entity, uuid)`
|
|
38
|
+
|
|
39
|
+
파일을 삭제합니다.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
await client.fileDelete("product", "uuid-here");
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## `fileList(entity, opts?)`
|
|
46
|
+
|
|
47
|
+
엔티티에 연결된 파일 목록을 조회합니다.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const res = await client.fileList("product", { refSeq: 10 });
|
|
51
|
+
res.data.items; // FileMeta[]
|
|
52
|
+
res.data.total; // 전체 건수
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## `fileMeta(entity, uuid)`
|
|
56
|
+
|
|
57
|
+
파일 메타 정보를 조회합니다.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const res = await client.fileMeta("product", "uuid-here");
|
|
61
|
+
res.data; // FileMeta
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## `fileToken(uuid)`
|
|
65
|
+
|
|
66
|
+
임시 파일 접근 토큰을 발급합니다. 비공개 파일을 일시적으로 공유할 때 사용합니다.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const res = await client.fileToken("uuid-here");
|
|
70
|
+
const tempUrl = `${baseUrl}/v1/files/${res.token}`;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## `fileUrl(uuid)`
|
|
74
|
+
|
|
75
|
+
파일 직접 접근 URL을 반환합니다. (fetch 없음, URL 조합만)
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const url = client.fileUrl("uuid-here");
|
|
79
|
+
// → "http://localhost:47200/v1/files/uuid-here"
|
|
80
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 서버 헬스체크
|
|
2
|
+
|
|
3
|
+
## 개요
|
|
4
|
+
|
|
5
|
+
앱 시작 시 서버의 패킷 암호화 설정을 감지하여 클라이언트 설정을 자동으로 맞춥니다.
|
|
6
|
+
|
|
7
|
+
**헬스체크 응답:**
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"ok": true,
|
|
12
|
+
"packet_encryption": false
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 자동 초기화 (권장)
|
|
17
|
+
|
|
18
|
+
**admin-web 예시:**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { entityServer } from "entity-server-client";
|
|
22
|
+
|
|
23
|
+
// 앱 초기화 시 한 번 실행 (예: App.tsx mount, main.tsx 초기화)
|
|
24
|
+
const health = await entityServer.checkHealth();
|
|
25
|
+
// → 서버의 packet_encryption: true 이면
|
|
26
|
+
// 클라이언트의 encryptRequests: true 로 자동 설정됨
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**수동 초기화:**
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const res = await fetch("/v1/health");
|
|
33
|
+
const { packet_encryption } = await res.json();
|
|
34
|
+
|
|
35
|
+
if (packet_encryption) {
|
|
36
|
+
entityServer.configure({ encryptRequests: true });
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **주의:** 패킷 magic 길이(K)는 `K = 2 + key[31] % 14` 공식으로 패킷 키에서 자동 파생됩니다.
|
|
41
|
+
> 클라이언트와 서버 모두 동일한 패킷 키를 보유하므로 별도 설정 필요 없습니다.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 본인인증
|
|
2
|
+
|
|
3
|
+
## `identityRequest(opts)`
|
|
4
|
+
|
|
5
|
+
본인인증 세션을 생성합니다.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const res = await client.identityRequest({
|
|
9
|
+
redirect_url: "https://example.com/identity/callback",
|
|
10
|
+
method: "pass",
|
|
11
|
+
});
|
|
12
|
+
res.data; // { request_id: "...", auth_url: "..." } 등
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## `identityResult(requestId)`
|
|
16
|
+
|
|
17
|
+
본인인증 결과를 조회합니다. 콜백 처리 후 호출합니다.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const res = await client.identityResult("request-id-here");
|
|
21
|
+
res.data; // { name, phone, birth, gender, di, ci, ... }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `identityVerifyCI(ciHash)`
|
|
25
|
+
|
|
26
|
+
CI(연계정보) 해시로 기존 계정 중복 여부를 확인합니다.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const res = await client.identityVerifyCI("ci-hash-string");
|
|
30
|
+
res.data.exists; // true: 이미 가입된 계정 존재
|
|
31
|
+
res.data.account_seq; // 존재할 경우 해당 계정 seq
|
|
32
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# import
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import {
|
|
5
|
+
EntityServerClient,
|
|
6
|
+
entityServer,
|
|
7
|
+
type EntityListParams,
|
|
8
|
+
type EntityListResult,
|
|
9
|
+
type EntityHistoryRecord,
|
|
10
|
+
type EntityQueryRequest,
|
|
11
|
+
type EntityServerClientOptions,
|
|
12
|
+
type RegisterPushDeviceOptions,
|
|
13
|
+
type PushSendRequest,
|
|
14
|
+
type PushSendAllRequest,
|
|
15
|
+
type SmsSendRequest,
|
|
16
|
+
type SmtpSendRequest,
|
|
17
|
+
type AlimtalkSendRequest,
|
|
18
|
+
type FriendtalkSendRequest,
|
|
19
|
+
type PgCreateOrderRequest,
|
|
20
|
+
type PgConfirmPaymentRequest,
|
|
21
|
+
type PgCancelPaymentRequest,
|
|
22
|
+
type FileMeta,
|
|
23
|
+
type FileUploadOptions,
|
|
24
|
+
type IdentityRequestOptions,
|
|
25
|
+
type QRCodeOptions,
|
|
26
|
+
type BarcodeOptions,
|
|
27
|
+
} from "entity-server-client";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
React Hook:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { useEntityServer } from "entity-server-client/react";
|
|
34
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 암호화 패킷 처리
|
|
2
|
+
|
|
3
|
+
## 자동 복호화
|
|
4
|
+
|
|
5
|
+
서버 응답이 `Content-Type: application/octet-stream`이면 `request()` 내부에서 자동으로 복호화됩니다.
|
|
6
|
+
XChaCha20-Poly1305 알고리즘을 사용하며, 키는 현재 JWT 토큰의 SHA-256 해시입니다.
|
|
7
|
+
|
|
8
|
+
별도 처리 없이 일반 응답과 동일하게 사용하면 됩니다.
|
|
9
|
+
|
|
10
|
+
## `readRequestBody(body, contentType?, requireEncrypted?)`
|
|
11
|
+
|
|
12
|
+
원시 암호화 payload를 직접 파싱할 때 사용합니다.
|
|
13
|
+
주로 서버 측 미들웨어나 SSR 환경에서 클라이언트로부터 받은 암호화 body를 처리할 때 활용합니다.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// 암호화 body 복호화
|
|
17
|
+
const data = client.readRequestBody(
|
|
18
|
+
arrayBuffer, // ArrayBuffer | Uint8Array
|
|
19
|
+
"application/octet-stream",
|
|
20
|
+
true, // requireEncrypted: 암호화 아니면 에러
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// 일반 JSON body 파싱
|
|
24
|
+
const data2 = client.readRequestBody(jsonString, "application/json");
|
|
25
|
+
```
|
package/docs/api/pg.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# PG 결제
|
|
2
|
+
|
|
3
|
+
결제 라이프사이클: `pgCreateOrder` → (사용자 결제 진행) → `pgConfirmPayment` → (`pgSyncPayment` | `pgCancelPayment`)
|
|
4
|
+
|
|
5
|
+
## `pgCreateOrder(req)`
|
|
6
|
+
|
|
7
|
+
결제 주문을 생성합니다. 결제 전 서버에 주문 정보를 등록합니다.
|
|
8
|
+
|
|
9
|
+
| 필드 | 타입 | 설명 |
|
|
10
|
+
| --------------- | -------- | ----------------------- |
|
|
11
|
+
| `orderId` | `string` | 가맹점 주문 ID (고유값) |
|
|
12
|
+
| `amount` | `number` | 결제 금액 (원 단위) |
|
|
13
|
+
| `orderName` | `string` | 주문명 |
|
|
14
|
+
| `customerName` | `string` | 구매자 이름 |
|
|
15
|
+
| `customerEmail` | `string` | 구매자 이메일 |
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const order = await client.pgCreateOrder({
|
|
19
|
+
orderId: "order-2026-001",
|
|
20
|
+
amount: 15000,
|
|
21
|
+
orderName: "상품 A",
|
|
22
|
+
customerName: "홍길동",
|
|
23
|
+
customerEmail: "hong@example.com",
|
|
24
|
+
});
|
|
25
|
+
order.paymentKey; // 결제창 호출에 필요한 키
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## `pgGetOrder(orderId)`
|
|
29
|
+
|
|
30
|
+
주문 정보를 조회합니다.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const order = await client.pgGetOrder("order-2026-001");
|
|
34
|
+
order.status; // "pending" | "paid" | "cancelled" ...
|
|
35
|
+
order.amount;
|
|
36
|
+
order.paymentKey;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## `pgConfirmPayment(req)`
|
|
40
|
+
|
|
41
|
+
결제 완료 후 서버 측 승인 처리를 합니다. PG사에서 발급된 `paymentKey`와 금액으로 검증합니다.
|
|
42
|
+
|
|
43
|
+
| 필드 | 타입 | 설명 |
|
|
44
|
+
| ------------ | -------- | -------------------------------------- |
|
|
45
|
+
| `paymentKey` | `string` | PG사 결제 키 (클라이언트에서 수신) |
|
|
46
|
+
| `orderId` | `string` | 가맹점 주문 ID |
|
|
47
|
+
| `amount` | `number` | 결제 금액 (주문 금액과 불일치 시 에러) |
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const result = await client.pgConfirmPayment({
|
|
51
|
+
paymentKey: "toss_paymentKey_abc",
|
|
52
|
+
orderId: "order-2026-001",
|
|
53
|
+
amount: 15000,
|
|
54
|
+
});
|
|
55
|
+
result.status; // "paid"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `pgCancelPayment(paymentKey, req?)`
|
|
59
|
+
|
|
60
|
+
결제를 취소(환불)합니다.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// 전액 취소
|
|
64
|
+
await client.pgCancelPayment("toss_paymentKey_abc");
|
|
65
|
+
|
|
66
|
+
// 부분 취소
|
|
67
|
+
await client.pgCancelPayment("toss_paymentKey_abc", {
|
|
68
|
+
cancelReason: "고객 요청",
|
|
69
|
+
cancelAmount: 5000,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## `pgSyncPayment(paymentKey)`
|
|
74
|
+
|
|
75
|
+
PG사의 실시간 결제 상태를 서버 DB에 동기화합니다.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const result = await client.pgSyncPayment("toss_paymentKey_abc");
|
|
79
|
+
result.status; // 동기화 후 상태
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## `pgConfig()`
|
|
83
|
+
|
|
84
|
+
설정된 PG 정보를 조회합니다. 클라이언트 측 결제창 초기화에 필요한 `clientKey` 등을 포함합니다.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const config = await client.pgConfig();
|
|
88
|
+
config.provider; // "toss" | ...
|
|
89
|
+
config.clientKey; // 결제창 초기화용 키
|
|
90
|
+
```
|
package/docs/api/push.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 푸시 관련
|
|
2
|
+
|
|
3
|
+
## `push(pushEntity, payload, opts?)`
|
|
4
|
+
|
|
5
|
+
`submit()`의 별칭입니다. 푸시 관련 엔티티에 데이터를 제출합니다.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
await client.push("push_message", {
|
|
9
|
+
title: "새 알림",
|
|
10
|
+
body: "내용",
|
|
11
|
+
account_seq: 1,
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## `pushLogList(params?)`
|
|
16
|
+
|
|
17
|
+
`push_log` 엔티티의 목록을 조회합니다. `list()`의 별칭입니다.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const logs = await client.pushLogList({
|
|
21
|
+
page: 1,
|
|
22
|
+
limit: 30,
|
|
23
|
+
orderBy: "-created_time",
|
|
24
|
+
conditions: { account_seq: 1 },
|
|
25
|
+
});
|
|
26
|
+
logs.data.items;
|
|
27
|
+
logs.data.total;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## `registerPushDevice(accountSeq, deviceId, pushToken, opts?)`
|
|
31
|
+
|
|
32
|
+
`account_device` 엔티티에 디바이스를 등록합니다.
|
|
33
|
+
|
|
34
|
+
| 옵션 | 타입 | 설명 |
|
|
35
|
+
| ---------------- | --------- | --------------------------------- |
|
|
36
|
+
| `platform` | `string` | 플랫폼 (예: `"web"`, `"android"`) |
|
|
37
|
+
| `deviceType` | `string` | 디바이스 종류 (예: `"mobile"`) |
|
|
38
|
+
| `browser` | `string` | 브라우저명 (예: `"Chrome"`) |
|
|
39
|
+
| `browserVersion` | `string` | 브라우저 버전 |
|
|
40
|
+
| `pushEnabled` | `boolean` | 푸시 수신 여부. 기본값 `true` |
|
|
41
|
+
| `transactionId` | `string` | 트랜잭션 ID |
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const res = await client.registerPushDevice(
|
|
45
|
+
1,
|
|
46
|
+
"device-uuid-001",
|
|
47
|
+
"fcm-token-abc",
|
|
48
|
+
{
|
|
49
|
+
platform: "web",
|
|
50
|
+
browser: "Chrome",
|
|
51
|
+
browserVersion: "120",
|
|
52
|
+
pushEnabled: true,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
res.seq; // 등록된 account_device seq
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `updatePushDeviceToken(deviceSeq, pushToken, opts?)`
|
|
59
|
+
|
|
60
|
+
등록된 디바이스의 푸시 토큰을 갱신합니다.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await client.updatePushDeviceToken(10, "new-fcm-token", { pushEnabled: true });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## `disablePushDevice(deviceSeq, opts?)`
|
|
67
|
+
|
|
68
|
+
디바이스의 푸시 수신을 비활성화합니다 (`push_enabled: false`).
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
await client.disablePushDevice(10);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## `pushSend(req)`
|
|
75
|
+
|
|
76
|
+
특정 계정에 푸시 알림을 직접 발송합니다.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const res = await client.pushSend({
|
|
80
|
+
account_seq: 1,
|
|
81
|
+
title: "알림 제목",
|
|
82
|
+
body: "알림 내용",
|
|
83
|
+
});
|
|
84
|
+
res.seq; // 발송 로그 seq
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## `pushSendAll(req)`
|
|
88
|
+
|
|
89
|
+
전체 또는 조건에 맞는 모든 사용자에게 푸시 알림을 발송합니다.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const res = await client.pushSendAll({
|
|
93
|
+
title: "공지사항",
|
|
94
|
+
body: "전체 공지 내용입니다.",
|
|
95
|
+
});
|
|
96
|
+
res.sent; // 발송 성공 수
|
|
97
|
+
res.failed; // 발송 실패 수
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## `pushStatus(seq)`
|
|
101
|
+
|
|
102
|
+
푸시 발송 로그의 처리 상태를 조회합니다.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const res = await client.pushStatus(42);
|
|
106
|
+
res.status; // "sent" | "failed" | "pending" 등
|
|
107
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# React Hook
|
|
2
|
+
|
|
3
|
+
## `useEntityServer(options?)`
|
|
4
|
+
|
|
5
|
+
React 컴포넌트에서 EntityServerClient를 사용할 때 권장되는 훅입니다.
|
|
6
|
+
`submit` / `del` / `query` 의 `isPending`, `error` 상태를 자동으로 관리합니다.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { useEntityServer } from "entity-server-client/react";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### 옵션
|
|
13
|
+
|
|
14
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
15
|
+
| ------------------ | ----------------------------------- | ------ | ----------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `singleton` | `boolean` | `true` | `true`이면 전역 `entityServer` 인스턴스 사용 |
|
|
17
|
+
| `baseUrl` | `string` | — | 서버 주소 (singleton일 때 `configure()` 호출) |
|
|
18
|
+
| `token` | `string` | — | JWT Access Token |
|
|
19
|
+
| `tokenResolver` | `() => string \| undefined \| null` | — | 렌더 시점에 토큰을 동적으로 주입하는 함수 |
|
|
20
|
+
| `keepSession` | `boolean` | — | 세션 유지 활성화 |
|
|
21
|
+
| `onTokenRefreshed` | `(accessToken, expiresIn) => void` | — | 세션 유지 성공 콜백 |
|
|
22
|
+
| `onSessionExpired` | `(error: Error) => void` | — | 세션 만료 콜백 (refresh_token 만료 등) |
|
|
23
|
+
| `resumeSession` | `string` | — | 저장된 refresh_token. 마운트 시 `refreshToken()` 호출해 토큰 복원 + `keepSession` 타이머 재시작 |
|
|
24
|
+
|
|
25
|
+
### 반환값
|
|
26
|
+
|
|
27
|
+
| 필드 | 타입 | 설명 |
|
|
28
|
+
| ----------- | -------------------- | ----------------------------------------------------- |
|
|
29
|
+
| `client` | `EntityServerClient` | **모든 API 메서드에 접근 가능한 클라이언트 인스턴스** |
|
|
30
|
+
| `isPending` | `boolean` | `submit` / `del` / `query` 진행 중 여부 |
|
|
31
|
+
| `error` | `Error \| null` | 마지막 mutation 에러. 성공 또는 `reset()` 시 `null` |
|
|
32
|
+
| `reset` | `() => void` | `isPending`, `error` 초기화 |
|
|
33
|
+
| `submit` | function | `client.submit()` 래퍼 — 상태 자동 관리 |
|
|
34
|
+
| `del` | function | `client.delete()` 래퍼 — 상태 자동 관리 |
|
|
35
|
+
| `query` | function | `client.query()` 래퍼 — 상태 자동 관리 |
|
|
36
|
+
|
|
37
|
+
> **`client`는 `EntityServerClient` 인스턴스 그대로입니다.**
|
|
38
|
+
> `submit` / `del` / `query` 래퍼는 `isPending` / `error` 상태를 자동 관리해주는 편의 메서드일 뿐이며,
|
|
39
|
+
> 인증, 푸시, 파일, SMS, PG 등 **모든 API**는 `client.메서드명()`으로 직접 호출합니다.
|
|
40
|
+
|
|
41
|
+
### `client`로 사용 가능한 모든 API
|
|
42
|
+
|
|
43
|
+
`client`를 통해 아래 모든 섹션의 메서드를 그대로 사용할 수 있습니다.
|
|
44
|
+
|
|
45
|
+
| 카테고리 | 주요 메서드 (예시) | 상세 문서 |
|
|
46
|
+
| ------------------ | ---------------------------------------------------------------------------------- | -------------------------------- |
|
|
47
|
+
| 인증 | `login`, `logout`, `register`, `refreshToken`, `oauthLogin`, `enable2FA` … | [auth.md](auth.md) |
|
|
48
|
+
| 트랜잭션 | `transStart`, `transCommit`, `transRollback` | [transaction.md](transaction.md) |
|
|
49
|
+
| 엔티티 CRUD / 조회 | `get`, `find`, `list`, `count`, `query`, `submit`, `delete`, `history`, `rollback` | [entity.md](entity.md) |
|
|
50
|
+
| 푸시 | `pushSend`, `pushSendAll`, `pushStatus`, `registerPushDevice` … | [push.md](push.md) |
|
|
51
|
+
| 이메일 인증 | `emailVerificationSend`, `emailVerificationConfirm`, `emailChange` | [email.md](email.md) |
|
|
52
|
+
| SMS | `smsSend`, `smsVerificationSend`, `smsVerificationVerify` | [sms.md](sms.md) |
|
|
53
|
+
| SMTP 메일 | `smtpSend`, `smtpStatus` | [smtp.md](smtp.md) |
|
|
54
|
+
| 알림톡 / 친구톡 | `alimtalkSend`, `alimtalkTemplates`, `friendtalkSend` | [alimtalk.md](alimtalk.md) |
|
|
55
|
+
| PG 결제 | `pgCreateOrder`, `pgConfirmPayment`, `pgCancelPayment`, `pgConfig` | [pg.md](pg.md) |
|
|
56
|
+
| 파일 스토리지 | `fileUpload`, `fileDownload`, `fileDelete`, `fileList`, `fileToken`, `fileUrl` | [file.md](file.md) |
|
|
57
|
+
| 본인인증 | `identityRequest`, `identityResult`, `identityVerifyCI` | [identity.md](identity.md) |
|
|
58
|
+
| QR코드 / 바코드 | `qrcode`, `qrcodeBase64`, `barcode` | [utils.md](utils.md) |
|
|
59
|
+
|
|
60
|
+
### 기본 사용 예시
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useEntityServer } from "entity-server-client/react";
|
|
64
|
+
|
|
65
|
+
function AccountForm() {
|
|
66
|
+
const { submit, del, isPending, error, reset } = useEntityServer();
|
|
67
|
+
|
|
68
|
+
const handleSave = async () => {
|
|
69
|
+
reset();
|
|
70
|
+
await submit("account", { name: "홍길동", email: "hong@example.com" });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleDelete = async (seq: number) => {
|
|
74
|
+
await del("account", seq);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div>
|
|
79
|
+
{isPending && <span>저장 중...</span>}
|
|
80
|
+
{error && <span style={{ color: "red" }}>{error.message}</span>}
|
|
81
|
+
<button onClick={handleSave} disabled={isPending}>
|
|
82
|
+
저장
|
|
83
|
+
</button>
|
|
84
|
+
<button onClick={() => handleDelete(1)} disabled={isPending}>
|
|
85
|
+
삭제
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 동적 토큰 주입
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const { submit } = useEntityServer({
|
|
96
|
+
tokenResolver: () => localStorage.getItem("access_token"),
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 페이지 새로고침 후 세션 복원
|
|
101
|
+
|
|
102
|
+
`resumeSession`에 저장된 refresh_token을 넘기면 마운트 시 `refreshToken()`을 호출해
|
|
103
|
+
새 access_token을 발급받고, `keepSession: true`이면 타이머도 자동 재시작됩니다.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// App.tsx 또는 로그인 복원을 담당하는 컴포넌트
|
|
107
|
+
export function AppShell() {
|
|
108
|
+
const storedRefreshToken =
|
|
109
|
+
localStorage.getItem("auth_refresh_token") ?? undefined;
|
|
110
|
+
|
|
111
|
+
useEntityServer({
|
|
112
|
+
resumeSession: storedRefreshToken,
|
|
113
|
+
keepSession: true,
|
|
114
|
+
onTokenRefreshed: (accessToken) => {
|
|
115
|
+
localStorage.setItem("auth_access_token", accessToken);
|
|
116
|
+
},
|
|
117
|
+
onSessionExpired: () => {
|
|
118
|
+
window.location.href = "/login";
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return <RouterOutlet />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
마운트 시 `resumeSession`이 있으면:
|
|
127
|
+
|
|
128
|
+
1. `refreshToken(storedRefreshToken)` 호출 → 서버에서 새 access_token 발급
|
|
129
|
+
2. 패키지 내부 `this.token` 자동 교체
|
|
130
|
+
3. `onTokenRefreshed` 콜백 호출
|
|
131
|
+
4. `keepSession: true`이면 다음 만료 전 타이머 자동 예약
|
|
132
|
+
5. 실패 시 `onSessionExpired` 콜백 호출
|
|
133
|
+
|
|
134
|
+
### 컴포넌트별 독립 인스턴스
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
const { client } = useEntityServer({
|
|
138
|
+
singleton: false,
|
|
139
|
+
baseUrl: "https://other-server.example.com",
|
|
140
|
+
});
|
|
141
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 인스턴스 생성/설정
|
|
2
|
+
|
|
3
|
+
## `new EntityServerClient(options?)`
|
|
4
|
+
|
|
5
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
6
|
+
| ------------------ | ---------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
7
|
+
| `baseUrl` | `string` | `VITE_ENTITY_SERVER_URL` 또는 `http://localhost:47200` | 서버 주소 |
|
|
8
|
+
| `token` | `string` | `""` | JWT Access Token |
|
|
9
|
+
| `encryptRequests` | `boolean` | `false` | `true` 로 설정하면 인증된 POST/PUT/PATCH 요청 바디를 XChaCha20-Poly1305 로 암호화하여 전송합니다. 서버에서 `requirePacketEncryption = true` 로 설정된 경우 반드시 활성화해야 합니다. |
|
|
10
|
+
| `keepSession` | `boolean` | `false` | `true`이면 `login()` 성공 후 Access Token 만료 전에 자동으로 갱신합니다. |
|
|
11
|
+
| `refreshBuffer` | `number` | `60` | 만료 몇 초 전에 갱신을 시도할지 설정합니다. 예: `expires_in=3600`, `refreshBuffer=60` → 3540초 후 갱신 |
|
|
12
|
+
| `onTokenRefreshed` | `(accessToken, expiresIn) => void` | — | 자동 갱신 성공 시 호출됩니다. 새 `access_token`을 받아 앱 레벨 저장소에 업데이트하세요. |
|
|
13
|
+
| `onSessionExpired` | `(error: Error) => void` | — | 세션 유지 갱신 실패 시 호출됩니다 (refresh_token 만료 등). 로그인 페이지로 이동하는 등의 처리를 여기서 합니다. |
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// 직접 생성
|
|
17
|
+
const client = new EntityServerClient({
|
|
18
|
+
baseUrl: "https://api.example.com",
|
|
19
|
+
token: "eyJhbGciOi...",
|
|
20
|
+
encryptRequests: true, // 요청 바디 암호화 활성화
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// 싱글톤 (환경변수 자동 읽기)
|
|
24
|
+
import { entityServer } from "entity-server-client";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## `configure(options)`
|
|
28
|
+
|
|
29
|
+
런타임에 설정을 갱신합니다.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
client.configure({
|
|
33
|
+
baseUrl: "https://api.example.com",
|
|
34
|
+
token: "new-access-token",
|
|
35
|
+
encryptRequests: true,
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## `setToken(token)`
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
client.setToken("eyJhbGciOi...");
|
|
43
|
+
```
|
package/docs/api/sms.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# SMS
|
|
2
|
+
|
|
3
|
+
## `smsSend(req)`
|
|
4
|
+
|
|
5
|
+
SMS를 직접 발송합니다.
|
|
6
|
+
|
|
7
|
+
| 필드 | 타입 | 설명 |
|
|
8
|
+
| ------ | -------- | ---------------------------------- |
|
|
9
|
+
| `to` | `string` | 수신 전화번호 (`01012345678` 형식) |
|
|
10
|
+
| `text` | `string` | 메시지 내용 |
|
|
11
|
+
| `from` | `string` | 발신 번호 (미지정 시 설정값 사용) |
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const res = await client.smsSend({
|
|
15
|
+
to: "01012345678",
|
|
16
|
+
text: "안녕하세요. 인증번호는 123456입니다.",
|
|
17
|
+
});
|
|
18
|
+
res.seq; // 발송 로그 seq
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## `smsStatus(seq)`
|
|
22
|
+
|
|
23
|
+
SMS 발송 처리 상태를 조회합니다.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const res = await client.smsStatus(10);
|
|
27
|
+
res.status; // "sent" | "failed" | "pending"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## `smsVerificationSend(phone)`
|
|
31
|
+
|
|
32
|
+
SMS 인증번호를 발송합니다. 인증 불필요 (공개 API).
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
await client.smsVerificationSend("01012345678");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## `smsVerificationVerify(phone, code)`
|
|
39
|
+
|
|
40
|
+
발송된 SMS 인증번호를 검증합니다. 인증 불필요 (공개 API).
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const res = await client.smsVerificationVerify("01012345678", "123456");
|
|
44
|
+
res.verified; // true | false
|
|
45
|
+
```
|
package/docs/api/smtp.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# SMTP 메일
|
|
2
|
+
|
|
3
|
+
## `smtpSend(req)`
|
|
4
|
+
|
|
5
|
+
SMTP를 통해 이메일을 발송합니다.
|
|
6
|
+
|
|
7
|
+
| 필드 | 타입 | 설명 |
|
|
8
|
+
| --------- | ---------- | ---------------------------- |
|
|
9
|
+
| `to` | `string` | 수신 이메일 주소 |
|
|
10
|
+
| `subject` | `string` | 이메일 제목 |
|
|
11
|
+
| `html` | `string` | HTML 본문 |
|
|
12
|
+
| `text` | `string` | 텍스트 본문 (HTML 대체) |
|
|
13
|
+
| `from` | `string` | 발신 주소 (미지정 시 설정값) |
|
|
14
|
+
| `cc` | `string[]` | 참조 수신자 목록 |
|
|
15
|
+
| `bcc` | `string[]` | 숨은 참조 수신자 목록 |
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const res = await client.smtpSend({
|
|
19
|
+
to: "user@example.com",
|
|
20
|
+
subject: "가입을 환영합니다!",
|
|
21
|
+
html: "<h1>안녕하세요</h1><p>가입해주셔서 감사합니다.</p>",
|
|
22
|
+
});
|
|
23
|
+
res.seq; // 발송 로그 seq
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## `smtpStatus(seq)`
|
|
27
|
+
|
|
28
|
+
SMTP 발송 로그의 처리 상태를 조회합니다.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const res = await client.smtpStatus(5);
|
|
32
|
+
res.status; // "sent" | "failed" | "pending"
|
|
33
|
+
```
|