@01.software/sdk 0.2.9-dev.260309.c56872d → 0.2.9-dev.260311.926a6d4
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 +21 -8
- package/dist/auth.cjs +3 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +36 -3
- package/dist/auth.d.ts +36 -3
- package/dist/auth.js +3 -1
- package/dist/auth.js.map +1 -1
- package/dist/components/code-block.cjs +182 -0
- package/dist/components/code-block.cjs.map +1 -0
- package/dist/components/code-block.d.cts +62 -0
- package/dist/components/code-block.d.ts +62 -0
- package/dist/components/code-block.js +152 -0
- package/dist/components/code-block.js.map +1 -0
- package/dist/{flow.cjs → components/flow.cjs} +111 -87
- package/dist/components/flow.cjs.map +1 -0
- package/dist/{flow.d.cts → components/flow.d.cts} +27 -11
- package/dist/{flow.d.ts → components/flow.d.ts} +27 -11
- package/dist/{flow.js → components/flow.js} +113 -88
- package/dist/components/flow.js.map +1 -0
- package/dist/{components.cjs → components/form.cjs} +25 -518
- package/dist/components/form.cjs.map +1 -0
- package/dist/components/form.d.cts +37 -0
- package/dist/components/form.d.ts +37 -0
- package/dist/{components.js → components/form.js} +17 -513
- package/dist/components/form.js.map +1 -0
- package/dist/components/image.cjs +208 -0
- package/dist/components/image.cjs.map +1 -0
- package/dist/components/image.d.cts +44 -0
- package/dist/components/image.d.ts +44 -0
- package/dist/components/image.js +180 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/rich-text.cjs +258 -0
- package/dist/components/rich-text.cjs.map +1 -0
- package/dist/components/rich-text.d.cts +110 -0
- package/dist/components/rich-text.d.ts +110 -0
- package/dist/components/rich-text.js +235 -0
- package/dist/components/rich-text.js.map +1 -0
- package/dist/index.cjs +252 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +324 -161
- package/dist/index.d.ts +324 -161
- package/dist/index.js +255 -258
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-2wbfaDxp.d.cts → payload-types-BjvBwB8Z.d.cts} +1633 -507
- package/dist/{payload-types-2wbfaDxp.d.ts → payload-types-BjvBwB8Z.d.ts} +1633 -507
- package/dist/{webhook-I6ZDGW1d.d.ts → webhook-CszIpUKn.d.cts} +2 -2
- package/dist/{webhook-Byzl1A0g.d.cts → webhook-_LdLdjGa.d.ts} +2 -2
- package/dist/webhook.d.cts +2 -2
- package/dist/webhook.d.ts +2 -2
- package/package.json +66 -33
- package/dist/auth-BieKA-OQ.d.ts +0 -298
- package/dist/auth-CAV8xgZk.d.cts +0 -298
- package/dist/components.cjs.map +0 -1
- package/dist/components.d.cts +0 -240
- package/dist/components.d.ts +0 -240
- package/dist/components.js.map +0 -1
- package/dist/flow.cjs.map +0 -1
- package/dist/flow.js.map +0 -1
package/README.md
CHANGED
|
@@ -34,8 +34,12 @@ import { createServerToken, verifyServerToken, createApiKey } from '@01.software
|
|
|
34
34
|
// Webhook only - webhook handlers
|
|
35
35
|
import { handleWebhook, createTypedWebhookHandler } from '@01.software/sdk/webhook'
|
|
36
36
|
|
|
37
|
-
// Components
|
|
38
|
-
import { RichTextContent } from '@01.software/sdk/components'
|
|
37
|
+
// Components - sub-path imports per domain
|
|
38
|
+
import { RichTextContent } from '@01.software/sdk/components/rich-text'
|
|
39
|
+
import { Image } from '@01.software/sdk/components/image'
|
|
40
|
+
import { FormRenderer } from '@01.software/sdk/components/form'
|
|
41
|
+
import { CodeBlock } from '@01.software/sdk/components/code-block'
|
|
42
|
+
import { FlowRenderer } from '@01.software/sdk/components/flow'
|
|
39
43
|
|
|
40
44
|
```
|
|
41
45
|
|
|
@@ -74,6 +78,7 @@ const order = await client.api.createOrder({
|
|
|
74
78
|
email: 'user@example.com',
|
|
75
79
|
orderProducts: [...],
|
|
76
80
|
totalAmount: 10000,
|
|
81
|
+
discountCode: 'WELCOME10', // optional
|
|
77
82
|
})
|
|
78
83
|
|
|
79
84
|
// SSR prefetch (server)
|
|
@@ -285,6 +290,9 @@ const profile = await client.customer.me()
|
|
|
285
290
|
client.customer.isAuthenticated()
|
|
286
291
|
client.customer.logout()
|
|
287
292
|
|
|
293
|
+
// Orders
|
|
294
|
+
const orders = await client.customer.getMyOrders({ page: 1, limit: 10, status: 'paid' })
|
|
295
|
+
|
|
288
296
|
// Password & email
|
|
289
297
|
await client.customer.forgotPassword('john@example.com')
|
|
290
298
|
await client.customer.resetPassword(token, newPassword)
|
|
@@ -301,7 +309,7 @@ Available only in ServerClient via `client.api.*`.
|
|
|
301
309
|
await client.api.createOrder(params)
|
|
302
310
|
await client.api.updateOrder({ orderNumber, status })
|
|
303
311
|
await client.api.getOrder({ orderNumber })
|
|
304
|
-
await client.api.checkout({ cartId, paymentId, orderNumber, customerSnapshot })
|
|
312
|
+
await client.api.checkout({ cartId, paymentId, orderNumber, customerSnapshot, discountCode? })
|
|
305
313
|
|
|
306
314
|
// Fulfillment & transactions
|
|
307
315
|
await client.api.createFulfillment({ orderNumber, carrier, trackingNumber, items })
|
|
@@ -370,14 +378,19 @@ const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
|
370
378
|
| Category | Collections |
|
|
371
379
|
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
372
380
|
| Tenant | `tenants`, `tenant-metadata`, `tenant-logos`, `tenant-og-images` |
|
|
373
|
-
| Products | `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`, `brands`, `brand-logos` |
|
|
381
|
+
| Products | `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`, `product-collections`, `brands`, `brand-logos` |
|
|
374
382
|
| Orders | `orders`, `order-products`, `returns`, `return-products`, `exchanges`, `exchange-products`, `fulfillments`, `fulfillment-items`, `transactions` |
|
|
375
|
-
| Customers | `customers`, `customer-addresses`
|
|
383
|
+
| Customers | `customers`, `customer-addresses`, `customer-groups`, `customer-group-images` |
|
|
376
384
|
| Carts | `carts`, `cart-items` |
|
|
377
385
|
| Commerce | `discounts`, `shipping-policies` |
|
|
378
|
-
| Content | `posts`, `post-categories`, `post-tags`, `post-images`, `documents`, `document-categories`, `document-images`
|
|
379
|
-
|
|
|
386
|
+
| Content | `posts`, `post-categories`, `post-tags`, `post-images`, `documents`, `document-categories`, `document-types`, `document-images` |
|
|
387
|
+
| Playlists | `playlists`, `playlist-images`, `playlist-categories`, `playlist-tags`, `musics` |
|
|
388
|
+
| Galleries | `galleries`, `gallery-images`, `gallery-categories`, `gallery-tags` |
|
|
389
|
+
| Flows | `flows`, `flow-images`, `flow-node-types`, `flow-edge-types`, `flow-categories`, `flow-tags` |
|
|
390
|
+
| Videos | `videos`, `video-images`, `video-categories`, `video-tags` |
|
|
391
|
+
| Live Streams | `live-streams`, `live-stream-images` |
|
|
380
392
|
| Forms | `forms`, `form-submissions` |
|
|
393
|
+
| Media | `media` |
|
|
381
394
|
|
|
382
395
|
## Utilities
|
|
383
396
|
|
|
@@ -425,7 +438,7 @@ React component for rendering Payload CMS Lexical rich text. Two variants:
|
|
|
425
438
|
- **`StyledRichTextContent`** — Headless component with slot-based customization (`components` prop)
|
|
426
439
|
|
|
427
440
|
```tsx
|
|
428
|
-
import { RichTextContent, StyledRichTextContent } from '@01.software/sdk/components'
|
|
441
|
+
import { RichTextContent, StyledRichTextContent } from '@01.software/sdk/components/rich-text'
|
|
429
442
|
|
|
430
443
|
// Base: full converter control
|
|
431
444
|
<RichTextContent
|
package/dist/auth.cjs
CHANGED
|
@@ -48,7 +48,7 @@ __export(auth_exports, {
|
|
|
48
48
|
});
|
|
49
49
|
module.exports = __toCommonJS(auth_exports);
|
|
50
50
|
|
|
51
|
-
// src/core/internal/utils/
|
|
51
|
+
// src/core/internal/utils/jwt.ts
|
|
52
52
|
var import_jose = require("jose");
|
|
53
53
|
function createServerToken(clientKey, secretKey, expiresIn = "1h") {
|
|
54
54
|
return __async(this, null, function* () {
|
|
@@ -92,6 +92,8 @@ function decodeServerToken(token) {
|
|
|
92
92
|
exp: payload.exp
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
// src/core/internal/utils/encoding.ts
|
|
95
97
|
function createApiKey(clientKey, secretKey) {
|
|
96
98
|
if (!clientKey || !secretKey) {
|
|
97
99
|
throw new Error("clientKey and secretKey are required.");
|
package/dist/auth.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth.ts","../src/core/internal/utils/index.ts"],"sourcesContent":["export type { JwtPayload } from './core/internal/utils'\nexport {\n createServerToken,\n verifyServerToken,\n decodeServerToken,\n createApiKey,\n parseApiKey,\n} from './core/internal/utils'\n","import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n /** Called on 401 when customerToken is set. Return a new token to retry, or null to fail. */\n onUnauthorized?: () => Promise<string | null>\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n onUnauthorized,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n let hasRetried401 = false\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body && !(requestInit.body instanceof FormData)) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Auto-refresh customer token on 401\n if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401) {\n hasRetried401 = true\n try {\n const newToken = await onUnauthorized()\n if (newToken) {\n authToken = newToken\n continue\n }\n } catch {\n // Refresh failed, fall through to throw\n }\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA8C;AA2C9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,oBAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,cAAU,uBAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/core/internal/utils/jwt.ts","../src/core/internal/utils/encoding.ts"],"sourcesContent":["export type { JwtPayload } from './core/internal/utils'\nexport {\n createServerToken,\n verifyServerToken,\n decodeServerToken,\n createApiKey,\n parseApiKey,\n} from './core/internal/utils'\n","import { SignJWT, jwtVerify, decodeJwt } from 'jose'\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n","/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA8C;AAY9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,oBAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAKA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAOO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,cAAU,uBAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;;;AC1EO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAEA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAKO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
package/dist/auth.d.cts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import './payload-types-BjvBwB8Z.cjs';
|
|
2
|
+
|
|
3
|
+
interface JwtPayload {
|
|
4
|
+
clientKey: string;
|
|
5
|
+
iat?: number;
|
|
6
|
+
exp?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a JWT token for server-side authentication.
|
|
10
|
+
* The token is valid for 1 hour by default.
|
|
11
|
+
*/
|
|
12
|
+
declare function createServerToken(clientKey: string, secretKey: string, expiresIn?: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Verifies a JWT token and returns the payload.
|
|
15
|
+
*/
|
|
16
|
+
declare function verifyServerToken(token: string, secretKey: string): Promise<JwtPayload>;
|
|
17
|
+
/**
|
|
18
|
+
* Decodes a JWT token without verification.
|
|
19
|
+
* WARNING: Use this only when you need to inspect token contents.
|
|
20
|
+
* Always use verifyServerToken for authentication.
|
|
21
|
+
*/
|
|
22
|
+
declare function decodeServerToken(token: string): JwtPayload;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Base64-encoded API key from clientKey and secretKey.
|
|
26
|
+
*/
|
|
27
|
+
declare function createApiKey(clientKey: string, secretKey: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Parses a Base64-encoded API key to extract clientKey and secretKey.
|
|
30
|
+
*/
|
|
31
|
+
declare function parseApiKey(apiKey: string): {
|
|
32
|
+
clientKey: string;
|
|
33
|
+
secretKey: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { type JwtPayload, createApiKey, createServerToken, decodeServerToken, parseApiKey, verifyServerToken };
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import './payload-types-BjvBwB8Z.js';
|
|
2
|
+
|
|
3
|
+
interface JwtPayload {
|
|
4
|
+
clientKey: string;
|
|
5
|
+
iat?: number;
|
|
6
|
+
exp?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a JWT token for server-side authentication.
|
|
10
|
+
* The token is valid for 1 hour by default.
|
|
11
|
+
*/
|
|
12
|
+
declare function createServerToken(clientKey: string, secretKey: string, expiresIn?: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Verifies a JWT token and returns the payload.
|
|
15
|
+
*/
|
|
16
|
+
declare function verifyServerToken(token: string, secretKey: string): Promise<JwtPayload>;
|
|
17
|
+
/**
|
|
18
|
+
* Decodes a JWT token without verification.
|
|
19
|
+
* WARNING: Use this only when you need to inspect token contents.
|
|
20
|
+
* Always use verifyServerToken for authentication.
|
|
21
|
+
*/
|
|
22
|
+
declare function decodeServerToken(token: string): JwtPayload;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Base64-encoded API key from clientKey and secretKey.
|
|
26
|
+
*/
|
|
27
|
+
declare function createApiKey(clientKey: string, secretKey: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Parses a Base64-encoded API key to extract clientKey and secretKey.
|
|
30
|
+
*/
|
|
31
|
+
declare function parseApiKey(apiKey: string): {
|
|
32
|
+
clientKey: string;
|
|
33
|
+
secretKey: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { type JwtPayload, createApiKey, createServerToken, decodeServerToken, parseApiKey, verifyServerToken };
|
package/dist/auth.js
CHANGED
|
@@ -19,7 +19,7 @@ var __async = (__this, __arguments, generator) => {
|
|
|
19
19
|
});
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
// src/core/internal/utils/
|
|
22
|
+
// src/core/internal/utils/jwt.ts
|
|
23
23
|
import { SignJWT, jwtVerify, decodeJwt } from "jose";
|
|
24
24
|
function createServerToken(clientKey, secretKey, expiresIn = "1h") {
|
|
25
25
|
return __async(this, null, function* () {
|
|
@@ -63,6 +63,8 @@ function decodeServerToken(token) {
|
|
|
63
63
|
exp: payload.exp
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
// src/core/internal/utils/encoding.ts
|
|
66
68
|
function createApiKey(clientKey, secretKey) {
|
|
67
69
|
if (!clientKey || !secretKey) {
|
|
68
70
|
throw new Error("clientKey and secretKey are required.");
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/internal/utils/index.ts"],"sourcesContent":["import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n /** Called on 401 when customerToken is set. Return a new token to retry, or null to fail. */\n onUnauthorized?: () => Promise<string | null>\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n onUnauthorized,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n let hasRetried401 = false\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body && !(requestInit.body instanceof FormData)) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Auto-refresh customer token on 401\n if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401) {\n hasRetried401 = true\n try {\n const newToken = await onUnauthorized()\n if (newToken) {\n authToken = newToken\n continue\n }\n } catch {\n // Refresh failed, fall through to throw\n }\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,WAAW,iBAAiB;AA2C9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,QAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,UAAU,UAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/internal/utils/jwt.ts","../src/core/internal/utils/encoding.ts"],"sourcesContent":["import { SignJWT, jwtVerify, decodeJwt } from 'jose'\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n","/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,WAAW,iBAAiB;AAY9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,QAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAKA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAOO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,UAAU,UAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;;;AC1EO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAEA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAKO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var __async = (__this, __arguments, generator) => {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
var fulfilled = (value) => {
|
|
33
|
+
try {
|
|
34
|
+
step(generator.next(value));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
reject(e);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var rejected = (value) => {
|
|
40
|
+
try {
|
|
41
|
+
step(generator.throw(value));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
reject(e);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
47
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/components/CodeBlock/index.tsx
|
|
52
|
+
var CodeBlock_exports = {};
|
|
53
|
+
__export(CodeBlock_exports, {
|
|
54
|
+
CodeBlock: () => CodeBlock,
|
|
55
|
+
highlight: () => highlight
|
|
56
|
+
});
|
|
57
|
+
module.exports = __toCommonJS(CodeBlock_exports);
|
|
58
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
59
|
+
|
|
60
|
+
// src/components/CodeBlock/highlight.ts
|
|
61
|
+
var import_react = require("react");
|
|
62
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
63
|
+
var import_hast_util_to_jsx_runtime = require("hast-util-to-jsx-runtime");
|
|
64
|
+
var import_shiki = require("shiki");
|
|
65
|
+
var LANGUAGE_ALIASES = {
|
|
66
|
+
js: "javascript",
|
|
67
|
+
ts: "typescript",
|
|
68
|
+
sh: "bash",
|
|
69
|
+
shell: "bash",
|
|
70
|
+
yml: "yaml",
|
|
71
|
+
py: "python",
|
|
72
|
+
rb: "ruby",
|
|
73
|
+
plaintext: "text"
|
|
74
|
+
};
|
|
75
|
+
function normalizeLanguage(lang) {
|
|
76
|
+
return LANGUAGE_ALIASES[lang] || lang;
|
|
77
|
+
}
|
|
78
|
+
function highlight(code, lang, theme = "github-dark") {
|
|
79
|
+
return __async(this, null, function* () {
|
|
80
|
+
const normalized = normalizeLanguage(lang);
|
|
81
|
+
try {
|
|
82
|
+
const hast = yield (0, import_shiki.codeToHast)(code, {
|
|
83
|
+
lang: normalized,
|
|
84
|
+
theme
|
|
85
|
+
});
|
|
86
|
+
return (0, import_hast_util_to_jsx_runtime.toJsxRuntime)(hast, { Fragment: import_react.Fragment, jsx: import_jsx_runtime.jsx, jsxs: import_jsx_runtime.jsxs });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
const hast = yield (0, import_shiki.codeToHast)(code, { lang: "text", theme });
|
|
89
|
+
return (0, import_hast_util_to_jsx_runtime.toJsxRuntime)(hast, { Fragment: import_react.Fragment, jsx: import_jsx_runtime.jsx, jsxs: import_jsx_runtime.jsxs });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/components/CodeBlock/index.tsx
|
|
95
|
+
function CodeBlock({
|
|
96
|
+
code,
|
|
97
|
+
language = "typescript",
|
|
98
|
+
theme = "github-dark",
|
|
99
|
+
className,
|
|
100
|
+
initial,
|
|
101
|
+
showLineNumbers = false,
|
|
102
|
+
showCopyButton = true
|
|
103
|
+
}) {
|
|
104
|
+
const [nodes, setNodes] = (0, import_react2.useState)(initial);
|
|
105
|
+
const [copied, setCopied] = (0, import_react2.useState)(false);
|
|
106
|
+
(0, import_react2.useEffect)(() => {
|
|
107
|
+
let cancelled = false;
|
|
108
|
+
void highlight(code, language, theme).then((el) => {
|
|
109
|
+
if (!cancelled) setNodes(el);
|
|
110
|
+
});
|
|
111
|
+
return () => {
|
|
112
|
+
cancelled = true;
|
|
113
|
+
};
|
|
114
|
+
}, [code, language, theme]);
|
|
115
|
+
const handleCopy = () => {
|
|
116
|
+
void navigator.clipboard.writeText(code).then(() => {
|
|
117
|
+
setCopied(true);
|
|
118
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
119
|
+
}, () => {
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
return /* @__PURE__ */ import_react2.default.createElement("div", { className, style: { position: "relative" } }, showCopyButton && /* @__PURE__ */ import_react2.default.createElement(
|
|
123
|
+
"button",
|
|
124
|
+
{
|
|
125
|
+
type: "button",
|
|
126
|
+
onClick: handleCopy,
|
|
127
|
+
"aria-label": "Copy code",
|
|
128
|
+
style: {
|
|
129
|
+
position: "absolute",
|
|
130
|
+
top: 8,
|
|
131
|
+
right: 8,
|
|
132
|
+
zIndex: 1,
|
|
133
|
+
padding: "4px 8px",
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
lineHeight: 1,
|
|
136
|
+
border: "1px solid rgba(255,255,255,0.2)",
|
|
137
|
+
borderRadius: 4,
|
|
138
|
+
background: "rgba(0,0,0,0.3)",
|
|
139
|
+
color: "#ccc",
|
|
140
|
+
cursor: "pointer",
|
|
141
|
+
opacity: 0.7,
|
|
142
|
+
transition: "opacity 0.15s"
|
|
143
|
+
},
|
|
144
|
+
onMouseEnter: (e) => {
|
|
145
|
+
e.currentTarget.style.opacity = "1";
|
|
146
|
+
},
|
|
147
|
+
onMouseLeave: (e) => {
|
|
148
|
+
e.currentTarget.style.opacity = "0.7";
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
copied ? "Copied!" : "Copy"
|
|
152
|
+
), showLineNumbers && nodes ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ import_react2.default.createElement(
|
|
153
|
+
"div",
|
|
154
|
+
{
|
|
155
|
+
"aria-hidden": true,
|
|
156
|
+
style: {
|
|
157
|
+
padding: "1em 0.5em 1em 1em",
|
|
158
|
+
textAlign: "right",
|
|
159
|
+
userSelect: "none",
|
|
160
|
+
color: "rgba(255,255,255,0.3)",
|
|
161
|
+
fontFamily: "monospace",
|
|
162
|
+
fontSize: 13,
|
|
163
|
+
lineHeight: 1.5
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
code.split("\n").map((_, i) => /* @__PURE__ */ import_react2.default.createElement("div", { key: i }, i + 1))
|
|
167
|
+
), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, overflow: "auto" } }, nodes)) : nodes != null ? nodes : /* @__PURE__ */ import_react2.default.createElement(
|
|
168
|
+
"pre",
|
|
169
|
+
{
|
|
170
|
+
style: {
|
|
171
|
+
margin: 0,
|
|
172
|
+
padding: "1em",
|
|
173
|
+
overflow: "auto",
|
|
174
|
+
fontFamily: "monospace",
|
|
175
|
+
fontSize: 13,
|
|
176
|
+
lineHeight: 1.5
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
/* @__PURE__ */ import_react2.default.createElement("code", null, code)
|
|
180
|
+
));
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=code-block.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/CodeBlock/index.tsx","../../src/components/CodeBlock/highlight.ts"],"sourcesContent":["'use client'\n\nimport React, { useEffect, useState, type JSX } from 'react'\nimport { highlight, type BundledTheme } from './highlight'\n\nexport type { BundledTheme }\nexport { highlight }\n\nexport interface CodeBlockProps {\n /** Code string to highlight */\n code: string\n /** Language identifier (e.g. 'typescript', 'js', 'python') */\n language?: string\n /** Shiki theme name. Default: 'github-dark' */\n theme?: BundledTheme\n /** CSS class for the wrapper */\n className?: string\n /** Pre-rendered JSX from server component via `highlight()` */\n initial?: JSX.Element\n /** Show line numbers. Default: false */\n showLineNumbers?: boolean\n /** Show copy button. Default: true */\n showCopyButton?: boolean\n}\n\n/**\n * Syntax-highlighted code block using shiki.\n *\n * @example Basic usage\n * ```tsx\n * <CodeBlock code=\"const x = 1\" language=\"typescript\" />\n * ```\n *\n * @example With server pre-rendering (Next.js)\n * ```tsx\n * // Server Component\n * const initial = await highlight(code, 'typescript')\n * return <CodeBlock code={code} language=\"typescript\" initial={initial} />\n * ```\n *\n * @example As RichTextContent block renderer\n * ```tsx\n * <RichTextContent\n * data={data}\n * blocks={{\n * Code: ({ node }) => (\n * <CodeBlock code={node.fields.code} language={node.fields.language} />\n * ),\n * }}\n * />\n * ```\n */\nexport function CodeBlock({\n code,\n language = 'typescript',\n theme = 'github-dark',\n className,\n initial,\n showLineNumbers = false,\n showCopyButton = true,\n}: CodeBlockProps) {\n const [nodes, setNodes] = useState<JSX.Element | undefined>(initial)\n const [copied, setCopied] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n void highlight(code, language, theme).then((el) => {\n if (!cancelled) setNodes(el)\n })\n return () => {\n cancelled = true\n }\n }, [code, language, theme])\n\n const handleCopy = () => {\n void navigator.clipboard.writeText(code).then(() => {\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n }, () => {})\n }\n\n return (\n <div className={className} style={{ position: 'relative' }}>\n {showCopyButton && (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label=\"Copy code\"\n style={{\n position: 'absolute',\n top: 8,\n right: 8,\n zIndex: 1,\n padding: '4px 8px',\n fontSize: 12,\n lineHeight: 1,\n border: '1px solid rgba(255,255,255,0.2)',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.3)',\n color: '#ccc',\n cursor: 'pointer',\n opacity: 0.7,\n transition: 'opacity 0.15s',\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = '1'\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = '0.7'\n }}\n >\n {copied ? 'Copied!' : 'Copy'}\n </button>\n )}\n {showLineNumbers && nodes ? (\n <div style={{ display: 'flex' }}>\n <div\n aria-hidden\n style={{\n padding: '1em 0.5em 1em 1em',\n textAlign: 'right',\n userSelect: 'none',\n color: 'rgba(255,255,255,0.3)',\n fontFamily: 'monospace',\n fontSize: 13,\n lineHeight: 1.5,\n }}\n >\n {code.split('\\n').map((_, i) => (\n <div key={i}>{i + 1}</div>\n ))}\n </div>\n <div style={{ flex: 1, overflow: 'auto' }}>{nodes}</div>\n </div>\n ) : (\n nodes ?? (\n <pre\n style={{\n margin: 0,\n padding: '1em',\n overflow: 'auto',\n fontFamily: 'monospace',\n fontSize: 13,\n lineHeight: 1.5,\n }}\n >\n <code>{code}</code>\n </pre>\n )\n )}\n </div>\n )\n}\n","import type { JSX } from 'react'\nimport { Fragment } from 'react'\nimport { jsx, jsxs } from 'react/jsx-runtime'\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime'\nimport { codeToHast, type BundledLanguage, type BundledTheme } from 'shiki'\n\nexport type { BundledLanguage, BundledTheme }\n\n/** Normalize language aliases to shiki-compatible language IDs */\nconst LANGUAGE_ALIASES: Record<string, string> = {\n js: 'javascript',\n ts: 'typescript',\n sh: 'bash',\n shell: 'bash',\n yml: 'yaml',\n py: 'python',\n rb: 'ruby',\n plaintext: 'text',\n}\n\nfunction normalizeLanguage(lang: string): string {\n return LANGUAGE_ALIASES[lang] || lang\n}\n\n/**\n * Highlight code to JSX using shiki.\n * Works in both Server and Client components.\n *\n * @example Server Component\n * ```tsx\n * const highlighted = await highlight('const x = 1', 'typescript')\n * return <div>{highlighted}</div>\n * ```\n */\nexport async function highlight(\n code: string,\n lang: string,\n theme: BundledTheme = 'github-dark',\n): Promise<JSX.Element> {\n const normalized = normalizeLanguage(lang)\n try {\n const hast = await codeToHast(code, {\n lang: normalized as BundledLanguage,\n theme,\n })\n return toJsxRuntime(hast, { Fragment, jsx, jsxs }) as JSX.Element\n } catch {\n // Fallback to plain text if language is not supported\n const hast = await codeToHast(code, { lang: 'text', theme })\n return toJsxRuntime(hast, { Fragment, jsx, jsxs }) as JSX.Element\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAAA,gBAAqD;;;ACDrD,mBAAyB;AACzB,yBAA0B;AAC1B,sCAA6B;AAC7B,mBAAoE;AAKpE,IAAM,mBAA2C;AAAA,EAC/C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,WAAW;AACb;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,iBAAiB,IAAI,KAAK;AACnC;AAYA,SAAsB,UACpB,MACA,MACA,QAAsB,eACA;AAAA;AACtB,UAAM,aAAa,kBAAkB,IAAI;AACzC,QAAI;AACF,YAAM,OAAO,UAAM,yBAAW,MAAM;AAAA,QAClC,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AACD,iBAAO,8CAAa,MAAM,EAAE,iCAAU,6BAAK,8BAAK,CAAC;AAAA,IACnD,SAAQ;AAEN,YAAM,OAAO,UAAM,yBAAW,MAAM,EAAE,MAAM,QAAQ,MAAM,CAAC;AAC3D,iBAAO,8CAAa,MAAM,EAAE,iCAAU,6BAAK,8BAAK,CAAC;AAAA,IACnD;AAAA,EACF;AAAA;;;ADCO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA,WAAW;AAAA,EACX,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,iBAAiB;AACnB,GAAmB;AACjB,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAkC,OAAO;AACnE,QAAM,CAAC,QAAQ,SAAS,QAAI,wBAAS,KAAK;AAE1C,+BAAU,MAAM;AACd,QAAI,YAAY;AAChB,SAAK,UAAU,MAAM,UAAU,KAAK,EAAE,KAAK,CAAC,OAAO;AACjD,UAAI,CAAC,UAAW,UAAS,EAAE;AAAA,IAC7B,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC;AAE1B,QAAM,aAAa,MAAM;AACvB,SAAK,UAAU,UAAU,UAAU,IAAI,EAAE,KAAK,MAAM;AAClD,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAAA,IACzC,GAAG,MAAM;AAAA,IAAC,CAAC;AAAA,EACb;AAEA,SACE,8BAAAC,QAAA,cAAC,SAAI,WAAsB,OAAO,EAAE,UAAU,WAAW,KACtD,kBACC,8BAAAA,QAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS;AAAA,MACT,cAAW;AAAA,MACX,OAAO;AAAA,QACL,UAAU;AAAA,QACV,KAAK;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAY;AAAA,MACd;AAAA,MACA,cAAc,CAAC,MAAM;AACnB,UAAE,cAAc,MAAM,UAAU;AAAA,MAClC;AAAA,MACA,cAAc,CAAC,MAAM;AACnB,UAAE,cAAc,MAAM,UAAU;AAAA,MAClC;AAAA;AAAA,IAEC,SAAS,YAAY;AAAA,EACxB,GAED,mBAAmB,QAClB,8BAAAA,QAAA,cAAC,SAAI,OAAO,EAAE,SAAS,OAAO,KAC5B,8BAAAA,QAAA;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACX,OAAO;AAAA,QACL,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA;AAAA,IAEC,KAAK,MAAM,IAAI,EAAE,IAAI,CAAC,GAAG,MACxB,8BAAAA,QAAA,cAAC,SAAI,KAAK,KAAI,IAAI,CAAE,CACrB;AAAA,EACH,GACA,8BAAAA,QAAA,cAAC,SAAI,OAAO,EAAE,MAAM,GAAG,UAAU,OAAO,KAAI,KAAM,CACpD,IAEA,wBACE,8BAAAA,QAAA;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA;AAAA,IAEA,8BAAAA,QAAA,cAAC,cAAM,IAAK;AAAA,EACd,CAGN;AAEJ;","names":["import_react","React"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { BundledTheme } from 'shiki';
|
|
3
|
+
export { BundledTheme } from 'shiki';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Highlight code to JSX using shiki.
|
|
7
|
+
* Works in both Server and Client components.
|
|
8
|
+
*
|
|
9
|
+
* @example Server Component
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const highlighted = await highlight('const x = 1', 'typescript')
|
|
12
|
+
* return <div>{highlighted}</div>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare function highlight(code: string, lang: string, theme?: BundledTheme): Promise<JSX.Element>;
|
|
16
|
+
|
|
17
|
+
interface CodeBlockProps {
|
|
18
|
+
/** Code string to highlight */
|
|
19
|
+
code: string;
|
|
20
|
+
/** Language identifier (e.g. 'typescript', 'js', 'python') */
|
|
21
|
+
language?: string;
|
|
22
|
+
/** Shiki theme name. Default: 'github-dark' */
|
|
23
|
+
theme?: BundledTheme;
|
|
24
|
+
/** CSS class for the wrapper */
|
|
25
|
+
className?: string;
|
|
26
|
+
/** Pre-rendered JSX from server component via `highlight()` */
|
|
27
|
+
initial?: JSX.Element;
|
|
28
|
+
/** Show line numbers. Default: false */
|
|
29
|
+
showLineNumbers?: boolean;
|
|
30
|
+
/** Show copy button. Default: true */
|
|
31
|
+
showCopyButton?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Syntax-highlighted code block using shiki.
|
|
35
|
+
*
|
|
36
|
+
* @example Basic usage
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <CodeBlock code="const x = 1" language="typescript" />
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example With server pre-rendering (Next.js)
|
|
42
|
+
* ```tsx
|
|
43
|
+
* // Server Component
|
|
44
|
+
* const initial = await highlight(code, 'typescript')
|
|
45
|
+
* return <CodeBlock code={code} language="typescript" initial={initial} />
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example As RichTextContent block renderer
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <RichTextContent
|
|
51
|
+
* data={data}
|
|
52
|
+
* blocks={{
|
|
53
|
+
* Code: ({ node }) => (
|
|
54
|
+
* <CodeBlock code={node.fields.code} language={node.fields.language} />
|
|
55
|
+
* ),
|
|
56
|
+
* }}
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function CodeBlock({ code, language, theme, className, initial, showLineNumbers, showCopyButton, }: CodeBlockProps): JSX.Element;
|
|
61
|
+
|
|
62
|
+
export { CodeBlock, type CodeBlockProps, highlight };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { BundledTheme } from 'shiki';
|
|
3
|
+
export { BundledTheme } from 'shiki';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Highlight code to JSX using shiki.
|
|
7
|
+
* Works in both Server and Client components.
|
|
8
|
+
*
|
|
9
|
+
* @example Server Component
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const highlighted = await highlight('const x = 1', 'typescript')
|
|
12
|
+
* return <div>{highlighted}</div>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare function highlight(code: string, lang: string, theme?: BundledTheme): Promise<JSX.Element>;
|
|
16
|
+
|
|
17
|
+
interface CodeBlockProps {
|
|
18
|
+
/** Code string to highlight */
|
|
19
|
+
code: string;
|
|
20
|
+
/** Language identifier (e.g. 'typescript', 'js', 'python') */
|
|
21
|
+
language?: string;
|
|
22
|
+
/** Shiki theme name. Default: 'github-dark' */
|
|
23
|
+
theme?: BundledTheme;
|
|
24
|
+
/** CSS class for the wrapper */
|
|
25
|
+
className?: string;
|
|
26
|
+
/** Pre-rendered JSX from server component via `highlight()` */
|
|
27
|
+
initial?: JSX.Element;
|
|
28
|
+
/** Show line numbers. Default: false */
|
|
29
|
+
showLineNumbers?: boolean;
|
|
30
|
+
/** Show copy button. Default: true */
|
|
31
|
+
showCopyButton?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Syntax-highlighted code block using shiki.
|
|
35
|
+
*
|
|
36
|
+
* @example Basic usage
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <CodeBlock code="const x = 1" language="typescript" />
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example With server pre-rendering (Next.js)
|
|
42
|
+
* ```tsx
|
|
43
|
+
* // Server Component
|
|
44
|
+
* const initial = await highlight(code, 'typescript')
|
|
45
|
+
* return <CodeBlock code={code} language="typescript" initial={initial} />
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example As RichTextContent block renderer
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <RichTextContent
|
|
51
|
+
* data={data}
|
|
52
|
+
* blocks={{
|
|
53
|
+
* Code: ({ node }) => (
|
|
54
|
+
* <CodeBlock code={node.fields.code} language={node.fields.language} />
|
|
55
|
+
* ),
|
|
56
|
+
* }}
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function CodeBlock({ code, language, theme, className, initial, showLineNumbers, showCopyButton, }: CodeBlockProps): JSX.Element;
|
|
61
|
+
|
|
62
|
+
export { CodeBlock, type CodeBlockProps, highlight };
|