api-ape 3.0.2 → 4.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 +59 -572
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -203
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +31 -6
- package/server/README.md +272 -67
- package/server/adapters/README.md +23 -14
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +322 -71
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -219
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -224
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -311
- package/server/lib/broadcast.js +0 -146
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Wrapped key CRUD operations for IndexedDB
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { STORE_KEYS } = require('./constants');
|
|
7
|
+
const { openDatabase } = require('./db');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Save a wrapped L_key
|
|
11
|
+
* @param {string} userId - User identifier
|
|
12
|
+
* @param {string} keyId - Key identifier
|
|
13
|
+
* @param {Uint8Array|string} wrappedKey - Wrapped key data
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
async function saveWrappedKey(userId, keyId, wrappedKey) {
|
|
17
|
+
if (!userId || typeof userId !== 'string') {
|
|
18
|
+
throw new Error('userId is required and must be a string');
|
|
19
|
+
}
|
|
20
|
+
if (!keyId || typeof keyId !== 'string') {
|
|
21
|
+
throw new Error('keyId is required and must be a string');
|
|
22
|
+
}
|
|
23
|
+
if (!wrappedKey) {
|
|
24
|
+
throw new Error('wrappedKey is required');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const keyData = wrappedKey instanceof Uint8Array
|
|
28
|
+
? btoa(String.fromCharCode(...wrappedKey))
|
|
29
|
+
: wrappedKey;
|
|
30
|
+
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const db = await openDatabase();
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const tx = db.transaction(STORE_KEYS, 'readwrite');
|
|
36
|
+
const store = tx.objectStore(STORE_KEYS);
|
|
37
|
+
const index = store.index('keyId');
|
|
38
|
+
|
|
39
|
+
const getRequest = index.get(keyId);
|
|
40
|
+
|
|
41
|
+
getRequest.onsuccess = () => {
|
|
42
|
+
const existing = getRequest.result;
|
|
43
|
+
|
|
44
|
+
if (existing) {
|
|
45
|
+
existing.wrappedKey = keyData;
|
|
46
|
+
existing.userId = userId;
|
|
47
|
+
store.put(existing);
|
|
48
|
+
} else {
|
|
49
|
+
store.add({
|
|
50
|
+
userId,
|
|
51
|
+
keyId,
|
|
52
|
+
wrappedKey: keyData,
|
|
53
|
+
createdAt: now
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
getRequest.onerror = () => reject(getRequest.error);
|
|
59
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
60
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get a wrapped L_key by keyId
|
|
66
|
+
* @param {string} keyId - Key identifier
|
|
67
|
+
* @returns {Promise<Object|null>}
|
|
68
|
+
*/
|
|
69
|
+
async function getWrappedKey(keyId) {
|
|
70
|
+
if (!keyId || typeof keyId !== 'string') {
|
|
71
|
+
throw new Error('keyId is required and must be a string');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const db = await openDatabase();
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const tx = db.transaction(STORE_KEYS, 'readonly');
|
|
78
|
+
const store = tx.objectStore(STORE_KEYS);
|
|
79
|
+
const index = store.index('keyId');
|
|
80
|
+
|
|
81
|
+
const request = index.get(keyId);
|
|
82
|
+
|
|
83
|
+
request.onsuccess = () => {
|
|
84
|
+
const result = request.result;
|
|
85
|
+
if (result) {
|
|
86
|
+
resolve({
|
|
87
|
+
wrappedKey: result.wrappedKey,
|
|
88
|
+
userId: result.userId,
|
|
89
|
+
createdAt: result.createdAt
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
resolve(null);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
request.onerror = () => reject(request.error);
|
|
97
|
+
tx.oncomplete = () => db.close();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get all wrapped keys for a user
|
|
103
|
+
* @param {string} userId - User identifier
|
|
104
|
+
* @returns {Promise<Array>}
|
|
105
|
+
*/
|
|
106
|
+
async function getAllWrappedKeys(userId) {
|
|
107
|
+
if (!userId || typeof userId !== 'string') {
|
|
108
|
+
throw new Error('userId is required and must be a string');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const db = await openDatabase();
|
|
112
|
+
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const tx = db.transaction(STORE_KEYS, 'readonly');
|
|
115
|
+
const store = tx.objectStore(STORE_KEYS);
|
|
116
|
+
const index = store.index('userId');
|
|
117
|
+
|
|
118
|
+
const request = index.getAll(userId);
|
|
119
|
+
|
|
120
|
+
request.onsuccess = () => {
|
|
121
|
+
const results = request.result.map(r => ({
|
|
122
|
+
keyId: r.keyId,
|
|
123
|
+
wrappedKey: r.wrappedKey,
|
|
124
|
+
createdAt: r.createdAt
|
|
125
|
+
}));
|
|
126
|
+
resolve(results);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
request.onerror = () => reject(request.error);
|
|
130
|
+
tx.oncomplete = () => db.close();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Delete a wrapped key
|
|
136
|
+
* @param {string} keyId - Key identifier
|
|
137
|
+
* @returns {Promise<boolean>}
|
|
138
|
+
*/
|
|
139
|
+
async function deleteWrappedKey(keyId) {
|
|
140
|
+
if (!keyId || typeof keyId !== 'string') {
|
|
141
|
+
throw new Error('keyId is required and must be a string');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const db = await openDatabase();
|
|
145
|
+
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
const tx = db.transaction(STORE_KEYS, 'readwrite');
|
|
148
|
+
const store = tx.objectStore(STORE_KEYS);
|
|
149
|
+
const index = store.index('keyId');
|
|
150
|
+
|
|
151
|
+
const getRequest = index.get(keyId);
|
|
152
|
+
let deleted = false;
|
|
153
|
+
|
|
154
|
+
getRequest.onsuccess = () => {
|
|
155
|
+
const result = getRequest.result;
|
|
156
|
+
if (result) {
|
|
157
|
+
store.delete(result.id);
|
|
158
|
+
deleted = true;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
getRequest.onerror = () => reject(getRequest.error);
|
|
163
|
+
tx.oncomplete = () => { db.close(); resolve(deleted); };
|
|
164
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
saveWrappedKey,
|
|
170
|
+
getWrappedKey,
|
|
171
|
+
getAllWrappedKeys,
|
|
172
|
+
deleteWrappedKey,
|
|
173
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Share CRUD operations for IndexedDB
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { STORE_SHARES } = require('./constants');
|
|
7
|
+
const { openDatabase } = require('./db');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Save an encrypted S2 share
|
|
11
|
+
* @param {string} userId - User identifier
|
|
12
|
+
* @param {string} shareId - Share identifier
|
|
13
|
+
* @param {Uint8Array|string} encryptedShare - Encrypted data
|
|
14
|
+
* @param {number} [version] - Version number
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
*/
|
|
17
|
+
async function saveShare(userId, shareId, encryptedShare, version = 1) {
|
|
18
|
+
if (!userId || typeof userId !== 'string') {
|
|
19
|
+
throw new Error('userId is required and must be a string');
|
|
20
|
+
}
|
|
21
|
+
if (!shareId || typeof shareId !== 'string') {
|
|
22
|
+
throw new Error('shareId is required and must be a string');
|
|
23
|
+
}
|
|
24
|
+
if (!encryptedShare) {
|
|
25
|
+
throw new Error('encryptedShare is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const shareData = encryptedShare instanceof Uint8Array
|
|
29
|
+
? btoa(String.fromCharCode(...encryptedShare))
|
|
30
|
+
: encryptedShare;
|
|
31
|
+
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const db = await openDatabase();
|
|
34
|
+
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const tx = db.transaction(STORE_SHARES, 'readwrite');
|
|
37
|
+
const store = tx.objectStore(STORE_SHARES);
|
|
38
|
+
const index = store.index('userShare');
|
|
39
|
+
|
|
40
|
+
const getRequest = index.get([userId, shareId]);
|
|
41
|
+
|
|
42
|
+
getRequest.onsuccess = () => {
|
|
43
|
+
const existing = getRequest.result;
|
|
44
|
+
|
|
45
|
+
if (existing) {
|
|
46
|
+
existing.encryptedShare = shareData;
|
|
47
|
+
existing.version = version;
|
|
48
|
+
existing.updatedAt = now;
|
|
49
|
+
store.put(existing);
|
|
50
|
+
} else {
|
|
51
|
+
store.add({
|
|
52
|
+
userId,
|
|
53
|
+
shareId,
|
|
54
|
+
encryptedShare: shareData,
|
|
55
|
+
version,
|
|
56
|
+
createdAt: now,
|
|
57
|
+
updatedAt: now
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
getRequest.onerror = () => reject(getRequest.error);
|
|
63
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
64
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get an encrypted S2 share
|
|
70
|
+
* @param {string} userId - User identifier
|
|
71
|
+
* @param {string} shareId - Share identifier
|
|
72
|
+
* @returns {Promise<Object|null>}
|
|
73
|
+
*/
|
|
74
|
+
async function getShare(userId, shareId) {
|
|
75
|
+
if (!userId || typeof userId !== 'string') {
|
|
76
|
+
throw new Error('userId is required and must be a string');
|
|
77
|
+
}
|
|
78
|
+
if (!shareId || typeof shareId !== 'string') {
|
|
79
|
+
throw new Error('shareId is required and must be a string');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const db = await openDatabase();
|
|
83
|
+
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const tx = db.transaction(STORE_SHARES, 'readonly');
|
|
86
|
+
const store = tx.objectStore(STORE_SHARES);
|
|
87
|
+
const index = store.index('userShare');
|
|
88
|
+
|
|
89
|
+
const request = index.get([userId, shareId]);
|
|
90
|
+
|
|
91
|
+
request.onsuccess = () => {
|
|
92
|
+
const result = request.result;
|
|
93
|
+
if (result) {
|
|
94
|
+
resolve({
|
|
95
|
+
encryptedShare: result.encryptedShare,
|
|
96
|
+
version: result.version,
|
|
97
|
+
createdAt: result.createdAt,
|
|
98
|
+
updatedAt: result.updatedAt
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
resolve(null);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
request.onerror = () => reject(request.error);
|
|
106
|
+
tx.oncomplete = () => db.close();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get all shares for a user
|
|
112
|
+
* @param {string} userId - User identifier
|
|
113
|
+
* @returns {Promise<Array>}
|
|
114
|
+
*/
|
|
115
|
+
async function getAllShares(userId) {
|
|
116
|
+
if (!userId || typeof userId !== 'string') {
|
|
117
|
+
throw new Error('userId is required and must be a string');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const db = await openDatabase();
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const tx = db.transaction(STORE_SHARES, 'readonly');
|
|
124
|
+
const store = tx.objectStore(STORE_SHARES);
|
|
125
|
+
const index = store.index('userId');
|
|
126
|
+
|
|
127
|
+
const request = index.getAll(userId);
|
|
128
|
+
|
|
129
|
+
request.onsuccess = () => {
|
|
130
|
+
const results = request.result.map(r => ({
|
|
131
|
+
shareId: r.shareId,
|
|
132
|
+
encryptedShare: r.encryptedShare,
|
|
133
|
+
version: r.version,
|
|
134
|
+
createdAt: r.createdAt,
|
|
135
|
+
updatedAt: r.updatedAt
|
|
136
|
+
}));
|
|
137
|
+
resolve(results);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
request.onerror = () => reject(request.error);
|
|
141
|
+
tx.oncomplete = () => db.close();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Delete a specific share
|
|
147
|
+
* @param {string} userId - User identifier
|
|
148
|
+
* @param {string} shareId - Share identifier
|
|
149
|
+
* @returns {Promise<boolean>}
|
|
150
|
+
*/
|
|
151
|
+
async function deleteShare(userId, shareId) {
|
|
152
|
+
if (!userId || typeof userId !== 'string') {
|
|
153
|
+
throw new Error('userId is required and must be a string');
|
|
154
|
+
}
|
|
155
|
+
if (!shareId || typeof shareId !== 'string') {
|
|
156
|
+
throw new Error('shareId is required and must be a string');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const db = await openDatabase();
|
|
160
|
+
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const tx = db.transaction(STORE_SHARES, 'readwrite');
|
|
163
|
+
const store = tx.objectStore(STORE_SHARES);
|
|
164
|
+
const index = store.index('userShare');
|
|
165
|
+
|
|
166
|
+
const getRequest = index.get([userId, shareId]);
|
|
167
|
+
let deleted = false;
|
|
168
|
+
|
|
169
|
+
getRequest.onsuccess = () => {
|
|
170
|
+
const result = getRequest.result;
|
|
171
|
+
if (result) {
|
|
172
|
+
store.delete(result.id);
|
|
173
|
+
deleted = true;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
getRequest.onerror = () => reject(getRequest.error);
|
|
178
|
+
tx.oncomplete = () => { db.close(); resolve(deleted); };
|
|
179
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get share version
|
|
185
|
+
* @param {string} userId - User identifier
|
|
186
|
+
* @param {string} shareId - Share identifier
|
|
187
|
+
* @returns {Promise<number|null>}
|
|
188
|
+
*/
|
|
189
|
+
async function getShareVersion(userId, shareId) {
|
|
190
|
+
const share = await getShare(userId, shareId);
|
|
191
|
+
return share ? share.version : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
saveShare,
|
|
196
|
+
getShare,
|
|
197
|
+
getAllShares,
|
|
198
|
+
deleteShare,
|
|
199
|
+
getShareVersion,
|
|
200
|
+
};
|
package/client/browser.js
CHANGED
|
@@ -1,25 +1,192 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry point for api-ape client
|
|
3
|
+
*
|
|
4
|
+
* This module sets up the global `window.api` object for direct browser usage.
|
|
5
|
+
* It is designed to be loaded via a `<script>` tag and automatically establishes
|
|
6
|
+
* a WebSocket connection with automatic reconnection enabled.
|
|
7
|
+
*
|
|
8
|
+
* @module client/browser
|
|
9
|
+
* @file Browser bundle entry point - creates global `window.api` object
|
|
10
|
+
*
|
|
11
|
+
* @description
|
|
12
|
+
* When loaded in a browser environment, this module:
|
|
13
|
+
* 1. Establishes a WebSocket connection to the server
|
|
14
|
+
* 2. Enables automatic reconnection on disconnect
|
|
15
|
+
* 3. Exposes the `window.api` global object for making API calls
|
|
16
|
+
*
|
|
17
|
+
* The `window.api` object is a Proxy that allows calling server endpoints
|
|
18
|
+
* using a fluent, path-building syntax.
|
|
19
|
+
*
|
|
20
|
+
* @example <caption>Loading in HTML</caption>
|
|
21
|
+
* <script src="/api/ape.js"></script>
|
|
22
|
+
* <script>
|
|
23
|
+
* // The global `api` object is now available
|
|
24
|
+
* api.users.list().then(users => console.log(users))
|
|
25
|
+
* </script>
|
|
26
|
+
*
|
|
27
|
+
* @example <caption>Making API calls</caption>
|
|
28
|
+
* // Call /users endpoint
|
|
29
|
+
* api.users({ name: 'Alice' })
|
|
30
|
+
*
|
|
31
|
+
* // Call /users/create endpoint
|
|
32
|
+
* api.users.create({ name: 'Bob', email: 'bob@example.com' })
|
|
33
|
+
*
|
|
34
|
+
* // Call /chat/messages endpoint with path parameter
|
|
35
|
+
* api.chat.messages('/room123', { text: 'Hello!' })
|
|
36
|
+
*
|
|
37
|
+
* @example <caption>Subscribing to broadcasts</caption>
|
|
38
|
+
* // Listen for 'notification' broadcasts from the server
|
|
39
|
+
* api.on('notification', (payload) => {
|
|
40
|
+
* console.log('Received:', payload.data)
|
|
41
|
+
* })
|
|
42
|
+
*
|
|
43
|
+
* @example <caption>Monitoring connection state</caption>
|
|
44
|
+
* api.onConnectionChange((state) => {
|
|
45
|
+
* console.log('Connection state:', state)
|
|
46
|
+
* // state: 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected'
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
* @example <caption>Checking transport type</caption>
|
|
50
|
+
* console.log('Transport:', api.transport) // 'websocket' | 'polling' | null
|
|
51
|
+
*
|
|
52
|
+
* @see {@link module:client/connectSocket} for connection implementation details
|
|
53
|
+
* @see {@link module:client/connection/proxy} for the Proxy-based API syntax
|
|
54
|
+
*/
|
|
2
55
|
|
|
3
|
-
|
|
4
|
-
connectSocket.autoReconnect()
|
|
56
|
+
import connectSocket from "./connectSocket.js";
|
|
5
57
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The client instance created by connectSocket.
|
|
60
|
+
* Contains the sender proxy, event handlers, and transport information.
|
|
61
|
+
*
|
|
62
|
+
* @type {Object}
|
|
63
|
+
* @property {Proxy} sender - Proxied object for making API calls
|
|
64
|
+
* @property {Function} setOnReceiver - Function to register broadcast handlers
|
|
65
|
+
* @property {Function} onConnectionChange - Function to monitor connection state
|
|
66
|
+
* @property {string|null} transport - Current transport type ('websocket' | 'polling' | null)
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
const client = connectSocket();
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Enable automatic reconnection when the WebSocket connection is lost.
|
|
73
|
+
* This ensures the client will attempt to reconnect after disconnection.
|
|
74
|
+
*/
|
|
75
|
+
connectSocket.autoReconnect();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Global API object exposed on `window.api`.
|
|
79
|
+
*
|
|
80
|
+
* This is a Proxy object that intercepts property access to build endpoint paths.
|
|
81
|
+
* Each property access returns a new Proxy, allowing chained path building.
|
|
82
|
+
*
|
|
83
|
+
* @global
|
|
84
|
+
* @name api
|
|
85
|
+
* @type {Proxy}
|
|
86
|
+
*
|
|
87
|
+
* @property {Function} on - Subscribe to server broadcasts
|
|
88
|
+
* @property {Function} onConnectionChange - Subscribe to connection state changes
|
|
89
|
+
* @property {string|null} transport - Current transport type (read-only)
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // These are equivalent:
|
|
93
|
+
* api.users({ id: 1 }) // Calls /users
|
|
94
|
+
* api.users.profile({ id: 1 }) // Calls /users/profile
|
|
95
|
+
*/
|
|
96
|
+
window.api = client.sender;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Register a handler for server broadcasts.
|
|
100
|
+
*
|
|
101
|
+
* @function api.on
|
|
102
|
+
* @param {string} type - The broadcast type/event name to listen for
|
|
103
|
+
* @param {Function} handler - Callback function invoked when broadcast is received
|
|
104
|
+
* @param {Object} handler.payload - The broadcast payload
|
|
105
|
+
* @param {*} handler.payload.data - The broadcast data
|
|
106
|
+
* @param {string} handler.payload.type - The broadcast type
|
|
107
|
+
* @param {Error|null} handler.payload.err - Error if any occurred
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* api.on('chat.message', ({ data, type }) => {
|
|
111
|
+
* console.log(`New message: ${data.text}`)
|
|
112
|
+
* })
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* api.on('user.joined', ({ data }) => {
|
|
116
|
+
* showNotification(`${data.username} joined the room`)
|
|
117
|
+
* })
|
|
118
|
+
*/
|
|
119
|
+
Object.defineProperty(window.api, "on", {
|
|
120
|
+
value: client.setOnReceiver,
|
|
121
|
+
writable: false,
|
|
122
|
+
enumerable: false,
|
|
123
|
+
configurable: false,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Subscribe to connection state changes.
|
|
128
|
+
*
|
|
129
|
+
* The handler is called immediately with the current state and then
|
|
130
|
+
* again whenever the connection state changes.
|
|
131
|
+
*
|
|
132
|
+
* @function api.onConnectionChange
|
|
133
|
+
* @param {Function} handler - Callback invoked on state changes
|
|
134
|
+
* @param {ConnectionState} handler.state - The new connection state
|
|
135
|
+
* @returns {Function} Unsubscribe function to stop receiving updates
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const unsubscribe = api.onConnectionChange((state) => {
|
|
139
|
+
* switch (state) {
|
|
140
|
+
* case 'connected':
|
|
141
|
+
* hideOfflineBanner()
|
|
142
|
+
* break
|
|
143
|
+
* case 'disconnected':
|
|
144
|
+
* case 'offline':
|
|
145
|
+
* showOfflineBanner()
|
|
146
|
+
* break
|
|
147
|
+
* case 'walled':
|
|
148
|
+
* showCaptivePortalWarning()
|
|
149
|
+
* break
|
|
150
|
+
* }
|
|
151
|
+
* })
|
|
152
|
+
*
|
|
153
|
+
* // Later, to stop listening:
|
|
154
|
+
* unsubscribe()
|
|
155
|
+
*/
|
|
156
|
+
Object.defineProperty(window.api, "onConnectionChange", {
|
|
157
|
+
value: client.onConnectionChange,
|
|
158
|
+
writable: false,
|
|
159
|
+
enumerable: false,
|
|
160
|
+
configurable: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Current transport type (read-only).
|
|
165
|
+
*
|
|
166
|
+
* Indicates which transport mechanism is currently being used for communication:
|
|
167
|
+
* - `'websocket'` - Primary WebSocket connection is active
|
|
168
|
+
* - `'polling'` - Fallback HTTP streaming/long-polling is active
|
|
169
|
+
* - `null` - No connection established yet
|
|
170
|
+
*
|
|
171
|
+
* This property is read-only and managed internally by api-ape.
|
|
172
|
+
* The transport may change during the lifecycle of the connection
|
|
173
|
+
* (e.g., falling back from WebSocket to polling if WS is blocked).
|
|
174
|
+
*
|
|
175
|
+
* @name api.transport
|
|
176
|
+
* @type {string|null}
|
|
177
|
+
* @readonly
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* console.log('Current transport:', api.transport)
|
|
181
|
+
* // Output: 'websocket', 'polling', or null
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* if (api.transport === 'polling') {
|
|
185
|
+
* console.warn('WebSocket unavailable, using fallback transport')
|
|
186
|
+
* }
|
|
187
|
+
*/
|
|
188
|
+
Object.defineProperty(window.api, "transport", {
|
|
189
|
+
get: () => client.transport,
|
|
190
|
+
enumerable: false,
|
|
191
|
+
configurable: false,
|
|
192
|
+
});
|