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.
Files changed (186) hide show
  1. package/README.md +59 -572
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -203
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +31 -6
  44. package/server/README.md +272 -67
  45. package/server/adapters/README.md +23 -14
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +322 -71
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -219
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -224
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -311
  186. 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
- import connectSocket from './connectSocket.js'
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
- const client = connectSocket()
4
- connectSocket.autoReconnect()
56
+ import connectSocket from "./connectSocket.js";
5
57
 
6
- // Global API - use defineProperty to bypass Proxy interception
7
- window.api = client.sender
8
- Object.defineProperty(window.api, 'on', {
9
- value: client.setOnReceiver,
10
- writable: false,
11
- enumerable: false,
12
- configurable: false
13
- })
14
- Object.defineProperty(window.api, 'onConnectionChange', {
15
- value: client.onConnectionChange,
16
- writable: false,
17
- enumerable: false,
18
- configurable: false
19
- })
20
- // Read-only transport property - only ape can change this internally
21
- Object.defineProperty(window.api, 'transport', {
22
- get: () => client.transport,
23
- enumerable: false,
24
- configurable: false
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
+ });