@xdevplatform/chat-xdk 0.1.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/README.md +102 -0
- package/index.d.ts +558 -0
- package/index.js +394 -0
- package/package.json +43 -0
- package/pkg/chat_xdk_wasm.d.ts +315 -0
- package/pkg/chat_xdk_wasm.js +1437 -0
- package/pkg/chat_xdk_wasm_bg.wasm +0 -0
- package/pkg/chat_xdk_wasm_bg.wasm.d.ts +50 -0
package/index.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X Chat SDK - JavaScript Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Juicebox key-storage lifecycle is handled entirely in this JS layer.
|
|
5
|
+
* The Rust WASM `Chat` is a pure crypto engine that never touches
|
|
6
|
+
* globalThis or module-level singletons.
|
|
7
|
+
*
|
|
8
|
+
* Each `ChatWithJuicebox` instance holds its **own** Juicebox client, so
|
|
9
|
+
* multiple users / configs can coexist safely in the same runtime.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function isNodeRuntime() {
|
|
13
|
+
return (
|
|
14
|
+
typeof process !== "undefined" &&
|
|
15
|
+
Boolean(process.versions && process.versions.node)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const REGISTER_REASON_BY_VALUE = {
|
|
20
|
+
0: "InvalidAuth",
|
|
21
|
+
1: "UpgradeRequired",
|
|
22
|
+
2: "RateLimitExceeded",
|
|
23
|
+
3: "Assertion",
|
|
24
|
+
4: "Transient",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const RECOVER_REASON_BY_VALUE = {
|
|
28
|
+
0: "InvalidPin",
|
|
29
|
+
1: "NotRegistered",
|
|
30
|
+
2: "InvalidAuth",
|
|
31
|
+
3: "UpgradeRequired",
|
|
32
|
+
4: "RateLimitExceeded",
|
|
33
|
+
5: "Assertion",
|
|
34
|
+
6: "Transient",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const MIN_PIN_LENGTH = 4;
|
|
38
|
+
|
|
39
|
+
function toPinBytes(pin) {
|
|
40
|
+
return typeof pin === 'string' ? new TextEncoder().encode(pin) : pin;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate PIN strength for new registrations: at least MIN_PIN_LENGTH
|
|
45
|
+
* characters, not a single repeated character, not an ascending or
|
|
46
|
+
* descending run of digits. Never applied on unlock.
|
|
47
|
+
*/
|
|
48
|
+
function validatePinStrength(pinBytes) {
|
|
49
|
+
if (pinBytes.length < MIN_PIN_LENGTH) {
|
|
50
|
+
throw new Error(`Weak PIN: must be at least ${MIN_PIN_LENGTH} characters`);
|
|
51
|
+
}
|
|
52
|
+
if (pinBytes.every((b) => b === pinBytes[0])) {
|
|
53
|
+
throw new Error('Weak PIN: must not be a single repeated character');
|
|
54
|
+
}
|
|
55
|
+
const allDigits = pinBytes.every((b) => b >= 0x30 && b <= 0x39);
|
|
56
|
+
let ascending = true;
|
|
57
|
+
let descending = true;
|
|
58
|
+
for (let i = 1; i < pinBytes.length; i++) {
|
|
59
|
+
if (pinBytes[i] !== pinBytes[i - 1] + 1) ascending = false;
|
|
60
|
+
if (pinBytes[i] !== pinBytes[i - 1] - 1) descending = false;
|
|
61
|
+
}
|
|
62
|
+
if (allDigits && (ascending || descending)) {
|
|
63
|
+
throw new Error('Weak PIN: must not be a sequential run of digits');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatJuiceboxError(err, reasonMap) {
|
|
68
|
+
if (!err) {
|
|
69
|
+
return "Unknown Juicebox error";
|
|
70
|
+
}
|
|
71
|
+
const reasonValue = err.reason ?? err.reasonCode ?? err.code;
|
|
72
|
+
const reason =
|
|
73
|
+
typeof reasonValue === "number" ? reasonMap[reasonValue] : reasonValue;
|
|
74
|
+
const guessesRemaining =
|
|
75
|
+
err.guesses_remaining ?? err.guessesRemaining ?? err.guesses;
|
|
76
|
+
const parts = [];
|
|
77
|
+
if (reason) {
|
|
78
|
+
parts.push(`reason=${reason}`);
|
|
79
|
+
}
|
|
80
|
+
if (guessesRemaining !== undefined) {
|
|
81
|
+
parts.push(`guesses_remaining=${guessesRemaining}`);
|
|
82
|
+
}
|
|
83
|
+
if (!parts.length) {
|
|
84
|
+
return `Unknown Juicebox error: ${String(err)}`;
|
|
85
|
+
}
|
|
86
|
+
return parts.join(" ");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function initWasmModule() {
|
|
90
|
+
let wasmModule;
|
|
91
|
+
let init;
|
|
92
|
+
if (isNodeRuntime()) {
|
|
93
|
+
const { createRequire } = await import("module");
|
|
94
|
+
const require = createRequire(import.meta.url);
|
|
95
|
+
wasmModule = require("./pkg/chat_xdk_wasm.js");
|
|
96
|
+
init =
|
|
97
|
+
wasmModule?.default ||
|
|
98
|
+
wasmModule?.init ||
|
|
99
|
+
wasmModule?.__wbindgen_init;
|
|
100
|
+
if (typeof init !== "function") {
|
|
101
|
+
throw new Error("WASM init function not found in chat_xdk_wasm module.");
|
|
102
|
+
}
|
|
103
|
+
const fs = await import("fs");
|
|
104
|
+
const wasmUrl = new URL("./pkg/chat_xdk_wasm_bg.wasm", import.meta.url);
|
|
105
|
+
let wasmBytes;
|
|
106
|
+
try {
|
|
107
|
+
wasmBytes = fs.readFileSync(wasmUrl);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const { fileURLToPath } = await import("url");
|
|
110
|
+
wasmBytes = fs.readFileSync(fileURLToPath(wasmUrl));
|
|
111
|
+
}
|
|
112
|
+
await init({ module_or_path: wasmBytes });
|
|
113
|
+
} else {
|
|
114
|
+
wasmModule = await import("./pkg/chat_xdk_wasm.js");
|
|
115
|
+
init =
|
|
116
|
+
wasmModule?.default ||
|
|
117
|
+
wasmModule?.init ||
|
|
118
|
+
wasmModule?.__wbindgen_init;
|
|
119
|
+
if (typeof init !== "function") {
|
|
120
|
+
throw new Error("WASM init function not found in chat_xdk_wasm module.");
|
|
121
|
+
}
|
|
122
|
+
await init();
|
|
123
|
+
}
|
|
124
|
+
return wasmModule;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function loadJuiceboxSdk() {
|
|
128
|
+
if (isNodeRuntime()) {
|
|
129
|
+
const juiceboxBg = await import("juicebox-sdk/juicebox-sdk_bg.js");
|
|
130
|
+
const { createRequire } = await import("module");
|
|
131
|
+
const require = createRequire(import.meta.url);
|
|
132
|
+
const fs = await import("fs");
|
|
133
|
+
const wasmPath = require.resolve("juicebox-sdk/juicebox-sdk_bg.wasm");
|
|
134
|
+
const wasmBytes = fs.readFileSync(wasmPath);
|
|
135
|
+
const { instance } = await WebAssembly.instantiate(wasmBytes, {
|
|
136
|
+
"./juicebox-sdk_bg.js": juiceboxBg,
|
|
137
|
+
});
|
|
138
|
+
juiceboxBg.__wbg_set_wasm(instance.exports);
|
|
139
|
+
return juiceboxBg;
|
|
140
|
+
}
|
|
141
|
+
return await import("juicebox-sdk");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ChatWithJuicebox — JS wrapper that owns both a WasmChat and a Juicebox client
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* JS wrapper that combines the Rust WASM crypto engine with per-instance
|
|
148
|
+
* Juicebox key storage. Every crypto method is forwarded transparently;
|
|
149
|
+
* setup/unlock/delete/changePin/updateConfig are handled in pure JS.
|
|
150
|
+
*/
|
|
151
|
+
export class ChatWithJuicebox {
|
|
152
|
+
/** @type {import('./pkg/chat_xdk_wasm.js').Chat} */
|
|
153
|
+
#inner;
|
|
154
|
+
#juiceboxClient;
|
|
155
|
+
#numGuesses;
|
|
156
|
+
#JuiceboxConfiguration;
|
|
157
|
+
#JuiceboxClient;
|
|
158
|
+
|
|
159
|
+
constructor(wasmChat, juiceboxClient, numGuesses, JuiceboxConfiguration, JuiceboxClientCtor) {
|
|
160
|
+
this.#inner = wasmChat;
|
|
161
|
+
this.#juiceboxClient = juiceboxClient;
|
|
162
|
+
this.#numGuesses = numGuesses;
|
|
163
|
+
this.#JuiceboxConfiguration = JuiceboxConfiguration;
|
|
164
|
+
this.#JuiceboxClient = JuiceboxClientCtor;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Juicebox lifecycle (JS-only — no globalThis, no singletons)
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Register existing keys with Juicebox. Call generateKeypairs() first.
|
|
171
|
+
* The PIN must meet minimum strength requirements (4+ characters, not a
|
|
172
|
+
* single repeated character or a sequential digit run). Accepts a string
|
|
173
|
+
* or a Uint8Array; pass a Uint8Array if you want to zero it afterwards.
|
|
174
|
+
*/
|
|
175
|
+
async setup(pin) {
|
|
176
|
+
const pinBytes = toPinBytes(pin);
|
|
177
|
+
const ownsPin = typeof pin === 'string';
|
|
178
|
+
validatePinStrength(pinBytes);
|
|
179
|
+
const secretBytes = this.#inner.exportKeys();
|
|
180
|
+
try {
|
|
181
|
+
await this.#juiceboxClient.register(
|
|
182
|
+
pinBytes,
|
|
183
|
+
secretBytes,
|
|
184
|
+
new Uint8Array(0),
|
|
185
|
+
this.#numGuesses,
|
|
186
|
+
);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
throw new Error(`Juicebox register failed: ${formatJuiceboxError(err, REGISTER_REASON_BY_VALUE)}`);
|
|
189
|
+
} finally {
|
|
190
|
+
secretBytes.fill(0);
|
|
191
|
+
if (ownsPin) pinBytes.fill(0);
|
|
192
|
+
}
|
|
193
|
+
return this.#inner.getPublicKeys();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Unlock: Recover keys from Juicebox using PIN (string or Uint8Array).
|
|
198
|
+
* No strength validation — existing registrations must stay recoverable.
|
|
199
|
+
*/
|
|
200
|
+
async unlock(pin) {
|
|
201
|
+
const pinBytes = toPinBytes(pin);
|
|
202
|
+
const ownsPin = typeof pin === 'string';
|
|
203
|
+
let secretBytes;
|
|
204
|
+
try {
|
|
205
|
+
secretBytes = await this.#juiceboxClient.recover(pinBytes, new Uint8Array(0));
|
|
206
|
+
} catch (err) {
|
|
207
|
+
throw new Error(`Juicebox recovery failed: ${formatJuiceboxError(err, RECOVER_REASON_BY_VALUE)}`);
|
|
208
|
+
} finally {
|
|
209
|
+
if (ownsPin) pinBytes.fill(0);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.#inner.importKeys(secretBytes);
|
|
213
|
+
} finally {
|
|
214
|
+
secretBytes.fill(0);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Delete keys from Juicebox. Warning: Irreversible. */
|
|
219
|
+
async delete() {
|
|
220
|
+
await this.#juiceboxClient.delete();
|
|
221
|
+
this.#inner.lock();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Re-register keys with a new PIN. The new PIN must meet strength requirements. */
|
|
225
|
+
async changePin(oldPin, newPin) {
|
|
226
|
+
const newPinBytes = toPinBytes(newPin);
|
|
227
|
+
const ownsNewPin = typeof newPin === 'string';
|
|
228
|
+
validatePinStrength(newPinBytes);
|
|
229
|
+
await this.unlock(oldPin);
|
|
230
|
+
const secretBytes = this.#inner.exportKeys();
|
|
231
|
+
try {
|
|
232
|
+
await this.#juiceboxClient.register(
|
|
233
|
+
newPinBytes,
|
|
234
|
+
secretBytes,
|
|
235
|
+
new Uint8Array(0),
|
|
236
|
+
this.#numGuesses,
|
|
237
|
+
);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
throw new Error(`Juicebox re-registration failed: ${formatJuiceboxError(err, REGISTER_REASON_BY_VALUE)}`);
|
|
240
|
+
} finally {
|
|
241
|
+
secretBytes.fill(0);
|
|
242
|
+
if (ownsNewPin) newPinBytes.fill(0);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Update Juicebox config (e.g. to refresh auth tokens). Re-creates the client. */
|
|
247
|
+
updateConfig(juiceboxConfig) {
|
|
248
|
+
const configValue = JSON.parse(juiceboxConfig);
|
|
249
|
+
const config = new this.#JuiceboxConfiguration(configValue);
|
|
250
|
+
this.#juiceboxClient = new this.#JuiceboxClient(config, []);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Forwarded crypto methods — delegate to inner WasmChat
|
|
254
|
+
|
|
255
|
+
setRejectUnverified(reject) { return this.#inner.setRejectUnverified(reject); }
|
|
256
|
+
generateKeypairs() { return this.#inner.generateKeypairs(); }
|
|
257
|
+
setKeyVersion(version) { return this.#inner.setKeyVersion(version); }
|
|
258
|
+
getPublicKeys() { return this.#inner.getPublicKeys(); }
|
|
259
|
+
getPublicKeyFingerprint() { return this.#inner.getPublicKeyFingerprint(); }
|
|
260
|
+
isUnlocked() { return this.#inner.isUnlocked(); }
|
|
261
|
+
hasIdentityKey() { return this.#inner.hasIdentityKey(); }
|
|
262
|
+
lock() { return this.#inner.lock(); }
|
|
263
|
+
decryptConversationKey(b64) { return this.#inner.decryptConversationKey(b64); }
|
|
264
|
+
generateConversationKey() { return this.#inner.generateConversationKey(); }
|
|
265
|
+
extractConversationKeys(events) { return this.#inner.extractConversationKeys(events); }
|
|
266
|
+
decryptEvent(eventB64, conversationKeys, signingKeys) {
|
|
267
|
+
return this.#inner.decryptEvent(eventB64, conversationKeys, signingKeys ?? []);
|
|
268
|
+
}
|
|
269
|
+
decryptEvents(events, signingKeys) {
|
|
270
|
+
return this.#inner.decryptEvents(events, signingKeys ?? []);
|
|
271
|
+
}
|
|
272
|
+
sign(data) { return this.#inner.sign(data); }
|
|
273
|
+
verify(publicKeyB64, signature, data) { return this.#inner.verify(publicKeyB64, signature, data); }
|
|
274
|
+
verifyKeyBinding(identityPublicKeyB64, signingPublicKeyB64, identityPublicKeySignatureB64) {
|
|
275
|
+
return this.#inner.verifyKeyBinding(identityPublicKeyB64, signingPublicKeyB64, identityPublicKeySignatureB64);
|
|
276
|
+
}
|
|
277
|
+
encryptMessage(...args) { return this.#inner.encryptMessage(...args); }
|
|
278
|
+
encryptReply(...args) { return this.#inner.encryptReply(...args); }
|
|
279
|
+
encryptAddReaction(...args) { return this.#inner.encryptAddReaction(...args); }
|
|
280
|
+
encryptRemoveReaction(...args) { return this.#inner.encryptRemoveReaction(...args); }
|
|
281
|
+
encryptStream(plaintext, key) { return this.#inner.encryptStream(plaintext, key); }
|
|
282
|
+
decryptStream(encrypted, key) { return this.#inner.decryptStream(encrypted, key); }
|
|
283
|
+
encrypt(plaintext, key) { return this.#inner.encrypt(plaintext, key); }
|
|
284
|
+
decrypt(ciphertext, key) { return this.#inner.decrypt(ciphertext, key); }
|
|
285
|
+
encryptConversationKeyForRecipients(key, recipients) {
|
|
286
|
+
return this.#inner.encryptConversationKeyForRecipients(key, recipients);
|
|
287
|
+
}
|
|
288
|
+
prepareConversationKeys(publicKeys) {
|
|
289
|
+
return this.#inner.prepareConversationKeys(publicKeys);
|
|
290
|
+
}
|
|
291
|
+
signAddMembers(...args) { return this.#inner.signAddMembers(...args); }
|
|
292
|
+
signKeyChange(...args) { return this.#inner.signKeyChange(...args); }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create a Chat instance with integrated Juicebox support.
|
|
297
|
+
*
|
|
298
|
+
* @param {Object} options - Configuration options
|
|
299
|
+
* @param {string} options.juiceboxConfig - Juicebox configuration JSON from X API
|
|
300
|
+
* @param {Function} options.getAuthToken - Async function to get auth token for a realm
|
|
301
|
+
* Signature: (realmId: string) => Promise<string>
|
|
302
|
+
* @returns {Promise<Chat>} Initialized Chat instance
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* const chat = await createChat({
|
|
306
|
+
* juiceboxConfig: configFromXApi,
|
|
307
|
+
* getAuthToken: async (realmId) => {
|
|
308
|
+
* const response = await fetch('/api/juicebox/token?realm=' + realmId);
|
|
309
|
+
* return response.text();
|
|
310
|
+
* },
|
|
311
|
+
* });
|
|
312
|
+
*
|
|
313
|
+
* // First time setup
|
|
314
|
+
* const payload = chat.generateKeypairs();
|
|
315
|
+
* // POST payload to X API, then call setup once you have juiceboxConfig
|
|
316
|
+
* await chat.setup("2580");
|
|
317
|
+
*
|
|
318
|
+
* // Subsequent sessions
|
|
319
|
+
* await chat.unlock("2580");
|
|
320
|
+
*/
|
|
321
|
+
export async function createChat(options) {
|
|
322
|
+
const { juiceboxConfig, getAuthToken, maxGuessCount } = options;
|
|
323
|
+
|
|
324
|
+
if (!juiceboxConfig) {
|
|
325
|
+
throw new Error('juiceboxConfig is required');
|
|
326
|
+
}
|
|
327
|
+
if (!getAuthToken || typeof getAuthToken !== 'function') {
|
|
328
|
+
throw new Error('getAuthToken must be an async function');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Load Juicebox WASM SDK
|
|
332
|
+
let JuiceboxClientCtor;
|
|
333
|
+
let JuiceboxConfiguration;
|
|
334
|
+
try {
|
|
335
|
+
const juiceboxModule = await loadJuiceboxSdk();
|
|
336
|
+
JuiceboxClientCtor = juiceboxModule.Client;
|
|
337
|
+
JuiceboxConfiguration = juiceboxModule.Configuration;
|
|
338
|
+
} catch (e) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
'Failed to load juicebox-sdk. ' +
|
|
341
|
+
'Build it from https://github.com/juicebox-systems/juicebox-sdk:\n' +
|
|
342
|
+
' cd juicebox-sdk/rust/sdk/bridge/wasm && wasm-pack build --target nodejs --out-dir pkg --out-name juicebox-sdk\n' +
|
|
343
|
+
' npm install /path/to/juicebox-sdk/rust/sdk/bridge/wasm/pkg\n' +
|
|
344
|
+
'Original error: ' + e.message
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (!JuiceboxConfiguration) {
|
|
348
|
+
throw new Error('juicebox-sdk Configuration export is missing');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// The juicebox-sdk WASM resolves `JuiceboxGetAuthToken` as a bare-name
|
|
352
|
+
// global on every register/recover/delete call (not just construction).
|
|
353
|
+
// We must keep it set for the lifetime of the client.
|
|
354
|
+
globalThis.JuiceboxGetAuthToken = async (realmId) => {
|
|
355
|
+
const realmIdHex =
|
|
356
|
+
typeof realmId === "string"
|
|
357
|
+
? realmId
|
|
358
|
+
: Array.from(realmId)
|
|
359
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
360
|
+
.join("");
|
|
361
|
+
return await getAuthToken(realmIdHex);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const configValue = JSON.parse(juiceboxConfig);
|
|
365
|
+
const config = new JuiceboxConfiguration(configValue);
|
|
366
|
+
const juiceboxClient = new JuiceboxClientCtor(config, []);
|
|
367
|
+
|
|
368
|
+
const numGuesses =
|
|
369
|
+
Number.isFinite(maxGuessCount) && Number(maxGuessCount) > 0
|
|
370
|
+
? Math.floor(Number(maxGuessCount))
|
|
371
|
+
: 5;
|
|
372
|
+
|
|
373
|
+
// Load Rust WASM crypto engine
|
|
374
|
+
const wasmModule = await initWasmModule();
|
|
375
|
+
const wasmChat = new wasmModule.Chat();
|
|
376
|
+
|
|
377
|
+
return new ChatWithJuicebox(
|
|
378
|
+
wasmChat,
|
|
379
|
+
juiceboxClient,
|
|
380
|
+
numGuesses,
|
|
381
|
+
JuiceboxConfiguration,
|
|
382
|
+
JuiceboxClientCtor,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Re-export utility functions.
|
|
387
|
+
export {
|
|
388
|
+
bytesToBase64,
|
|
389
|
+
base64ToBytes,
|
|
390
|
+
bytesToHex,
|
|
391
|
+
hexToBytes,
|
|
392
|
+
detectMimeType,
|
|
393
|
+
detectImageDimensions,
|
|
394
|
+
} from './pkg/chat_xdk_wasm.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xdevplatform/chat-xdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "X Chat SDK - End-to-end encryption for X Direct Messages",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"index.d.ts",
|
|
14
|
+
"pkg/**"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"juicebox-sdk": "^0.3.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependenciesMeta": {
|
|
24
|
+
"juicebox-sdk": {
|
|
25
|
+
"optional": true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"x",
|
|
30
|
+
"twitter",
|
|
31
|
+
"chat",
|
|
32
|
+
"encryption",
|
|
33
|
+
"e2ee",
|
|
34
|
+
"end-to-end-encryption",
|
|
35
|
+
"dm",
|
|
36
|
+
"direct-messages"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/xdevplatform/chat-xdk"
|
|
42
|
+
}
|
|
43
|
+
}
|