dolphin-client 1.0.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 ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Shankar Phuyal
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARDARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # dolphin-client 🐬 β€” HTML is back!
2
+
3
+ **Hookless, framework-agnostic, zero-dependency real-time reactive DOM-binding engine.**
4
+
5
+ Unleash the full power of modern native browser APIs and DolphinCSS without the heavy framework tax of React or Vue!
6
+
7
+ ---
8
+
9
+ ### πŸ’Œ Returning Home: A Love Letter to HTML
10
+
11
+ Remember the early days of your coding journey? Writing a simple `index.html` in a plain text editor, double-clicking it, and watching it open instantly in the browser. No complex build pipelines, no `node_modules` weighing hundreds of megabytes, no state hooks, and no framework fatigue. Just a direct, pure, and magical relationship between your markup and the browser.
12
+
13
+ Over the last decade, we treated HTML as a dead, passive mounting point (`<div id="root"></div>`) while building virtual universes in JavaScript. We spent 80% of our time debugging configurations and boilerplate, and only 20% building beautiful user experiences.
14
+
15
+ **Dolphin Client is our way of coming home.**
16
+
17
+ By breathing life back into standard HTML, we have resurrected the simplicity of standard markup and empowered it with the modern superpowers of WebSockets, IndexedDB, WebRTC, and hardware-accelerated CSS variables. It is the renaissance of the native web standards we all fell in love with.
18
+
19
+ *Because the best JavaScript is actually the code you never had to write.*
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ - **Hookless Reactivity**: Bind topics to DOM elements via HTML data attributesβ€”no React, Vue, or Angular state management required.
26
+ - **Unified Event Binding**: Loop-based browser event handling for values (`data-rt-push`) and actions (`data-rt-[event]` / `data-api-[event]`).
27
+ - **Context API/Prop drilling in Pure DOM**: Crawls up the DOM tree (`getClosestContext`) to fetch parent contexts and inject parameters.
28
+ - **REST API + Realtime Hybrid Support**: Evaluates templates (`data-rt-template`) on initial HTTP fetches (`data-api-get`) and transitions seamlessly to real-time WebSockets on connection.
29
+ - **WebRTC Intercom Signaling**: Built-in methods to handle peer connections, track negotiation, ICE candidates, and signaling.
30
+ - **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs.
31
+
32
+ ### Method 1: NPM (For Modern Bundlers)
33
+ ```bash
34
+ npm install dolphin-client
35
+ ```
36
+
37
+ ### Method 2: Direct Local Download (For No-Install / Plain HTML)
38
+ Tired of the command line and `node_modules` clutter? We've got you covered!
39
+
40
+ [![Download dolphin-client.js](https://img.shields.io/badge/Download-dolphin--client.js-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://unpkg.com/dolphin-client/dist/dolphin-client.js) &nbsp;&nbsp; [![Download dolphin-bundle.zip](https://img.shields.io/badge/Download-dolphin--bundle.zip-4CAF50?style=for-the-badge&logo=archive&logoColor=white)](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
41
+
42
+ *(Right-click on **dolphin-client.js** and select "Save Link As..." to save it directly to your project)*
43
+
44
+
45
+ Extract the zip directly inside your project folder to get a clean local directory structure with pre-bundled assets:
46
+ ```
47
+ my-project/
48
+ β”œβ”€β”€ css/
49
+ β”‚ └── dolphin-css.css (DolphinCSS Premium Visuals Layer)
50
+ β”œβ”€β”€ js/
51
+ β”‚ └── dolphin-client.js (Dolphin Reactivity Engine)
52
+ └── index.html (A ready-to-run skeleton template!)
53
+ ```
54
+ Inside your HTML, simply link them locally:
55
+ ```html
56
+ <link rel="stylesheet" href="css/dolphin-css.css">
57
+ <script src="js/dolphin-client.js"></script>
58
+ ```
59
+
60
+ ## Basic Usage
61
+
62
+ ### 1. In Modern Bundlers (Next.js, Vite, React)
63
+
64
+ ```javascript
65
+ import { DolphinClient } from 'dolphin-client';
66
+
67
+ const dolphin = new DolphinClient('http://localhost:3000', 'ROOM_101');
68
+ await dolphin.connect();
69
+ ```
70
+
71
+ ### 2. In Plain HTML (Static / Script Tag)
72
+
73
+ ```html
74
+ <script src="node_modules/dolphin-client/dist/dolphin-client.js"></script>
75
+ <script>
76
+ const dolphin = new DolphinModule.DolphinClient('http://localhost:3000', 'ROOM_101');
77
+ dolphin.connect();
78
+ </script>
79
+ ```
80
+
81
+ ## HTML Directives
82
+
83
+ ### Pushing value changes
84
+ ```html
85
+ <input name="chat" data-rt-push="chat/messages/ROOM_101" placeholder="Type..." />
86
+ ```
87
+
88
+ ### Directives & Templates
89
+ ```html
90
+ <div data-api-get="/api/devices" data-rt-bind="devices/online" data-rt-template='
91
+ <div data-rt-type="context">
92
+ <h3>{{id}}</h3>
93
+ <button onclick="dialPeer(&apos;{{id}}&apos;)">Dial</button>
94
+ </div>
95
+ '></div>
96
+ ```
97
+
98
+ ## License
99
+
100
+ ISC License
package/dist/a11y.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function attachA11y(clientProto: any): void;
@@ -0,0 +1 @@
1
+ export declare function attachAnimations(clientProto: any): void;
package/dist/api.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export declare class APIHandler {
2
+ client: any;
3
+ /** @param {DolphinClient} client */
4
+ constructor(client: any);
5
+ /** @private */
6
+ _createProxy(pathParts: any): {
7
+ (options: any): any;
8
+ get(pathOrOptions: any, options: any): any;
9
+ post(pathOrBody: any, bodyOrOptions: any, options: any): any;
10
+ put(pathOrBody: any, bodyOrOptions: any, options: any): any;
11
+ patch(pathOrBody: any, bodyOrOptions: any, options: any): any;
12
+ del(pathOrOptions: any, options: any): any;
13
+ request(method: any, subPath: any, body: any, options: any): any;
14
+ requestDirect(method: any, path: any, body: any, options: any): any;
15
+ };
16
+ /**
17
+ * Intercept request for offline-first caching and queuing.
18
+ */
19
+ request(method: string, path: string, body?: any, options?: any): any;
20
+ /**
21
+ * Make an HTTP request with timeout + auto token refresh.
22
+ * @param {string} method
23
+ * @param {string} path
24
+ * @param {any} [body]
25
+ * @param {RequestInit} [options]
26
+ * @param {boolean} [_isRetry=false] β€” internal: prevent infinite refresh loop
27
+ * @returns {Promise<any>}
28
+ */
29
+ requestDirect(method: any, path: any, body?: any, options?: any): any;
30
+ }
package/dist/api.js ADDED
@@ -0,0 +1,112 @@
1
+ export class APIHandler {
2
+ /** @param {DolphinClient} client */
3
+ constructor(client) {
4
+ this.client = client;
5
+ return this._createProxy([]);
6
+ }
7
+ /** @private */
8
+ _createProxy(pathParts) {
9
+ const joined = pathParts.join('/');
10
+ const target = (options) => this.request('GET', joined, null, options);
11
+ target.get = (pathOrOptions, options) => typeof pathOrOptions === 'string'
12
+ ? this.request('GET', pathOrOptions, null, options)
13
+ : this.request('GET', joined, null, pathOrOptions);
14
+ target.post = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === 'string'
15
+ ? this.request('POST', pathOrBody, bodyOrOptions, options)
16
+ : this.request('POST', joined, pathOrBody, bodyOrOptions);
17
+ target.put = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === 'string'
18
+ ? this.request('PUT', pathOrBody, bodyOrOptions, options)
19
+ : this.request('PUT', joined, pathOrBody, bodyOrOptions);
20
+ target.patch = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === 'string'
21
+ ? this.request('PATCH', pathOrBody, bodyOrOptions, options)
22
+ : this.request('PATCH', joined, pathOrBody, bodyOrOptions);
23
+ target.del = (pathOrOptions, options) => typeof pathOrOptions === 'string'
24
+ ? this.request('DELETE', pathOrOptions, null, options)
25
+ : this.request('DELETE', joined, null, pathOrOptions);
26
+ target.request = (method, subPath, body, options) => {
27
+ const finalPath = subPath
28
+ ? `${joined}/${subPath.startsWith('/') ? subPath.slice(1) : subPath}`
29
+ : joined;
30
+ return this.request(method, finalPath, body, options);
31
+ };
32
+ const methods = ['get', 'post', 'put', 'patch', 'del', 'request'];
33
+ return new Proxy(target, {
34
+ get: (t, prop) => {
35
+ if (typeof prop === 'string' && !methods.includes(prop)) {
36
+ return this._createProxy([...pathParts, prop]);
37
+ }
38
+ return t[prop];
39
+ }
40
+ });
41
+ }
42
+ /**
43
+ * Make an HTTP request with timeout + auto token refresh.
44
+ * @param {string} method
45
+ * @param {string} path
46
+ * @param {any} [body]
47
+ * @param {RequestInit} [options]
48
+ * @param {boolean} [_isRetry=false] β€” internal: prevent infinite refresh loop
49
+ * @returns {Promise<any>}
50
+ */
51
+ async request(method, path, body = null, options = {}) {
52
+ const _isRetry = options._isRetry === true;
53
+ const url = `${this.client.httpUrl}${path.startsWith('/') ? path : '/' + path}`;
54
+ const controller = new AbortController();
55
+ const timeoutId = setTimeout(() => controller.abort(), this.client.options.timeout);
56
+ const headers = {
57
+ 'Content-Type': 'application/json',
58
+ ...(options.headers || {}),
59
+ };
60
+ if (this.client.accessToken) {
61
+ headers['Authorization'] = `Bearer ${this.client.accessToken}`;
62
+ }
63
+ const fetchOptions = { ...options };
64
+ delete fetchOptions._isRetry;
65
+ try {
66
+ const response = await fetch(url, {
67
+ method,
68
+ headers,
69
+ signal: controller.signal,
70
+ ...(body ? { body: JSON.stringify(body) } : {}),
71
+ ...fetchOptions,
72
+ });
73
+ clearTimeout(timeoutId);
74
+ // Auto-refresh: 401 + not a retry + autoRefreshToken enabled
75
+ if (response.status === 401 &&
76
+ !_isRetry &&
77
+ this.client.options.autoRefreshToken) {
78
+ const refreshed = await this.client.auth._silentRefresh();
79
+ if (refreshed) {
80
+ return this.request(method, path, body, { ...options, _isRetry: true });
81
+ }
82
+ }
83
+ const contentType = response.headers.get('content-type') || '';
84
+ const data = contentType.includes('application/json')
85
+ ? await response.json()
86
+ : await response.text();
87
+ if (!response.ok)
88
+ throw { status: response.status, data };
89
+ // Hookless auth token auto-save
90
+ if (data && typeof data === 'object') {
91
+ if (data.accessToken) {
92
+ this.client.setToken(data.accessToken);
93
+ if (data.user)
94
+ this.client.auth.user = data.user;
95
+ }
96
+ }
97
+ // Auto-broadcast data changes
98
+ if (this.client.options.autoBroadcast && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase())) {
99
+ const cleanPath = path.startsWith('/') ? path.substring(1) : path;
100
+ this.client.publish(cleanPath, { method: method.toUpperCase(), payload: body, result: data });
101
+ }
102
+ return data;
103
+ }
104
+ catch (err) {
105
+ clearTimeout(timeoutId);
106
+ if (err.name === 'AbortError') {
107
+ throw { status: 408, data: { error: 'Request timed out' } };
108
+ }
109
+ throw err;
110
+ }
111
+ }
112
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ export declare class AuthHandler {
2
+ client: any;
3
+ user: any;
4
+ _refreshing: boolean;
5
+ /** @param {DolphinClient} client */
6
+ constructor(client: any);
7
+ /**
8
+ * Login with email + password.
9
+ * @param {string} email
10
+ * @param {string} password
11
+ */
12
+ login(email: any, password: any): Promise<any>;
13
+ /**
14
+ * Register a new account.
15
+ * @param {{ email: string, password: string, [key: string]: any }} data
16
+ */
17
+ register(data: any): Promise<any>;
18
+ /** Get current user profile. */
19
+ me(): Promise<any>;
20
+ /** Logout and clear token. */
21
+ logout(): Promise<void>;
22
+ /**
23
+ * Manually refresh the access token using the httpOnly refresh-token cookie.
24
+ * Called automatically on 401 if autoRefreshToken is enabled.
25
+ * @returns {Promise<boolean>} β€” true if refresh succeeded
26
+ */
27
+ refresh(): Promise<boolean>;
28
+ /** @private */
29
+ _silentRefresh(): Promise<boolean>;
30
+ /**
31
+ * Verify a 2FA TOTP code after login.
32
+ * @param {string} code β€” 6-digit TOTP code
33
+ * @param {string} [email] β€” email (if not already in user)
34
+ */
35
+ verify2FA(code: any, email: any): Promise<any>;
36
+ /**
37
+ * Enable 2FA β€” returns QR code URL and secret.
38
+ */
39
+ enable2FA(): Promise<any>;
40
+ /**
41
+ * Disable 2FA.
42
+ * @param {string} code β€” current TOTP code to confirm
43
+ */
44
+ disable2FA(code: any): Promise<any>;
45
+ /**
46
+ * Request a password reset email.
47
+ * @param {string} email
48
+ */
49
+ forgotPassword(email: any): Promise<any>;
50
+ /**
51
+ * Reset password using the token from email.
52
+ * @param {string} token
53
+ * @param {string} newPassword
54
+ */
55
+ resetPassword(token: any, newPassword: any): Promise<any>;
56
+ }
package/dist/auth.js ADDED
@@ -0,0 +1,120 @@
1
+ export class AuthHandler {
2
+ /** @param {DolphinClient} client */
3
+ constructor(client) {
4
+ this.client = client;
5
+ /** @type {any|null} */
6
+ this.user = null;
7
+ this._refreshing = false;
8
+ }
9
+ /**
10
+ * Login with email + password.
11
+ * @param {string} email
12
+ * @param {string} password
13
+ */
14
+ async login(email, password) {
15
+ const res = await this.client.api.post('/api/auth/login', { email, password });
16
+ if (res.accessToken) {
17
+ this.client.setToken(res.accessToken);
18
+ this.user = res.user || null;
19
+ }
20
+ return res;
21
+ }
22
+ /**
23
+ * Register a new account.
24
+ * @param {{ email: string, password: string, [key: string]: any }} data
25
+ */
26
+ async register(data) {
27
+ return this.client.api.post('/api/auth/register', data);
28
+ }
29
+ /** Get current user profile. */
30
+ async me() {
31
+ const res = await this.client.api.get('/api/auth/me');
32
+ if (res.success)
33
+ this.user = res.data;
34
+ return res;
35
+ }
36
+ /** Logout and clear token. */
37
+ async logout() {
38
+ try {
39
+ await this.client.api.post('/api/auth/logout');
40
+ }
41
+ catch { }
42
+ this.client.setToken(null);
43
+ this.user = null;
44
+ }
45
+ /**
46
+ * Manually refresh the access token using the httpOnly refresh-token cookie.
47
+ * Called automatically on 401 if autoRefreshToken is enabled.
48
+ * @returns {Promise<boolean>} β€” true if refresh succeeded
49
+ */
50
+ async refresh() {
51
+ return this._silentRefresh();
52
+ }
53
+ /** @private */
54
+ async _silentRefresh() {
55
+ if (this._refreshing)
56
+ return false;
57
+ this._refreshing = true;
58
+ try {
59
+ const res = await this.client.api.post('/api/auth/refresh', null, { _isRetry: true });
60
+ if (res.accessToken) {
61
+ this.client.setToken(res.accessToken);
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+ catch {
67
+ this.client.setToken(null);
68
+ return false;
69
+ }
70
+ finally {
71
+ this._refreshing = false;
72
+ }
73
+ }
74
+ /**
75
+ * Verify a 2FA TOTP code after login.
76
+ * @param {string} code β€” 6-digit TOTP code
77
+ * @param {string} [email] β€” email (if not already in user)
78
+ */
79
+ async verify2FA(code, email) {
80
+ const payload = {
81
+ code,
82
+ email: email || this.user?.email,
83
+ };
84
+ const res = await this.client.api.post('/api/auth/2fa/verify', payload);
85
+ if (res.accessToken) {
86
+ this.client.setToken(res.accessToken);
87
+ if (res.user)
88
+ this.user = res.user;
89
+ }
90
+ return res;
91
+ }
92
+ /**
93
+ * Enable 2FA β€” returns QR code URL and secret.
94
+ */
95
+ async enable2FA() {
96
+ return this.client.api.post('/api/auth/2fa/enable');
97
+ }
98
+ /**
99
+ * Disable 2FA.
100
+ * @param {string} code β€” current TOTP code to confirm
101
+ */
102
+ async disable2FA(code) {
103
+ return this.client.api.post('/api/auth/2fa/disable', { code });
104
+ }
105
+ /**
106
+ * Request a password reset email.
107
+ * @param {string} email
108
+ */
109
+ async forgotPassword(email) {
110
+ return this.client.api.post('/api/auth/forgot-password', { email });
111
+ }
112
+ /**
113
+ * Reset password using the token from email.
114
+ * @param {string} token
115
+ * @param {string} newPassword
116
+ */
117
+ async resetPassword(token, newPassword) {
118
+ return this.client.api.post('/api/auth/reset-password', { token, newPassword });
119
+ }
120
+ }
@@ -0,0 +1 @@
1
+ export declare function attachCollab(clientProto: any): void;
package/dist/core.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ export declare class DolphinClient {
2
+ host: string;
3
+ httpUrl: string;
4
+ deviceId: string;
5
+ options: any;
6
+ socket: any;
7
+ storage: any;
8
+ accessToken: string | null;
9
+ api: any;
10
+ auth: any;
11
+ store: any;
12
+ handlers: Map<string, Set<any>>;
13
+ signalHandlers: Set<any>;
14
+ fileHandlers: Set<any>;
15
+ _offlineQueue: string[];
16
+ reconnectAttempts: number;
17
+ _attachedListeners: {
18
+ target: any;
19
+ event: string;
20
+ cb: any;
21
+ }[];
22
+ constructor(url?: string, deviceId?: string, options?: {});
23
+ /** Save or clear the access token */
24
+ setToken(token: any): void;
25
+ /** Connect to the Dolphin realtime server */
26
+ connect(): Promise<void>;
27
+ /** Disconnect cleanly */
28
+ disconnect(): void;
29
+ /** @private */
30
+ _handleMessage(data: any): void;
31
+ /** @private */
32
+ _dispatch(pattern: any, payload: any, topic?: any): void;
33
+ /** @private */
34
+ _sendRaw(msg: any): void;
35
+ /** Flush buffered messages after reconnect @private */
36
+ _flushOfflineQueue(): void;
37
+ /** @private */
38
+ _sendAck(to: any, msgId: any): void;
39
+ /** MQTT wildcard topic matching @private */
40
+ _matchTopic(pattern: any, topic: any): boolean;
41
+ /** @private */
42
+ _maybeReconnect(): void;
43
+ /**
44
+ * Subscribe to a topic (MQTT wildcards supported: + and #).
45
+ * @param {string} topic
46
+ * @param {TopicCallback} callback
47
+ */
48
+ subscribe(topic: any, callback: any): void;
49
+ /**
50
+ * Unsubscribe from a topic.
51
+ * @param {string} topic
52
+ * @param {TopicCallback} callback
53
+ */
54
+ unsubscribe(topic: any, callback: any): void;
55
+ /**
56
+ * Publish a message to a topic. Queued if offline.
57
+ * @param {string} topic
58
+ * @param {any} payload
59
+ */
60
+ publish(topic: any, payload: any): void;
61
+ /**
62
+ * High-frequency data push (IoT sensors).
63
+ * @param {string} topic
64
+ * @param {any} payload
65
+ */
66
+ pubPush(topic: any, payload: any): void;
67
+ /**
68
+ * Request historical data from a topic.
69
+ * @param {string} topic
70
+ * @param {number} [count=10]
71
+ */
72
+ subPull(topic: any, count?: number): void;
73
+ /**
74
+ * Upload a file to the server in chunks.
75
+ * @param {string} fileId
76
+ * @param {Blob|ArrayBuffer|Uint8Array} fileData
77
+ * @param {string} [filename]
78
+ * @param {function(number): void} [onProgress] β€” progress callback (0-100)
79
+ * @returns {Promise<void>}
80
+ */
81
+ pubFile(fileId: any, fileData: any, filename: string, onProgress: any): Promise<void>;
82
+ /** @private */
83
+ _uint8ToBase64(uint8: any): string;
84
+ /**
85
+ * Download a file from the server by chunks.
86
+ * @param {string} fileId
87
+ * @param {number} [startChunk=0]
88
+ */
89
+ subFile(fileId: any, startChunk?: number): void;
90
+ /**
91
+ * Resume a file download from saved progress.
92
+ * @param {string} fileId
93
+ */
94
+ resumeFile(fileId: any): void;
95
+ /**
96
+ * Save download chunk progress.
97
+ * @param {string} fileId
98
+ * @param {number} chunkIndex
99
+ */
100
+ saveFileProgress(fileId: any, chunkIndex: any): void;
101
+ /**
102
+ * @param {function(SignalMessage): void} handler
103
+ */
104
+ onSignal(handler: any): void;
105
+ /**
106
+ * @param {function(SignalMessage): void} handler
107
+ */
108
+ offSignal(handler: any): void;
109
+ /**
110
+ * @param {function(FileMetadata): void} handler
111
+ */
112
+ onFileAvailable(handler: any): void;
113
+ /**
114
+ * @param {function(FileMetadata): void} handler
115
+ */
116
+ offFileAvailable(handler: any): void;
117
+ addDomListener(target: any, event: string, cb: any): void;
118
+ cleanupDomListeners(): void;
119
+ }