@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.
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/client.d.ts +132 -0
- package/dist/client.js +251 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto.d.ts +92 -0
- package/dist/crypto.js +195 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +20 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/keypair.d.ts +8 -0
- package/dist/keypair.js +44 -0
- package/dist/keypair.js.map +1 -0
- package/dist/types.d.ts +300 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks.d.ts +35 -0
- package/dist/webhooks.js +78 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +54 -0
package/dist/crypto.d.ts
ADDED
|
@@ -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":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,SAAS,GAEV,MAAM,aAAa,CAAC;AAErB,gEAAgE;AAChE,SAAS,aAAa,CAAC,KAAiB;IACtC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,mEAAmE;AACnE,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,qDAAqD;AACrD,SAAS,aAAa;IACpB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAkBD;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAkC,EAAE;IAEpC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE;QAC3D,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,IAAI;QAC5C,cAAc,EAAE,OAAO;QACvB,kBAAkB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;QACpD,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE;KACnD,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,eAAe,CAAC,SAAS,CAAC;SAC1C,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;SACvC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAE9B,OAAO;QACL,KAAK;QACL,aAAa,EAAE,UAAU;QACzB,UAAU,EAAE,gBAAgB,CAAC,UAAU,CAAC;KACzC,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC9C,OAAO,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAiB,EACjB,sBAA8B,EAC9B,WAAmB,EACnB,mBAA2B;IAS3B,MAAM,YAAY,GAAG,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAEvD,qCAAqC;IACrC,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;QAChC,MAAM,CAAC,KAAK,EAAE;KACf,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAEpC,wDAAwD;IACxD,yEAAyE;IACzE,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,YAAY,GAAG,aAAa,CAChC;QACE,GAAG,EAAE,YAAY;QACjB,OAAO,EAAE,SAAS,CAAC,sBAAsB;QACzC,QAAQ,EAAE,QAAQ;KACnB,EACD,MAAM,CACP,CAAC;IACF,4EAA4E;IAC5E,kCAAkC;IAClC,MAAM,gBAAgB,GAAG,aAAa,CACpC;QACE,GAAG,EAAE,SAAS;QACd,OAAO,EAAE,SAAS,CAAC,sBAAsB;QACzC,QAAQ,EAAE,QAAQ;KACnB,EACD,MAAM,CACP,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;QAClD,EAAE,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;QACrC,OAAO,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;QAC/C,YAAY,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;QACzD,gBAAgB,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACjE,KAAK,EAAE,WAAW;KACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,QAMC,EACD,UAAqB;IAErB,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAC3E,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CACrC,CAAC;IACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACtC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,MAAM,GAAG,cAAc,CAC3B;gBACE,GAAG,EAAE,UAAU;gBACf,OAAO,EAAE,SAAS,CAAC,sBAAsB;gBACzC,QAAQ,EAAE,QAAQ,EAAE,oCAAoC;aACzD,EACD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CACvC,CAAC;YACF,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,aAAa,EACb,MAAM,EACN,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CACxC,CAAC;YACF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAClE,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC9B,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7D,QAAQ,CAAC,KAAK,EAAE;aACjB,CAAC,CAAC;YACH,OAAO,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,SAAqB,EACrB,sBAA8B,EAC9B,WAAmB,EACnB,mBAA2B;IAS3B,MAAM,YAAY,GAAG,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAEpC,MAAM,YAAY,GAAG,aAAa,CAChC;QACE,GAAG,EAAE,YAAY;QACjB,OAAO,EAAE,SAAS,CAAC,sBAAsB;QACzC,QAAQ,EAAE,QAAQ;KACnB,EACD,MAAM,CACP,CAAC;IACF,MAAM,gBAAgB,GAAG,aAAa,CACpC;QACE,GAAG,EAAE,SAAS;QACd,OAAO,EAAE,SAAS,CAAC,sBAAsB;QACzC,QAAQ,EAAE,QAAQ;KACnB,EACD,MAAM,CACP,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;QAClD,EAAE,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;QACrC,OAAO,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;QAC/C,YAAY,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;QACzD,gBAAgB,EAAE,aAAa,CAAC,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACjE,KAAK,EAAE,WAAW;KACnB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAMC,EACD,UAAqB;IAErB,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAC3E,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CACrC,CAAC;IACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACtC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,MAAM,GAAG,cAAc,CAC3B;gBACE,GAAG,EAAE,UAAU;gBACf,OAAO,EAAE,SAAS,CAAC,sBAAsB;gBACzC,QAAQ,EAAE,QAAQ;aACnB,EACD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CACvC,CAAC;YACF,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,aAAa,EACb,MAAM,EACN,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CACxC,CAAC;YACF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAClE,OAAO,MAAM,CAAC,MAAM,CAAC;gBACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7D,QAAQ,CAAC,KAAK,EAAE;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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;
|
package/dist/keypair.js
ADDED
|
@@ -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"}
|