@carddb/node 0.3.0 → 0.3.15
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 +28 -0
- package/dist/index.cjs +93 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +90 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -59,6 +59,34 @@ const exchange = await client.decks.exchangeAccessToken({
|
|
|
59
59
|
})
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Direct Signed Deck Tokens
|
|
63
|
+
|
|
64
|
+
Trusted server runtimes can also mint direct signed deck access tokens locally after registering an issuer public key in CardDB.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import {
|
|
68
|
+
generateDirectDeckAccessKeyPair,
|
|
69
|
+
signDirectDeckAccessToken,
|
|
70
|
+
} from '@carddb/node'
|
|
71
|
+
|
|
72
|
+
const keyPair = generateDirectDeckAccessKeyPair()
|
|
73
|
+
|
|
74
|
+
console.log(keyPair.publicKeyBase64) // Register this on directSigningKey.publicKey
|
|
75
|
+
|
|
76
|
+
const token = await signDirectDeckAccessToken({
|
|
77
|
+
issuerId: 'issuer_uuid',
|
|
78
|
+
deckId: 'deck_uuid',
|
|
79
|
+
apiApplicationId: 'app_uuid',
|
|
80
|
+
keyId: 'prod-2026-01',
|
|
81
|
+
privateKey: keyPair.privateKeyPem,
|
|
82
|
+
readMode: 'FULL',
|
|
83
|
+
subject: 'user_123',
|
|
84
|
+
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use an Ed25519 private key in PEM or PKCS#8 DER form. Keep the private key server-side; CardDB only stores the matching public key on the trusted issuer.
|
|
89
|
+
|
|
62
90
|
## Publisher Management
|
|
63
91
|
|
|
64
92
|
Use `secretKey` in trusted server runtimes for publisher workflows such as game management, import formats, import jobs, record upserts/deletes, file-backed imports, and exports.
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto = require('crypto');
|
|
3
4
|
var client = require('@carddb/client');
|
|
4
5
|
|
|
5
6
|
// src/cache.ts
|
|
@@ -65,8 +66,100 @@ var MemoryCache = class {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
};
|
|
69
|
+
var DIRECT_DECK_ACCESS_TOKEN_USE = "carddb.deck_access.v1";
|
|
70
|
+
var DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = "carddb.deck_access";
|
|
71
|
+
function generateDirectDeckAccessKeyPair() {
|
|
72
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
73
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
74
|
+
if (!("x" in jwk) || typeof jwk.x !== "string") {
|
|
75
|
+
throw new TypeError("failed to export Ed25519 public key");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
79
|
+
publicKeyBase64: encodeBase64(decodeBase64URL(jwk.x))
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function signDirectDeckAccessToken(input) {
|
|
83
|
+
const issuedAt = input.issuedAt ?? /* @__PURE__ */ new Date();
|
|
84
|
+
validateRequiredString(input.issuerId, "issuerId");
|
|
85
|
+
validateRequiredString(input.deckId, "deckId");
|
|
86
|
+
validateRequiredString(input.apiApplicationId, "apiApplicationId");
|
|
87
|
+
validateRequiredString(input.keyId, "keyId");
|
|
88
|
+
if (!(issuedAt instanceof Date) || Number.isNaN(issuedAt.getTime())) {
|
|
89
|
+
throw new TypeError("issuedAt must be a valid Date");
|
|
90
|
+
}
|
|
91
|
+
if (!(input.expiresAt instanceof Date) || Number.isNaN(input.expiresAt.getTime())) {
|
|
92
|
+
throw new TypeError("expiresAt must be a valid Date");
|
|
93
|
+
}
|
|
94
|
+
if (input.expiresAt.getTime() <= issuedAt.getTime()) {
|
|
95
|
+
throw new RangeError("expiresAt must be after issuedAt");
|
|
96
|
+
}
|
|
97
|
+
const privateKey = normalizePrivateKey(input.privateKey);
|
|
98
|
+
const header = {
|
|
99
|
+
alg: "EdDSA",
|
|
100
|
+
kid: input.keyId.trim()
|
|
101
|
+
};
|
|
102
|
+
const payload = {
|
|
103
|
+
token_use: DIRECT_DECK_ACCESS_TOKEN_USE,
|
|
104
|
+
deck_id: input.deckId.trim(),
|
|
105
|
+
app_id: input.apiApplicationId.trim(),
|
|
106
|
+
read_mode: normalizeReadMode(input.readMode),
|
|
107
|
+
iss: input.issuerId.trim(),
|
|
108
|
+
aud: DIRECT_DECK_ACCESS_TOKEN_AUDIENCE,
|
|
109
|
+
iat: Math.floor(issuedAt.getTime() / 1e3),
|
|
110
|
+
exp: Math.floor(input.expiresAt.getTime() / 1e3)
|
|
111
|
+
};
|
|
112
|
+
if (input.subject?.trim()) {
|
|
113
|
+
payload.sub = input.subject.trim();
|
|
114
|
+
}
|
|
115
|
+
if (input.tokenId?.trim()) {
|
|
116
|
+
payload.jti = input.tokenId.trim();
|
|
117
|
+
}
|
|
118
|
+
const encodedHeader = encodeBase64URL(JSON.stringify(header));
|
|
119
|
+
const encodedPayload = encodeBase64URL(JSON.stringify(payload));
|
|
120
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
121
|
+
const signature = crypto.sign(null, Buffer.from(signingInput), privateKey);
|
|
122
|
+
return `${signingInput}.${encodeBase64URL(signature)}`;
|
|
123
|
+
}
|
|
124
|
+
function validateRequiredString(value, name) {
|
|
125
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
126
|
+
throw new TypeError(`${name} is required`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function normalizeReadMode(readMode) {
|
|
130
|
+
switch (readMode) {
|
|
131
|
+
case "PUBLIC":
|
|
132
|
+
return "public";
|
|
133
|
+
case "EMBED":
|
|
134
|
+
return "embed";
|
|
135
|
+
case "FULL":
|
|
136
|
+
return "full";
|
|
137
|
+
default:
|
|
138
|
+
throw new TypeError("readMode must be PUBLIC, EMBED, or FULL");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function normalizePrivateKey(privateKey) {
|
|
142
|
+
const keyObject = privateKey instanceof crypto.KeyObject ? privateKey : typeof privateKey === "string" ? crypto.createPrivateKey(privateKey) : crypto.createPrivateKey({ key: Buffer.from(privateKey), format: "der", type: "pkcs8" });
|
|
143
|
+
if (keyObject.type !== "private" || keyObject.asymmetricKeyType !== "ed25519") {
|
|
144
|
+
throw new TypeError("privateKey must be an Ed25519 private key");
|
|
145
|
+
}
|
|
146
|
+
return keyObject;
|
|
147
|
+
}
|
|
148
|
+
function encodeBase64URL(value) {
|
|
149
|
+
return Buffer.from(value).toString("base64url");
|
|
150
|
+
}
|
|
151
|
+
function encodeBase64(value) {
|
|
152
|
+
return Buffer.from(value).toString("base64");
|
|
153
|
+
}
|
|
154
|
+
function decodeBase64URL(value) {
|
|
155
|
+
return Buffer.from(value, "base64url");
|
|
156
|
+
}
|
|
68
157
|
|
|
158
|
+
exports.DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = DIRECT_DECK_ACCESS_TOKEN_AUDIENCE;
|
|
159
|
+
exports.DIRECT_DECK_ACCESS_TOKEN_USE = DIRECT_DECK_ACCESS_TOKEN_USE;
|
|
69
160
|
exports.MemoryCache = MemoryCache;
|
|
161
|
+
exports.generateDirectDeckAccessKeyPair = generateDirectDeckAccessKeyPair;
|
|
162
|
+
exports.signDirectDeckAccessToken = signDirectDeckAccessToken;
|
|
70
163
|
Object.keys(client).forEach(function (k) {
|
|
71
164
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
72
165
|
enumerable: true,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":";;;;;AAiCO,IAAM,cAAN,MAAmC;AAAA,EAChC,KAAA,uBAAY,GAAA,EAAiC;AAAA;AAAA;AAAA;AAAA,EAKrD,IAAO,GAAA,EAAuB;AAC5B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAGnB,IAAA,IAAI,MAAM,SAAA,KAAc,IAAA,IAAQ,KAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AAC5D,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,GAAA,CAAO,GAAA,EAAa,KAAA,EAAU,GAAA,EAAoB;AAChD,IAAA,MAAM,YAAY,GAAA,KAAQ,MAAA,GAAY,KAAK,GAAA,EAAI,GAAI,MAAM,GAAA,GAAO,IAAA;AAChE,IAAA,IAAA,CAAK,MAAM,GAAA,CAAI,GAAA,EAAK,EAAE,KAAA,EAAO,WAAW,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,GAAA,EAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,KAAM,IAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,IAAA,CAAK,KAAA,CAAM,SAAQ,EAAG;AAC/C,MAAA,IAAI,KAAA,CAAM,SAAA,KAAc,IAAA,IAAQ,GAAA,GAAM,MAAM,SAAA,EAAW;AACrD,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["/**\n * In-memory cache implementation with TTL support for Node.js\n */\n\nimport type { Cache } from '@carddb/core'\n\ninterface CacheEntry<T> {\n value: T\n expiresAt: number | null\n}\n\n/**\n * Simple in-memory cache with TTL support.\n * Suitable for server-side applications where memory caching is appropriate.\n *\n * For production use with multiple instances, consider using Redis or another\n * distributed cache that implements the Cache interface.\n *\n * @example\n * import { CardDBClient, MemoryCache } from '@carddb/node'\n *\n * const client = new CardDBClient({\n * cache: new MemoryCache(),\n * cacheTtl: 300 // 5 minutes\n * })\n *\n * @example Manual usage\n * const cache = new MemoryCache()\n * cache.set('key', 'value', 60) // expires in 60 seconds\n * cache.get('key') // => 'value'\n * // After 60 seconds...\n * cache.get('key') // => null\n */\nexport class MemoryCache implements Cache {\n private store = new Map<string, CacheEntry<unknown>>()\n\n /**\n * Get a value from the cache\n */\n get<T>(key: string): T | null {\n const entry = this.store.get(key) as CacheEntry<T> | undefined\n if (!entry) return null\n\n // Check if expired\n if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n this.store.delete(key)\n return null\n }\n\n return entry.value\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - The cache key\n * @param value - The value to cache\n * @param ttl - Time to live in seconds (optional, undefined = no expiry)\n */\n set<T>(key: string, value: T, ttl?: number): void {\n const expiresAt = ttl !== undefined ? Date.now() + ttl * 1000 : null\n this.store.set(key, { value, expiresAt })\n }\n\n /**\n * Delete a value from the cache\n */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /**\n * Clear all cached values\n */\n clear(): void {\n this.store.clear()\n }\n\n /**\n * Check if a key exists and is not expired\n */\n has(key: string): boolean {\n return this.get(key) !== null\n }\n\n /**\n * Get the number of entries (including expired ones that haven't been cleaned up)\n */\n get size(): number {\n return this.store.size\n }\n\n /**\n * Clean up expired entries\n * Call this periodically if you want to reclaim memory from expired entries\n */\n cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.store.entries()) {\n if (entry.expiresAt !== null && now > entry.expiresAt) {\n this.store.delete(key)\n }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts","../src/deck-tokens.ts"],"names":["generateKeyPairSync","signBytes","KeyObject","createPrivateKey"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * In-memory cache implementation with TTL support for Node.js\n */\n\nimport type { Cache } from '@carddb/core'\n\ninterface CacheEntry<T> {\n value: T\n expiresAt: number | null\n}\n\n/**\n * Simple in-memory cache with TTL support.\n * Suitable for server-side applications where memory caching is appropriate.\n *\n * For production use with multiple instances, consider using Redis or another\n * distributed cache that implements the Cache interface.\n *\n * @example\n * import { CardDBClient, MemoryCache } from '@carddb/node'\n *\n * const client = new CardDBClient({\n * cache: new MemoryCache(),\n * cacheTtl: 300 // 5 minutes\n * })\n *\n * @example Manual usage\n * const cache = new MemoryCache()\n * cache.set('key', 'value', 60) // expires in 60 seconds\n * cache.get('key') // => 'value'\n * // After 60 seconds...\n * cache.get('key') // => null\n */\nexport class MemoryCache implements Cache {\n private store = new Map<string, CacheEntry<unknown>>()\n\n /**\n * Get a value from the cache\n */\n get<T>(key: string): T | null {\n const entry = this.store.get(key) as CacheEntry<T> | undefined\n if (!entry) return null\n\n // Check if expired\n if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n this.store.delete(key)\n return null\n }\n\n return entry.value\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - The cache key\n * @param value - The value to cache\n * @param ttl - Time to live in seconds (optional, undefined = no expiry)\n */\n set<T>(key: string, value: T, ttl?: number): void {\n const expiresAt = ttl !== undefined ? Date.now() + ttl * 1000 : null\n this.store.set(key, { value, expiresAt })\n }\n\n /**\n * Delete a value from the cache\n */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /**\n * Clear all cached values\n */\n clear(): void {\n this.store.clear()\n }\n\n /**\n * Check if a key exists and is not expired\n */\n has(key: string): boolean {\n return this.get(key) !== null\n }\n\n /**\n * Get the number of entries (including expired ones that haven't been cleaned up)\n */\n get size(): number {\n return this.store.size\n }\n\n /**\n * Clean up expired entries\n * Call this periodically if you want to reclaim memory from expired entries\n */\n cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.store.entries()) {\n if (entry.expiresAt !== null && now > entry.expiresAt) {\n this.store.delete(key)\n }\n }\n }\n}\n","import { createPrivateKey, generateKeyPairSync, KeyObject, sign as signBytes } from 'node:crypto'\n\nexport const DIRECT_DECK_ACCESS_TOKEN_USE = 'carddb.deck_access.v1'\nexport const DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = 'carddb.deck_access'\n\nexport type DirectDeckAccessReadMode = 'PUBLIC' | 'EMBED' | 'FULL'\n\nexport interface SignDirectDeckAccessTokenInput {\n\tissuerId: string\n\tdeckId: string\n\tapiApplicationId: string\n\tkeyId: string\n\tprivateKey: KeyObject | string | Uint8Array\n\treadMode: DirectDeckAccessReadMode\n\tsubject?: string\n\ttokenId?: string\n\tissuedAt?: Date\n\texpiresAt: Date\n}\n\nexport interface DirectDeckAccessKeyPair {\n\tprivateKeyPem: string\n\tpublicKeyBase64: string\n}\n\nexport function generateDirectDeckAccessKeyPair(): DirectDeckAccessKeyPair {\n\tconst { privateKey, publicKey } = generateKeyPairSync('ed25519')\n\tconst jwk = publicKey.export({ format: 'jwk' })\n\tif (!('x' in jwk) || typeof jwk.x !== 'string') {\n\t\tthrow new TypeError('failed to export Ed25519 public key')\n\t}\n\n\treturn {\n\t\tprivateKeyPem: privateKey.export({ format: 'pem', type: 'pkcs8' }).toString(),\n\t\tpublicKeyBase64: encodeBase64(decodeBase64URL(jwk.x)),\n\t}\n}\n\nexport async function signDirectDeckAccessToken(\n\tinput: SignDirectDeckAccessTokenInput\n): Promise<string> {\n\tconst issuedAt = input.issuedAt ?? new Date()\n\tvalidateRequiredString(input.issuerId, 'issuerId')\n\tvalidateRequiredString(input.deckId, 'deckId')\n\tvalidateRequiredString(input.apiApplicationId, 'apiApplicationId')\n\tvalidateRequiredString(input.keyId, 'keyId')\n\tif (!(issuedAt instanceof Date) || Number.isNaN(issuedAt.getTime())) {\n\t\tthrow new TypeError('issuedAt must be a valid Date')\n\t}\n\tif (!(input.expiresAt instanceof Date) || Number.isNaN(input.expiresAt.getTime())) {\n\t\tthrow new TypeError('expiresAt must be a valid Date')\n\t}\n\tif (input.expiresAt.getTime() <= issuedAt.getTime()) {\n\t\tthrow new RangeError('expiresAt must be after issuedAt')\n\t}\n\n\tconst privateKey = normalizePrivateKey(input.privateKey)\n\tconst header = {\n\t\talg: 'EdDSA',\n\t\tkid: input.keyId.trim(),\n\t}\n\tconst payload = {\n\t\ttoken_use: DIRECT_DECK_ACCESS_TOKEN_USE,\n\t\tdeck_id: input.deckId.trim(),\n\t\tapp_id: input.apiApplicationId.trim(),\n\t\tread_mode: normalizeReadMode(input.readMode),\n\t\tiss: input.issuerId.trim(),\n\t\taud: DIRECT_DECK_ACCESS_TOKEN_AUDIENCE,\n\t\tiat: Math.floor(issuedAt.getTime() / 1000),\n\t\texp: Math.floor(input.expiresAt.getTime() / 1000),\n\t} as Record<string, string | number>\n\tif (input.subject?.trim()) {\n\t\tpayload.sub = input.subject.trim()\n\t}\n\tif (input.tokenId?.trim()) {\n\t\tpayload.jti = input.tokenId.trim()\n\t}\n\n\tconst encodedHeader = encodeBase64URL(JSON.stringify(header))\n\tconst encodedPayload = encodeBase64URL(JSON.stringify(payload))\n\tconst signingInput = `${encodedHeader}.${encodedPayload}`\n\tconst signature = signBytes(null, Buffer.from(signingInput), privateKey)\n\n\treturn `${signingInput}.${encodeBase64URL(signature)}`\n}\n\nfunction validateRequiredString(value: string, name: string): void {\n\tif (typeof value !== 'string' || value.trim() === '') {\n\t\tthrow new TypeError(`${name} is required`)\n\t}\n}\n\nfunction normalizeReadMode(readMode: DirectDeckAccessReadMode): string {\n\tswitch (readMode) {\n\t\tcase 'PUBLIC':\n\t\t\treturn 'public'\n\t\tcase 'EMBED':\n\t\t\treturn 'embed'\n\t\tcase 'FULL':\n\t\t\treturn 'full'\n\t\tdefault:\n\t\t\tthrow new TypeError('readMode must be PUBLIC, EMBED, or FULL')\n\t}\n}\n\nfunction normalizePrivateKey(privateKey: KeyObject | string | Uint8Array): KeyObject {\n\tconst keyObject =\n\t\tprivateKey instanceof KeyObject\n\t\t\t? privateKey\n\t\t\t: typeof privateKey === 'string'\n\t\t\t\t? createPrivateKey(privateKey)\n\t\t\t\t: createPrivateKey({ key: Buffer.from(privateKey), format: 'der', type: 'pkcs8' })\n\tif (keyObject.type !== 'private' || keyObject.asymmetricKeyType !== 'ed25519') {\n\t\tthrow new TypeError('privateKey must be an Ed25519 private key')\n\t}\n\treturn keyObject\n}\n\nfunction encodeBase64URL(value: string | Uint8Array): string {\n\treturn Buffer.from(value).toString('base64url')\n}\n\nfunction encodeBase64(value: Uint8Array): string {\n\treturn Buffer.from(value).toString('base64')\n}\n\nfunction decodeBase64URL(value: string): Uint8Array {\n\treturn Buffer.from(value, 'base64url')\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Cache } from '@carddb/core';
|
|
2
|
+
import { KeyObject } from 'node:crypto';
|
|
2
3
|
export * from '@carddb/client';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -64,4 +65,26 @@ declare class MemoryCache implements Cache {
|
|
|
64
65
|
cleanup(): void;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
declare const DIRECT_DECK_ACCESS_TOKEN_USE = "carddb.deck_access.v1";
|
|
69
|
+
declare const DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = "carddb.deck_access";
|
|
70
|
+
type DirectDeckAccessReadMode = 'PUBLIC' | 'EMBED' | 'FULL';
|
|
71
|
+
interface SignDirectDeckAccessTokenInput {
|
|
72
|
+
issuerId: string;
|
|
73
|
+
deckId: string;
|
|
74
|
+
apiApplicationId: string;
|
|
75
|
+
keyId: string;
|
|
76
|
+
privateKey: KeyObject | string | Uint8Array;
|
|
77
|
+
readMode: DirectDeckAccessReadMode;
|
|
78
|
+
subject?: string;
|
|
79
|
+
tokenId?: string;
|
|
80
|
+
issuedAt?: Date;
|
|
81
|
+
expiresAt: Date;
|
|
82
|
+
}
|
|
83
|
+
interface DirectDeckAccessKeyPair {
|
|
84
|
+
privateKeyPem: string;
|
|
85
|
+
publicKeyBase64: string;
|
|
86
|
+
}
|
|
87
|
+
declare function generateDirectDeckAccessKeyPair(): DirectDeckAccessKeyPair;
|
|
88
|
+
declare function signDirectDeckAccessToken(input: SignDirectDeckAccessTokenInput): Promise<string>;
|
|
89
|
+
|
|
90
|
+
export { DIRECT_DECK_ACCESS_TOKEN_AUDIENCE, DIRECT_DECK_ACCESS_TOKEN_USE, type DirectDeckAccessKeyPair, type DirectDeckAccessReadMode, MemoryCache, type SignDirectDeckAccessTokenInput, generateDirectDeckAccessKeyPair, signDirectDeckAccessToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Cache } from '@carddb/core';
|
|
2
|
+
import { KeyObject } from 'node:crypto';
|
|
2
3
|
export * from '@carddb/client';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -64,4 +65,26 @@ declare class MemoryCache implements Cache {
|
|
|
64
65
|
cleanup(): void;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
declare const DIRECT_DECK_ACCESS_TOKEN_USE = "carddb.deck_access.v1";
|
|
69
|
+
declare const DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = "carddb.deck_access";
|
|
70
|
+
type DirectDeckAccessReadMode = 'PUBLIC' | 'EMBED' | 'FULL';
|
|
71
|
+
interface SignDirectDeckAccessTokenInput {
|
|
72
|
+
issuerId: string;
|
|
73
|
+
deckId: string;
|
|
74
|
+
apiApplicationId: string;
|
|
75
|
+
keyId: string;
|
|
76
|
+
privateKey: KeyObject | string | Uint8Array;
|
|
77
|
+
readMode: DirectDeckAccessReadMode;
|
|
78
|
+
subject?: string;
|
|
79
|
+
tokenId?: string;
|
|
80
|
+
issuedAt?: Date;
|
|
81
|
+
expiresAt: Date;
|
|
82
|
+
}
|
|
83
|
+
interface DirectDeckAccessKeyPair {
|
|
84
|
+
privateKeyPem: string;
|
|
85
|
+
publicKeyBase64: string;
|
|
86
|
+
}
|
|
87
|
+
declare function generateDirectDeckAccessKeyPair(): DirectDeckAccessKeyPair;
|
|
88
|
+
declare function signDirectDeckAccessToken(input: SignDirectDeckAccessTokenInput): Promise<string>;
|
|
89
|
+
|
|
90
|
+
export { DIRECT_DECK_ACCESS_TOKEN_AUDIENCE, DIRECT_DECK_ACCESS_TOKEN_USE, type DirectDeckAccessKeyPair, type DirectDeckAccessReadMode, MemoryCache, type SignDirectDeckAccessTokenInput, generateDirectDeckAccessKeyPair, signDirectDeckAccessToken };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateKeyPairSync, sign, KeyObject, createPrivateKey } from 'crypto';
|
|
1
2
|
export * from '@carddb/client';
|
|
2
3
|
|
|
3
4
|
// src/cache.ts
|
|
@@ -63,7 +64,95 @@ var MemoryCache = class {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
};
|
|
67
|
+
var DIRECT_DECK_ACCESS_TOKEN_USE = "carddb.deck_access.v1";
|
|
68
|
+
var DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = "carddb.deck_access";
|
|
69
|
+
function generateDirectDeckAccessKeyPair() {
|
|
70
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
71
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
72
|
+
if (!("x" in jwk) || typeof jwk.x !== "string") {
|
|
73
|
+
throw new TypeError("failed to export Ed25519 public key");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
77
|
+
publicKeyBase64: encodeBase64(decodeBase64URL(jwk.x))
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function signDirectDeckAccessToken(input) {
|
|
81
|
+
const issuedAt = input.issuedAt ?? /* @__PURE__ */ new Date();
|
|
82
|
+
validateRequiredString(input.issuerId, "issuerId");
|
|
83
|
+
validateRequiredString(input.deckId, "deckId");
|
|
84
|
+
validateRequiredString(input.apiApplicationId, "apiApplicationId");
|
|
85
|
+
validateRequiredString(input.keyId, "keyId");
|
|
86
|
+
if (!(issuedAt instanceof Date) || Number.isNaN(issuedAt.getTime())) {
|
|
87
|
+
throw new TypeError("issuedAt must be a valid Date");
|
|
88
|
+
}
|
|
89
|
+
if (!(input.expiresAt instanceof Date) || Number.isNaN(input.expiresAt.getTime())) {
|
|
90
|
+
throw new TypeError("expiresAt must be a valid Date");
|
|
91
|
+
}
|
|
92
|
+
if (input.expiresAt.getTime() <= issuedAt.getTime()) {
|
|
93
|
+
throw new RangeError("expiresAt must be after issuedAt");
|
|
94
|
+
}
|
|
95
|
+
const privateKey = normalizePrivateKey(input.privateKey);
|
|
96
|
+
const header = {
|
|
97
|
+
alg: "EdDSA",
|
|
98
|
+
kid: input.keyId.trim()
|
|
99
|
+
};
|
|
100
|
+
const payload = {
|
|
101
|
+
token_use: DIRECT_DECK_ACCESS_TOKEN_USE,
|
|
102
|
+
deck_id: input.deckId.trim(),
|
|
103
|
+
app_id: input.apiApplicationId.trim(),
|
|
104
|
+
read_mode: normalizeReadMode(input.readMode),
|
|
105
|
+
iss: input.issuerId.trim(),
|
|
106
|
+
aud: DIRECT_DECK_ACCESS_TOKEN_AUDIENCE,
|
|
107
|
+
iat: Math.floor(issuedAt.getTime() / 1e3),
|
|
108
|
+
exp: Math.floor(input.expiresAt.getTime() / 1e3)
|
|
109
|
+
};
|
|
110
|
+
if (input.subject?.trim()) {
|
|
111
|
+
payload.sub = input.subject.trim();
|
|
112
|
+
}
|
|
113
|
+
if (input.tokenId?.trim()) {
|
|
114
|
+
payload.jti = input.tokenId.trim();
|
|
115
|
+
}
|
|
116
|
+
const encodedHeader = encodeBase64URL(JSON.stringify(header));
|
|
117
|
+
const encodedPayload = encodeBase64URL(JSON.stringify(payload));
|
|
118
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
119
|
+
const signature = sign(null, Buffer.from(signingInput), privateKey);
|
|
120
|
+
return `${signingInput}.${encodeBase64URL(signature)}`;
|
|
121
|
+
}
|
|
122
|
+
function validateRequiredString(value, name) {
|
|
123
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
124
|
+
throw new TypeError(`${name} is required`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function normalizeReadMode(readMode) {
|
|
128
|
+
switch (readMode) {
|
|
129
|
+
case "PUBLIC":
|
|
130
|
+
return "public";
|
|
131
|
+
case "EMBED":
|
|
132
|
+
return "embed";
|
|
133
|
+
case "FULL":
|
|
134
|
+
return "full";
|
|
135
|
+
default:
|
|
136
|
+
throw new TypeError("readMode must be PUBLIC, EMBED, or FULL");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function normalizePrivateKey(privateKey) {
|
|
140
|
+
const keyObject = privateKey instanceof KeyObject ? privateKey : typeof privateKey === "string" ? createPrivateKey(privateKey) : createPrivateKey({ key: Buffer.from(privateKey), format: "der", type: "pkcs8" });
|
|
141
|
+
if (keyObject.type !== "private" || keyObject.asymmetricKeyType !== "ed25519") {
|
|
142
|
+
throw new TypeError("privateKey must be an Ed25519 private key");
|
|
143
|
+
}
|
|
144
|
+
return keyObject;
|
|
145
|
+
}
|
|
146
|
+
function encodeBase64URL(value) {
|
|
147
|
+
return Buffer.from(value).toString("base64url");
|
|
148
|
+
}
|
|
149
|
+
function encodeBase64(value) {
|
|
150
|
+
return Buffer.from(value).toString("base64");
|
|
151
|
+
}
|
|
152
|
+
function decodeBase64URL(value) {
|
|
153
|
+
return Buffer.from(value, "base64url");
|
|
154
|
+
}
|
|
66
155
|
|
|
67
|
-
export { MemoryCache };
|
|
156
|
+
export { DIRECT_DECK_ACCESS_TOKEN_AUDIENCE, DIRECT_DECK_ACCESS_TOKEN_USE, MemoryCache, generateDirectDeckAccessKeyPair, signDirectDeckAccessToken };
|
|
68
157
|
//# sourceMappingURL=index.js.map
|
|
69
158
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":";;;AAiCO,IAAM,cAAN,MAAmC;AAAA,EAChC,KAAA,uBAAY,GAAA,EAAiC;AAAA;AAAA;AAAA;AAAA,EAKrD,IAAO,GAAA,EAAuB;AAC5B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAGnB,IAAA,IAAI,MAAM,SAAA,KAAc,IAAA,IAAQ,KAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AAC5D,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,GAAA,CAAO,GAAA,EAAa,KAAA,EAAU,GAAA,EAAoB;AAChD,IAAA,MAAM,YAAY,GAAA,KAAQ,MAAA,GAAY,KAAK,GAAA,EAAI,GAAI,MAAM,GAAA,GAAO,IAAA;AAChE,IAAA,IAAA,CAAK,MAAM,GAAA,CAAI,GAAA,EAAK,EAAE,KAAA,EAAO,WAAW,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,GAAA,EAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,KAAM,IAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,IAAA,CAAK,KAAA,CAAM,SAAQ,EAAG;AAC/C,MAAA,IAAI,KAAA,CAAM,SAAA,KAAc,IAAA,IAAQ,GAAA,GAAM,MAAM,SAAA,EAAW;AACrD,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF","file":"index.js","sourcesContent":["/**\n * In-memory cache implementation with TTL support for Node.js\n */\n\nimport type { Cache } from '@carddb/core'\n\ninterface CacheEntry<T> {\n value: T\n expiresAt: number | null\n}\n\n/**\n * Simple in-memory cache with TTL support.\n * Suitable for server-side applications where memory caching is appropriate.\n *\n * For production use with multiple instances, consider using Redis or another\n * distributed cache that implements the Cache interface.\n *\n * @example\n * import { CardDBClient, MemoryCache } from '@carddb/node'\n *\n * const client = new CardDBClient({\n * cache: new MemoryCache(),\n * cacheTtl: 300 // 5 minutes\n * })\n *\n * @example Manual usage\n * const cache = new MemoryCache()\n * cache.set('key', 'value', 60) // expires in 60 seconds\n * cache.get('key') // => 'value'\n * // After 60 seconds...\n * cache.get('key') // => null\n */\nexport class MemoryCache implements Cache {\n private store = new Map<string, CacheEntry<unknown>>()\n\n /**\n * Get a value from the cache\n */\n get<T>(key: string): T | null {\n const entry = this.store.get(key) as CacheEntry<T> | undefined\n if (!entry) return null\n\n // Check if expired\n if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n this.store.delete(key)\n return null\n }\n\n return entry.value\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - The cache key\n * @param value - The value to cache\n * @param ttl - Time to live in seconds (optional, undefined = no expiry)\n */\n set<T>(key: string, value: T, ttl?: number): void {\n const expiresAt = ttl !== undefined ? Date.now() + ttl * 1000 : null\n this.store.set(key, { value, expiresAt })\n }\n\n /**\n * Delete a value from the cache\n */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /**\n * Clear all cached values\n */\n clear(): void {\n this.store.clear()\n }\n\n /**\n * Check if a key exists and is not expired\n */\n has(key: string): boolean {\n return this.get(key) !== null\n }\n\n /**\n * Get the number of entries (including expired ones that haven't been cleaned up)\n */\n get size(): number {\n return this.store.size\n }\n\n /**\n * Clean up expired entries\n * Call this periodically if you want to reclaim memory from expired entries\n */\n cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.store.entries()) {\n if (entry.expiresAt !== null && now > entry.expiresAt) {\n this.store.delete(key)\n }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts","../src/deck-tokens.ts"],"names":["signBytes"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-memory cache implementation with TTL support for Node.js\n */\n\nimport type { Cache } from '@carddb/core'\n\ninterface CacheEntry<T> {\n value: T\n expiresAt: number | null\n}\n\n/**\n * Simple in-memory cache with TTL support.\n * Suitable for server-side applications where memory caching is appropriate.\n *\n * For production use with multiple instances, consider using Redis or another\n * distributed cache that implements the Cache interface.\n *\n * @example\n * import { CardDBClient, MemoryCache } from '@carddb/node'\n *\n * const client = new CardDBClient({\n * cache: new MemoryCache(),\n * cacheTtl: 300 // 5 minutes\n * })\n *\n * @example Manual usage\n * const cache = new MemoryCache()\n * cache.set('key', 'value', 60) // expires in 60 seconds\n * cache.get('key') // => 'value'\n * // After 60 seconds...\n * cache.get('key') // => null\n */\nexport class MemoryCache implements Cache {\n private store = new Map<string, CacheEntry<unknown>>()\n\n /**\n * Get a value from the cache\n */\n get<T>(key: string): T | null {\n const entry = this.store.get(key) as CacheEntry<T> | undefined\n if (!entry) return null\n\n // Check if expired\n if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n this.store.delete(key)\n return null\n }\n\n return entry.value\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - The cache key\n * @param value - The value to cache\n * @param ttl - Time to live in seconds (optional, undefined = no expiry)\n */\n set<T>(key: string, value: T, ttl?: number): void {\n const expiresAt = ttl !== undefined ? Date.now() + ttl * 1000 : null\n this.store.set(key, { value, expiresAt })\n }\n\n /**\n * Delete a value from the cache\n */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /**\n * Clear all cached values\n */\n clear(): void {\n this.store.clear()\n }\n\n /**\n * Check if a key exists and is not expired\n */\n has(key: string): boolean {\n return this.get(key) !== null\n }\n\n /**\n * Get the number of entries (including expired ones that haven't been cleaned up)\n */\n get size(): number {\n return this.store.size\n }\n\n /**\n * Clean up expired entries\n * Call this periodically if you want to reclaim memory from expired entries\n */\n cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.store.entries()) {\n if (entry.expiresAt !== null && now > entry.expiresAt) {\n this.store.delete(key)\n }\n }\n }\n}\n","import { createPrivateKey, generateKeyPairSync, KeyObject, sign as signBytes } from 'node:crypto'\n\nexport const DIRECT_DECK_ACCESS_TOKEN_USE = 'carddb.deck_access.v1'\nexport const DIRECT_DECK_ACCESS_TOKEN_AUDIENCE = 'carddb.deck_access'\n\nexport type DirectDeckAccessReadMode = 'PUBLIC' | 'EMBED' | 'FULL'\n\nexport interface SignDirectDeckAccessTokenInput {\n\tissuerId: string\n\tdeckId: string\n\tapiApplicationId: string\n\tkeyId: string\n\tprivateKey: KeyObject | string | Uint8Array\n\treadMode: DirectDeckAccessReadMode\n\tsubject?: string\n\ttokenId?: string\n\tissuedAt?: Date\n\texpiresAt: Date\n}\n\nexport interface DirectDeckAccessKeyPair {\n\tprivateKeyPem: string\n\tpublicKeyBase64: string\n}\n\nexport function generateDirectDeckAccessKeyPair(): DirectDeckAccessKeyPair {\n\tconst { privateKey, publicKey } = generateKeyPairSync('ed25519')\n\tconst jwk = publicKey.export({ format: 'jwk' })\n\tif (!('x' in jwk) || typeof jwk.x !== 'string') {\n\t\tthrow new TypeError('failed to export Ed25519 public key')\n\t}\n\n\treturn {\n\t\tprivateKeyPem: privateKey.export({ format: 'pem', type: 'pkcs8' }).toString(),\n\t\tpublicKeyBase64: encodeBase64(decodeBase64URL(jwk.x)),\n\t}\n}\n\nexport async function signDirectDeckAccessToken(\n\tinput: SignDirectDeckAccessTokenInput\n): Promise<string> {\n\tconst issuedAt = input.issuedAt ?? new Date()\n\tvalidateRequiredString(input.issuerId, 'issuerId')\n\tvalidateRequiredString(input.deckId, 'deckId')\n\tvalidateRequiredString(input.apiApplicationId, 'apiApplicationId')\n\tvalidateRequiredString(input.keyId, 'keyId')\n\tif (!(issuedAt instanceof Date) || Number.isNaN(issuedAt.getTime())) {\n\t\tthrow new TypeError('issuedAt must be a valid Date')\n\t}\n\tif (!(input.expiresAt instanceof Date) || Number.isNaN(input.expiresAt.getTime())) {\n\t\tthrow new TypeError('expiresAt must be a valid Date')\n\t}\n\tif (input.expiresAt.getTime() <= issuedAt.getTime()) {\n\t\tthrow new RangeError('expiresAt must be after issuedAt')\n\t}\n\n\tconst privateKey = normalizePrivateKey(input.privateKey)\n\tconst header = {\n\t\talg: 'EdDSA',\n\t\tkid: input.keyId.trim(),\n\t}\n\tconst payload = {\n\t\ttoken_use: DIRECT_DECK_ACCESS_TOKEN_USE,\n\t\tdeck_id: input.deckId.trim(),\n\t\tapp_id: input.apiApplicationId.trim(),\n\t\tread_mode: normalizeReadMode(input.readMode),\n\t\tiss: input.issuerId.trim(),\n\t\taud: DIRECT_DECK_ACCESS_TOKEN_AUDIENCE,\n\t\tiat: Math.floor(issuedAt.getTime() / 1000),\n\t\texp: Math.floor(input.expiresAt.getTime() / 1000),\n\t} as Record<string, string | number>\n\tif (input.subject?.trim()) {\n\t\tpayload.sub = input.subject.trim()\n\t}\n\tif (input.tokenId?.trim()) {\n\t\tpayload.jti = input.tokenId.trim()\n\t}\n\n\tconst encodedHeader = encodeBase64URL(JSON.stringify(header))\n\tconst encodedPayload = encodeBase64URL(JSON.stringify(payload))\n\tconst signingInput = `${encodedHeader}.${encodedPayload}`\n\tconst signature = signBytes(null, Buffer.from(signingInput), privateKey)\n\n\treturn `${signingInput}.${encodeBase64URL(signature)}`\n}\n\nfunction validateRequiredString(value: string, name: string): void {\n\tif (typeof value !== 'string' || value.trim() === '') {\n\t\tthrow new TypeError(`${name} is required`)\n\t}\n}\n\nfunction normalizeReadMode(readMode: DirectDeckAccessReadMode): string {\n\tswitch (readMode) {\n\t\tcase 'PUBLIC':\n\t\t\treturn 'public'\n\t\tcase 'EMBED':\n\t\t\treturn 'embed'\n\t\tcase 'FULL':\n\t\t\treturn 'full'\n\t\tdefault:\n\t\t\tthrow new TypeError('readMode must be PUBLIC, EMBED, or FULL')\n\t}\n}\n\nfunction normalizePrivateKey(privateKey: KeyObject | string | Uint8Array): KeyObject {\n\tconst keyObject =\n\t\tprivateKey instanceof KeyObject\n\t\t\t? privateKey\n\t\t\t: typeof privateKey === 'string'\n\t\t\t\t? createPrivateKey(privateKey)\n\t\t\t\t: createPrivateKey({ key: Buffer.from(privateKey), format: 'der', type: 'pkcs8' })\n\tif (keyObject.type !== 'private' || keyObject.asymmetricKeyType !== 'ed25519') {\n\t\tthrow new TypeError('privateKey must be an Ed25519 private key')\n\t}\n\treturn keyObject\n}\n\nfunction encodeBase64URL(value: string | Uint8Array): string {\n\treturn Buffer.from(value).toString('base64url')\n}\n\nfunction encodeBase64(value: Uint8Array): string {\n\treturn Buffer.from(value).toString('base64')\n}\n\nfunction decodeBase64URL(value: string): Uint8Array {\n\treturn Buffer.from(value, 'base64url')\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carddb/node",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "CardDB client for Node.js, Bun, and Deno",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"url": "https://github.com/carddb/carddb-js.git",
|
|
46
46
|
"directory": "packages/node"
|
|
47
47
|
},
|
|
48
|
-
"homepage": "https://
|
|
48
|
+
"homepage": "https://carddb.dev",
|
|
49
49
|
"bugs": {
|
|
50
50
|
"url": "https://github.com/carddb/carddb-js/issues"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@carddb/client": "0.3.
|
|
54
|
-
"@carddb/core": "0.3.
|
|
53
|
+
"@carddb/client": "0.3.15",
|
|
54
|
+
"@carddb/core": "0.3.15"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=18.0.0"
|