@ixblix/sdk-js 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Operator-side end-to-end encryption helpers for ixblix.
3
+ *
4
+ * The operator (desk/CRM) holds a persistent RSA-OAEP keypair. The private key
5
+ * never leaves the operator process; only the public key is registered with the
6
+ * ixblix backend at conversation creation so the customer can encrypt messages to
7
+ * the operator.
8
+ *
9
+ * Outgoing messages are encrypted to the customer's public key; incoming
10
+ * messages are decrypted with the operator's private key.
11
+ *
12
+ * The scheme is hybrid: each message is encrypted with a fresh AES-256-GCM key,
13
+ * and that AES key is wrapped (RSA-OAEP/SHA-256) both to the recipient
14
+ * (`encryptedKey`) and to the sender's own public key (`selfEncryptedKey`) so
15
+ * the sender can read back its own sent messages.
16
+ */
17
+ import { type KeyObject } from "node:crypto";
18
+ /** An RSA keypair plus its identifier, as used by the operator. */
19
+ export interface OperatorKeyPair {
20
+ /** Identifier of the public key, sent in the message envelope. */
21
+ keyId: string;
22
+ /** Base64 SPKI DER of the public key, registered with ixblix. */
23
+ publicKeySpki: string;
24
+ /** The private key object. Never send this to ixblix. */
25
+ privateKey: KeyObject;
26
+ }
27
+ /** Options for generating an operator keypair. */
28
+ export interface GenerateKeyPairOptions {
29
+ /** RSA modulus length in bits. Defaults to 2048. */
30
+ modulusLength?: number;
31
+ }
32
+ /**
33
+ * Generate a fresh RSA keypair for the operator. The private key stays on the
34
+ * operator's side; only `publicKeySpki` should be registered with ixblix.
35
+ */
36
+ export declare function generateOperatorKeyPair(options?: GenerateKeyPairOptions): OperatorKeyPair;
37
+ /** Import a base64 SPKI DER RSA public key (e.g. the customer's key). */
38
+ export declare function importPublicKey(spkiBase64: string): KeyObject;
39
+ /**
40
+ * Encrypt a plaintext message to the recipient's RSA public key.
41
+ *
42
+ * Returns the envelope fields expected by the ixblix backend. The AES key is
43
+ * wrapped both to the recipient (`encryptedKey`) and to the sender's own public
44
+ * key (`selfEncryptedKey`) so the sender can read back its own sent message.
45
+ */
46
+ export declare function encryptToRecipient(plaintext: string, recipientPublicKeySpki: string, senderKeyId: string, senderPublicKeySpki: string): {
47
+ content: string;
48
+ iv: string;
49
+ authTag: string;
50
+ encryptedKey: string;
51
+ selfEncryptedKey: string;
52
+ keyId: string;
53
+ };
54
+ /**
55
+ * Decrypt a message envelope using the operator's private key. Tries the
56
+ * recipient-wrapped key (`encryptedKey`) first, then the sender-wrapped key
57
+ * (`selfEncryptedKey`), so the operator can read back its own sent messages
58
+ * too. Returns null when decryption fails.
59
+ */
60
+ export declare function decryptEnvelope(envelope: {
61
+ content: string;
62
+ iv?: string;
63
+ authTag?: string;
64
+ encryptedKey?: string;
65
+ selfEncryptedKey?: string;
66
+ }, privateKey: KeyObject): string | null;
67
+ /**
68
+ * Encrypt a binary media file (image/audio/document) to the recipient's RSA
69
+ * public key. Uses a fresh AES-256-GCM key wrapped to both the recipient and
70
+ * the sender (for self-read). Returns the envelope fields plus the base64
71
+ * ciphertext of the file bytes.
72
+ */
73
+ export declare function encryptMediaToRecipient(fileBytes: Uint8Array, recipientPublicKeySpki: string, senderKeyId: string, senderPublicKeySpki: string): {
74
+ content: string;
75
+ iv: string;
76
+ authTag: string;
77
+ encryptedKey: string;
78
+ selfEncryptedKey: string;
79
+ keyId: string;
80
+ };
81
+ /**
82
+ * Decrypt a media envelope using the operator's private key. Tries the
83
+ * recipient-wrapped key first, then the sender-wrapped key. Returns the
84
+ * plaintext file bytes, or null when decryption fails.
85
+ */
86
+ export declare function decryptMediaEnvelope(envelope: {
87
+ content: string;
88
+ iv?: string;
89
+ authTag?: string;
90
+ encryptedKey?: string;
91
+ selfEncryptedKey?: string;
92
+ }, privateKey: KeyObject): Uint8Array | null;
package/dist/crypto.js ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Operator-side end-to-end encryption helpers for ixblix.
3
+ *
4
+ * The operator (desk/CRM) holds a persistent RSA-OAEP keypair. The private key
5
+ * never leaves the operator process; only the public key is registered with the
6
+ * ixblix backend at conversation creation so the customer can encrypt messages to
7
+ * the operator.
8
+ *
9
+ * Outgoing messages are encrypted to the customer's public key; incoming
10
+ * messages are decrypted with the operator's private key.
11
+ *
12
+ * The scheme is hybrid: each message is encrypted with a fresh AES-256-GCM key,
13
+ * and that AES key is wrapped (RSA-OAEP/SHA-256) both to the recipient
14
+ * (`encryptedKey`) and to the sender's own public key (`selfEncryptedKey`) so
15
+ * the sender can read back its own sent messages.
16
+ */
17
+ import { generateKeyPairSync, createPublicKey, createPrivateKey, publicEncrypt, privateDecrypt, randomBytes, createCipheriv, createDecipheriv, constants, } from "node:crypto";
18
+ /** Encode a Buffer as a URL-safe base64 string (no padding). */
19
+ function bytesToBase64(bytes) {
20
+ return Buffer.from(bytes).toString("base64url");
21
+ }
22
+ /** Decode a base64 string (URL-safe or standard) into a Buffer. */
23
+ function base64ToBytes(value) {
24
+ return new Uint8Array(Buffer.from(value, "base64"));
25
+ }
26
+ /** Generate a random identifier for a public key. */
27
+ function generateKeyId() {
28
+ return randomBytes(16).toString("base64url");
29
+ }
30
+ /**
31
+ * Generate a fresh RSA keypair for the operator. The private key stays on the
32
+ * operator's side; only `publicKeySpki` should be registered with ixblix.
33
+ */
34
+ export function generateOperatorKeyPair(options = {}) {
35
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
36
+ modulusLength: options.modulusLength ?? 2048,
37
+ publicExponent: 0x10001,
38
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
39
+ publicKeyEncoding: { type: "spki", format: "pem" },
40
+ });
41
+ const publicSpki = createPublicKey(publicKey)
42
+ .export({ type: "spki", format: "der" })
43
+ .toString("base64");
44
+ const keyId = generateKeyId();
45
+ return {
46
+ keyId,
47
+ publicKeySpki: publicSpki,
48
+ privateKey: createPrivateKey(privateKey),
49
+ };
50
+ }
51
+ /** Import a base64 SPKI DER RSA public key (e.g. the customer's key). */
52
+ export function importPublicKey(spkiBase64) {
53
+ const der = Buffer.from(spkiBase64, "base64");
54
+ return createPublicKey({ key: der, type: "spki", format: "der" });
55
+ }
56
+ /**
57
+ * Encrypt a plaintext message to the recipient's RSA public key.
58
+ *
59
+ * Returns the envelope fields expected by the ixblix backend. The AES key is
60
+ * wrapped both to the recipient (`encryptedKey`) and to the sender's own public
61
+ * key (`selfEncryptedKey`) so the sender can read back its own sent message.
62
+ */
63
+ export function encryptToRecipient(plaintext, recipientPublicKeySpki, senderKeyId, senderPublicKeySpki) {
64
+ const recipientKey = importPublicKey(recipientPublicKeySpki);
65
+ const senderKey = importPublicKey(senderPublicKeySpki);
66
+ // Fresh AES-256-GCM key per message.
67
+ const aesKey = randomBytes(32);
68
+ const iv = randomBytes(12);
69
+ const cipher = createCipheriv("aes-256-gcm", aesKey, iv);
70
+ const ciphertext = Buffer.concat([
71
+ cipher.update(plaintext, "utf8"),
72
+ cipher.final(),
73
+ ]);
74
+ const authTag = cipher.getAuthTag();
75
+ // Wrap the AES key with the recipient's RSA public key.
76
+ // `RSA_PKCS1_OAEP_PADDING` + `oaepHash: "sha256"` matches the Web Crypto
77
+ // RSA-OAEP/SHA-256 used by the customer browser. (Note: padding value 1 is
78
+ // PKCS#1 v1.5, NOT OAEP, and is incompatible with Web Crypto.)
79
+ const encryptedKey = publicEncrypt({
80
+ key: recipientKey,
81
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
82
+ oaepHash: "sha256",
83
+ }, aesKey);
84
+ // Also wrap the AES key with the sender's own public key, so the sender can
85
+ // read back its own sent message.
86
+ const selfEncryptedKey = publicEncrypt({
87
+ key: senderKey,
88
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
89
+ oaepHash: "sha256",
90
+ }, aesKey);
91
+ return {
92
+ content: bytesToBase64(new Uint8Array(ciphertext)),
93
+ iv: bytesToBase64(new Uint8Array(iv)),
94
+ authTag: bytesToBase64(new Uint8Array(authTag)),
95
+ encryptedKey: bytesToBase64(new Uint8Array(encryptedKey)),
96
+ selfEncryptedKey: bytesToBase64(new Uint8Array(selfEncryptedKey)),
97
+ keyId: senderKeyId,
98
+ };
99
+ }
100
+ /**
101
+ * Decrypt a message envelope using the operator's private key. Tries the
102
+ * recipient-wrapped key (`encryptedKey`) first, then the sender-wrapped key
103
+ * (`selfEncryptedKey`), so the operator can read back its own sent messages
104
+ * too. Returns null when decryption fails.
105
+ */
106
+ export function decryptEnvelope(envelope, privateKey) {
107
+ const wrappedKeys = [envelope.encryptedKey, envelope.selfEncryptedKey].filter((key) => Boolean(key));
108
+ for (const wrappedKey of wrappedKeys) {
109
+ try {
110
+ if (!envelope.iv || !envelope.authTag) {
111
+ return null;
112
+ }
113
+ const aesKey = privateDecrypt({
114
+ key: privateKey,
115
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
116
+ oaepHash: "sha256", // match Web Crypto RSA-OAEP/SHA-256
117
+ }, Buffer.from(base64ToBytes(wrappedKey)));
118
+ const decipher = createDecipheriv("aes-256-gcm", aesKey, Buffer.from(base64ToBytes(envelope.iv)));
119
+ decipher.setAuthTag(Buffer.from(base64ToBytes(envelope.authTag)));
120
+ const plaintext = Buffer.concat([
121
+ decipher.update(Buffer.from(base64ToBytes(envelope.content))),
122
+ decipher.final(),
123
+ ]);
124
+ return plaintext.toString("utf8");
125
+ }
126
+ catch {
127
+ // Try the next wrapped key.
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Encrypt a binary media file (image/audio/document) to the recipient's RSA
134
+ * public key. Uses a fresh AES-256-GCM key wrapped to both the recipient and
135
+ * the sender (for self-read). Returns the envelope fields plus the base64
136
+ * ciphertext of the file bytes.
137
+ */
138
+ export function encryptMediaToRecipient(fileBytes, recipientPublicKeySpki, senderKeyId, senderPublicKeySpki) {
139
+ const recipientKey = importPublicKey(recipientPublicKeySpki);
140
+ const senderKey = importPublicKey(senderPublicKeySpki);
141
+ const aesKey = randomBytes(32);
142
+ const iv = randomBytes(12);
143
+ const cipher = createCipheriv("aes-256-gcm", aesKey, iv);
144
+ const ciphertext = Buffer.concat([cipher.update(fileBytes), cipher.final()]);
145
+ const authTag = cipher.getAuthTag();
146
+ const encryptedKey = publicEncrypt({
147
+ key: recipientKey,
148
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
149
+ oaepHash: "sha256",
150
+ }, aesKey);
151
+ const selfEncryptedKey = publicEncrypt({
152
+ key: senderKey,
153
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
154
+ oaepHash: "sha256",
155
+ }, aesKey);
156
+ return {
157
+ content: bytesToBase64(new Uint8Array(ciphertext)),
158
+ iv: bytesToBase64(new Uint8Array(iv)),
159
+ authTag: bytesToBase64(new Uint8Array(authTag)),
160
+ encryptedKey: bytesToBase64(new Uint8Array(encryptedKey)),
161
+ selfEncryptedKey: bytesToBase64(new Uint8Array(selfEncryptedKey)),
162
+ keyId: senderKeyId,
163
+ };
164
+ }
165
+ /**
166
+ * Decrypt a media envelope using the operator's private key. Tries the
167
+ * recipient-wrapped key first, then the sender-wrapped key. Returns the
168
+ * plaintext file bytes, or null when decryption fails.
169
+ */
170
+ export function decryptMediaEnvelope(envelope, privateKey) {
171
+ const wrappedKeys = [envelope.encryptedKey, envelope.selfEncryptedKey].filter((key) => Boolean(key));
172
+ for (const wrappedKey of wrappedKeys) {
173
+ try {
174
+ if (!envelope.iv || !envelope.authTag) {
175
+ return null;
176
+ }
177
+ const aesKey = privateDecrypt({
178
+ key: privateKey,
179
+ padding: constants.RSA_PKCS1_OAEP_PADDING,
180
+ oaepHash: "sha256",
181
+ }, Buffer.from(base64ToBytes(wrappedKey)));
182
+ const decipher = createDecipheriv("aes-256-gcm", aesKey, Buffer.from(base64ToBytes(envelope.iv)));
183
+ decipher.setAuthTag(Buffer.from(base64ToBytes(envelope.authTag)));
184
+ return Buffer.concat([
185
+ decipher.update(Buffer.from(base64ToBytes(envelope.content))),
186
+ decipher.final(),
187
+ ]);
188
+ }
189
+ catch {
190
+ // Try the next wrapped key.
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { IxblixErrorBody } from "./types.js";
2
+ /**
3
+ * Error thrown by the ixblix SDK when the API returns a non-2xx response or when
4
+ * a request cannot be completed.
5
+ */
6
+ export declare class IxblixError extends Error {
7
+ /** HTTP status code returned by the API (0 when the request never reached it). */
8
+ readonly status: number;
9
+ /** Machine-readable ixblix error code (e.g. `UNAUTHORIZED`, `NOT_FOUND`). */
10
+ readonly code: string;
11
+ /** Optional validation error details. */
12
+ readonly details?: Array<{
13
+ path: string;
14
+ message: string;
15
+ }>;
16
+ constructor(message: string, options?: {
17
+ status?: number;
18
+ code?: string;
19
+ details?: IxblixErrorBody["errors"];
20
+ });
21
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Error thrown by the ixblix SDK when the API returns a non-2xx response or when
3
+ * a request cannot be completed.
4
+ */
5
+ export class IxblixError extends Error {
6
+ /** HTTP status code returned by the API (0 when the request never reached it). */
7
+ status;
8
+ /** Machine-readable ixblix error code (e.g. `UNAUTHORIZED`, `NOT_FOUND`). */
9
+ code;
10
+ /** Optional validation error details. */
11
+ details;
12
+ constructor(message, options = {}) {
13
+ super(message);
14
+ this.name = "IxblixError";
15
+ this.status = options.status ?? 0;
16
+ this.code = options.code ?? "UNKNOWN";
17
+ this.details = options.details;
18
+ }
19
+ }
20
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,kFAAkF;IACzE,MAAM,CAAS;IACxB,6EAA6E;IACpE,IAAI,CAAS;IACtB,yCAAyC;IAChC,OAAO,CAA4C;IAE5D,YACE,OAAe,EACf,UAAmF,EAAE;QAErF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;QACtC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ixblix SDK — official client for desk/CRM integrations.
3
+ *
4
+ * Create overflow conversations, exchange end-to-end encrypted messages and
5
+ * media, and manage company onboarding.
6
+ *
7
+ * ```ts
8
+ * import { IxblixClient, generateOperatorKeyPair } from "ixblix-sdk-js";
9
+ *
10
+ * const ixblix = new IxblixClient({
11
+ * baseUrl: "https://api.ixblix.app",
12
+ * apiKey: process.env.IXBLIX_API_KEY,
13
+ * });
14
+ * ```
15
+ */
16
+ export { IxblixClient } from "./client.js";
17
+ export type { IxblixClientOptions, MediaUpload, MediaDownload, } from "./client.js";
18
+ export { IxblixError } from "./errors.js";
19
+ export { generateOperatorKeyPair, importPublicKey, encryptToRecipient, decryptEnvelope, encryptMediaToRecipient, decryptMediaEnvelope, } from "./crypto.js";
20
+ export type { OperatorKeyPair, GenerateKeyPairOptions } from "./crypto.js";
21
+ export { loadOrCreateOperatorKey } from "./keypair.js";
22
+ export { parseWebhook, verifyWebhook } from "./webhooks.js";
23
+ export type { ParsedWebhook } from "./webhooks.js";
24
+ export { WEBHOOK_SIGNATURE_HEADER, WEBHOOK_ID_HEADER, WEBHOOK_EVENT_HEADER, } from "./webhooks.js";
25
+ export type { SenderType, ConversationKeyStatus, ConversationStatus, ConsentStatus, Contact, ContactInput, Conversation, CompanyCustomization, CompanySummary, CreateConversationResult, ConversationKeys, RegisterCustomerKeyResult, EraseConversationResult, MessageEnvelope, Message, Media, Plan, RegisterCompanyInput, PaymentInstructions, RegisterCompanyResult, ActivateCompanyResult, CompanyBalance, CreditPurchase, PurchaseCreditsResult, PaymentProvidersResult, OriginalChannelMessageInput, WebhookEvent, CompanyActivatedEvent, MessageReceivedEvent, MessageReadEvent, CustomerJoinedEvent, ConversationClosedEvent, PresenceEvent, BalanceLowEvent, IxblixErrorBody, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ixblix SDK — official client for desk/CRM integrations.
3
+ *
4
+ * Create overflow conversations, exchange end-to-end encrypted messages and
5
+ * media, and manage company onboarding.
6
+ *
7
+ * ```ts
8
+ * import { IxblixClient, generateOperatorKeyPair } from "ixblix-sdk-js";
9
+ *
10
+ * const ixblix = new IxblixClient({
11
+ * baseUrl: "https://api.ixblix.app",
12
+ * apiKey: process.env.IXBLIX_API_KEY,
13
+ * });
14
+ * ```
15
+ */
16
+ export { IxblixClient } from "./client.js";
17
+ export { IxblixError } from "./errors.js";
18
+ export { generateOperatorKeyPair, importPublicKey, encryptToRecipient, decryptEnvelope, encryptMediaToRecipient, decryptMediaEnvelope, } from "./crypto.js";
19
+ export { loadOrCreateOperatorKey } from "./keypair.js";
20
+ export { parseWebhook, verifyWebhook } from "./webhooks.js";
21
+ export { WEBHOOK_SIGNATURE_HEADER, WEBHOOK_ID_HEADER, WEBHOOK_EVENT_HEADER, } from "./webhooks.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type OperatorKeyPair } from "./crypto.js";
2
+ /**
3
+ * Load the operator keypair from disk, generating and persisting it if absent.
4
+ *
5
+ * @param directory Directory where the keypair files are stored.
6
+ * @returns The loaded (or freshly generated) keypair.
7
+ */
8
+ export declare function loadOrCreateOperatorKey(directory: string): OperatorKeyPair;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Persistence helpers for the operator RSA keypair.
3
+ *
4
+ * The operator's private key must be stored securely and never sent to ixblix.
5
+ * These helpers load-or-create a keypair on disk (private key written with mode
6
+ * `0600`). Integrations that use a secret manager or KMS should instead manage
7
+ * the keypair themselves and pass the resulting `OperatorKeyPair` to the SDK.
8
+ */
9
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, } from "node:fs";
10
+ import path from "node:path";
11
+ import { createPrivateKey } from "node:crypto";
12
+ import { generateOperatorKeyPair, } from "./crypto.js";
13
+ /**
14
+ * Load the operator keypair from disk, generating and persisting it if absent.
15
+ *
16
+ * @param directory Directory where the keypair files are stored.
17
+ * @returns The loaded (or freshly generated) keypair.
18
+ */
19
+ export function loadOrCreateOperatorKey(directory) {
20
+ const privateKeyFile = path.join(directory, "operator-private.pem");
21
+ const publicKeyFile = path.join(directory, "operator-public.spki");
22
+ const keyIdFile = path.join(directory, "operator-key-id");
23
+ if (existsSync(privateKeyFile) &&
24
+ existsSync(publicKeyFile) &&
25
+ existsSync(keyIdFile)) {
26
+ const privatePem = readFileSync(privateKeyFile, "utf8");
27
+ const publicSpki = readFileSync(publicKeyFile, "utf8");
28
+ const keyId = readFileSync(keyIdFile, "utf8");
29
+ return {
30
+ keyId,
31
+ publicKeySpki: publicSpki,
32
+ privateKey: createPrivateKey(privatePem),
33
+ };
34
+ }
35
+ const keypair = generateOperatorKeyPair();
36
+ mkdirSync(directory, { recursive: true });
37
+ writeFileSync(privateKeyFile, keypair.privateKey.export({ type: "pkcs8", format: "pem" }), {
38
+ mode: 0o600,
39
+ });
40
+ writeFileSync(publicKeyFile, keypair.publicKeySpki);
41
+ writeFileSync(keyIdFile, keypair.keyId);
42
+ return keypair;
43
+ }
44
+ //# sourceMappingURL=keypair.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keypair.js","sourceRoot":"","sources":["../src/keypair.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,uBAAuB,GAExB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACvD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAE1D,IACE,UAAU,CAAC,cAAc,CAAC;QAC1B,UAAU,CAAC,aAAa,CAAC;QACzB,UAAU,CAAC,SAAS,CAAC,EACrB,CAAC;QACD,MAAM,UAAU,GAAG,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO;YACL,KAAK;YACL,aAAa,EAAE,UAAU;YACzB,UAAU,EAAE,gBAAgB,CAAC,UAAU,CAAC;SACzC,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,uBAAuB,EAAE,CAAC;IAE1C,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,aAAa,CAAC,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE;QACzF,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,aAAa,CAAC,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACpD,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAExC,OAAO,OAAO,CAAC;AACjB,CAAC"}