entity-server-client 0.2.4 → 0.2.5
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/dist/EntityServerClient.d.ts +203 -0
- package/dist/client/hmac.d.ts +8 -0
- package/dist/client/packet.d.ts +22 -0
- package/dist/client/request.d.ts +16 -0
- package/dist/client/utils.d.ts +4 -0
- package/dist/index.d.ts +3 -393
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/react.js +1 -1
- package/dist/react.js.map +4 -4
- package/dist/types.d.ts +165 -0
- package/package.json +1 -1
- package/src/EntityServerClient.ts +546 -0
- package/src/client/hmac.ts +41 -0
- package/src/client/packet.ts +113 -0
- package/src/client/request.ts +102 -0
- package/src/client/utils.ts +18 -0
- package/src/index.ts +3 -917
- package/src/types.ts +186 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { derivePacketKey, encryptPacket, decryptPacket } from "./packet";
|
|
2
|
+
import { buildHmacHeaders } from "./hmac";
|
|
3
|
+
|
|
4
|
+
export interface RequestOptions {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
token: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
hmacSecret: string;
|
|
9
|
+
packetMagicLen: number;
|
|
10
|
+
encryptRequests: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Entity Server에 HTTP 요청을 보냅니다.
|
|
15
|
+
*
|
|
16
|
+
* - `encryptRequests` 활성화 시 인증된 POST 바디를 자동 암호화합니다.
|
|
17
|
+
* - 응답이 `application/octet-stream`이면 자동 복호화합니다.
|
|
18
|
+
* - JSON 응답의 `ok`가 false이면 에러를 던집니다.
|
|
19
|
+
*/
|
|
20
|
+
export async function entityRequest<T>(
|
|
21
|
+
opts: RequestOptions,
|
|
22
|
+
method: string,
|
|
23
|
+
path: string,
|
|
24
|
+
body?: unknown,
|
|
25
|
+
withAuth = true,
|
|
26
|
+
extraHeaders: Record<string, string> = {},
|
|
27
|
+
): Promise<T> {
|
|
28
|
+
const {
|
|
29
|
+
baseUrl,
|
|
30
|
+
token,
|
|
31
|
+
apiKey,
|
|
32
|
+
hmacSecret,
|
|
33
|
+
packetMagicLen,
|
|
34
|
+
encryptRequests,
|
|
35
|
+
} = opts;
|
|
36
|
+
const isHmacMode = withAuth && !!(apiKey && hmacSecret);
|
|
37
|
+
|
|
38
|
+
const headers: Record<string, string> = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
...extraHeaders,
|
|
41
|
+
};
|
|
42
|
+
if (!isHmacMode && withAuth && token) {
|
|
43
|
+
headers.Authorization = `Bearer ${token}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let fetchBody: string | Uint8Array | null = null;
|
|
47
|
+
if (body != null) {
|
|
48
|
+
const shouldEncrypt =
|
|
49
|
+
encryptRequests &&
|
|
50
|
+
withAuth &&
|
|
51
|
+
(token || isHmacMode) &&
|
|
52
|
+
method !== "GET" &&
|
|
53
|
+
method !== "HEAD";
|
|
54
|
+
|
|
55
|
+
if (shouldEncrypt) {
|
|
56
|
+
const key = derivePacketKey(hmacSecret, token);
|
|
57
|
+
fetchBody = encryptPacket(
|
|
58
|
+
new TextEncoder().encode(JSON.stringify(body)),
|
|
59
|
+
key,
|
|
60
|
+
packetMagicLen,
|
|
61
|
+
);
|
|
62
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
63
|
+
} else {
|
|
64
|
+
fetchBody = JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isHmacMode) {
|
|
69
|
+
const bodyBytes =
|
|
70
|
+
fetchBody instanceof Uint8Array
|
|
71
|
+
? fetchBody
|
|
72
|
+
: typeof fetchBody === "string"
|
|
73
|
+
? new TextEncoder().encode(fetchBody)
|
|
74
|
+
: new Uint8Array(0);
|
|
75
|
+
Object.assign(
|
|
76
|
+
headers,
|
|
77
|
+
buildHmacHeaders(method, path, bodyBytes, apiKey, hmacSecret),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const res = await fetch(baseUrl + path, {
|
|
82
|
+
method,
|
|
83
|
+
headers,
|
|
84
|
+
...(fetchBody != null ? { body: fetchBody as BodyInit } : {}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const contentType = res.headers.get("Content-Type") ?? "";
|
|
88
|
+
if (contentType.includes("application/octet-stream")) {
|
|
89
|
+
const key = derivePacketKey(hmacSecret, token);
|
|
90
|
+
return decryptPacket<T>(await res.arrayBuffer(), key, packetMagicLen);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = await res.json();
|
|
94
|
+
if (!data.ok) {
|
|
95
|
+
const err = new Error(
|
|
96
|
+
data.message ?? `EntityServer error (HTTP ${res.status})`,
|
|
97
|
+
);
|
|
98
|
+
(err as { status?: number }).status = res.status;
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
return data as T;
|
|
102
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Vite 환경변수(`import.meta.env`)에서 값을 읽습니다. */
|
|
2
|
+
export function readEnv(name: string): string | undefined {
|
|
3
|
+
const meta = import.meta as unknown as {
|
|
4
|
+
env?: Record<string, string | undefined>;
|
|
5
|
+
};
|
|
6
|
+
return meta?.env?.[name];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** 쿼리 파라미터 객체를 URL 쿼리 문자열로 변환합니다. `orderBy` 키는 `order_by`로 변환됩니다. */
|
|
10
|
+
export function buildQuery(params: Record<string, unknown>): string {
|
|
11
|
+
return Object.entries(params)
|
|
12
|
+
.filter(([, value]) => value != null)
|
|
13
|
+
.map(
|
|
14
|
+
([key, value]) =>
|
|
15
|
+
`${encodeURIComponent(key === "orderBy" ? "order_by" : key)}=${encodeURIComponent(String(value))}`,
|
|
16
|
+
)
|
|
17
|
+
.join("&");
|
|
18
|
+
}
|