dolphin-client 1.0.1 → 1.0.3
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 +114 -30
- package/dist/dolphin-client.js +78 -0
- package/dist/dolphin-client.min.js +26 -0
- package/dist/index.cjs +78 -0
- package/dist/index.js +78 -0
- package/fulltutorial.md +734 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ By breathing life back into standard HTML, we have resurrected the simplicity of
|
|
|
23
23
|
## Documentation
|
|
24
24
|
|
|
25
25
|
> [!TIP]
|
|
26
|
-
> 📖 Read our comprehensive **[Full Developer Tutorial & Integration Guide](
|
|
26
|
+
> 📖 Read our comprehensive **[Full Developer Tutorial & Integration Guide](https://github.com/Phuyalshankar/dolphin-client/blob/main/fulltutorial.md)** for detailed guides, Next.js setups, WebRTC intercoms, global stores, drag-and-drop sortable lists, and real-world examples!
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -35,7 +35,11 @@ By breathing life back into standard HTML, we have resurrected the simplicity of
|
|
|
35
35
|
- **Context API/Prop drilling in Pure DOM**: Crawls up the DOM tree (`getClosestContext`) to fetch parent contexts and inject parameters.
|
|
36
36
|
- **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.
|
|
37
37
|
- **WebRTC Intercom Signaling**: Built-in methods to handle peer connections, track negotiation, ICE candidates, and signaling.
|
|
38
|
-
- **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs (~
|
|
38
|
+
- **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs (~39KB compressed bundle!).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation & Setup
|
|
39
43
|
|
|
40
44
|
### Method 1: NPM (For Modern Bundlers)
|
|
41
45
|
```bash
|
|
@@ -45,19 +49,38 @@ npm install dolphin-client
|
|
|
45
49
|
### Method 2: Direct Local Download (For No-Install / Plain HTML)
|
|
46
50
|
Tired of the command line and `node_modules` clutter? We've got you covered!
|
|
47
51
|
|
|
48
|
-
[](https://unpkg.com/dolphin-client/dist/dolphin-client.js) [](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
[](https://unpkg.com/dolphin-client/dist/dolphin-client.js) [](https://unpkg.com/dolphin-client/dist/dolphin-client.min.js) [](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
|
|
53
|
+
|
|
54
|
+
> [!IMPORTANT]
|
|
55
|
+
> **How to Download the JS Files:**
|
|
56
|
+
> Clicking the JS download buttons will display the raw JavaScript code in your browser.
|
|
57
|
+
> To download it as a file:
|
|
58
|
+
> 1. **Right-click** on the yellow/blue button and select **"Save Link As..."** (or "Save Target As...").
|
|
59
|
+
> 2. Or, if you clicked it, press **`Ctrl + S`** (or `Cmd + S` on Mac) on the code page to save it.
|
|
60
|
+
> 3. Alternatively, run these commands in your terminal to download directly:
|
|
61
|
+
> * **Standard (Development):**
|
|
62
|
+
> ```bash
|
|
63
|
+
> curl -o dolphin-client.js https://unpkg.com/dolphin-client/dist/dolphin-client.js
|
|
64
|
+
> ```
|
|
65
|
+
> * **Minified (Production):**
|
|
66
|
+
> ```bash
|
|
67
|
+
> curl -o dolphin-client.min.js https://unpkg.com/dolphin-client/dist/dolphin-client.min.js
|
|
68
|
+
> ```
|
|
51
69
|
|
|
70
|
+
> [!TIP]
|
|
71
|
+
> **If the ZIP file download is unavailable:**
|
|
72
|
+
> You can clone this repository directly or copy the `dist/dolphin-client.js` (or `dist/dolphin-client.min.js`) file from your clone.
|
|
73
|
+
> For the premium styling layer, create a `dolphin-css.css` file and add the custom effects (like `.fx-glass` and `.fx-neon`) described in the **[Full Developer Tutorial](https://github.com/Phuyalshankar/dolphin-client/blob/main/fulltutorial.md)**.
|
|
52
74
|
|
|
53
75
|
Extract the zip directly inside your project folder to get a clean local directory structure with pre-bundled assets:
|
|
54
76
|
```
|
|
55
77
|
my-project/
|
|
56
78
|
├── css/
|
|
57
|
-
│ └── dolphin-css.css
|
|
79
|
+
│ └── dolphin-css.css (DolphinCSS Premium Visuals Layer)
|
|
58
80
|
├── js/
|
|
59
|
-
│
|
|
60
|
-
└──
|
|
81
|
+
│ ├── dolphin-client.js (Standard Reactivity Engine)
|
|
82
|
+
│ └── dolphin-client.min.js (Minified Production Reactivity Engine)
|
|
83
|
+
└── index.html (A ready-to-run skeleton template!)
|
|
61
84
|
```
|
|
62
85
|
Inside your HTML, simply link them locally:
|
|
63
86
|
```html
|
|
@@ -65,44 +88,105 @@ Inside your HTML, simply link them locally:
|
|
|
65
88
|
<script src="js/dolphin-client.js"></script>
|
|
66
89
|
```
|
|
67
90
|
|
|
68
|
-
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Interactive Examples Guide
|
|
94
|
+
|
|
95
|
+
Below are clean, ready-to-use HTML examples for the core features of Dolphin Client. Because Dolphin supports **Zero-Configuration Auto-Initialization**, these will run instantly!
|
|
96
|
+
|
|
97
|
+
### 1. Real-Time (RT) Pub/Sub (Real-time Chat)
|
|
98
|
+
Publish inputs on typing, and display incoming messages in real-time under a WebSocket topic:
|
|
69
99
|
|
|
70
|
-
|
|
100
|
+
```html
|
|
101
|
+
<!-- Automatically publishes typed values to topic 'chat/messages' -->
|
|
102
|
+
<input name="chatMessage" data-rt-push="chat/messages" placeholder="Type a message..." />
|
|
103
|
+
|
|
104
|
+
<!-- Displays and appends all incoming messages under 'chat/messages' -->
|
|
105
|
+
<div data-rt-bind="chat/messages" data-rt-template='
|
|
106
|
+
<div class="message-card">
|
|
107
|
+
<span class="user-label">@user:</span>
|
|
108
|
+
<p>{{chatMessage}}</p>
|
|
109
|
+
</div>
|
|
110
|
+
'></div>
|
|
111
|
+
```
|
|
71
112
|
|
|
72
|
-
|
|
73
|
-
|
|
113
|
+
### 2. REST API Integration (Reactive List Loader)
|
|
114
|
+
Fetch data instantly via REST API on page load and compile templates dynamically:
|
|
74
115
|
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
```html
|
|
117
|
+
<!-- Fetches from GET /api/devices, compiles items inside #device-card-template -->
|
|
118
|
+
<div data-api-get="/api/devices" data-rt-template="#device-card-template">
|
|
119
|
+
<!-- Loading spinner placeholder: automatically removed when data arrives! -->
|
|
120
|
+
<div class="spinner">Loading devices...</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Browser-Native Template Tag (No quote-escaping or backticks needed!) -->
|
|
124
|
+
<template id="device-card-template">
|
|
125
|
+
<div class="device-card">
|
|
126
|
+
<h3>{{name}}</h3>
|
|
127
|
+
<span class="badge">{{status}}</span>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
77
130
|
```
|
|
78
131
|
|
|
79
|
-
###
|
|
132
|
+
### 3. Declarative Form Validation
|
|
133
|
+
Apply strong validation rules to inputs and display errors directly in the UI with absolutely **zero JavaScript**:
|
|
80
134
|
|
|
81
135
|
```html
|
|
82
|
-
<
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
136
|
+
<form id="login-form" data-api-submit="POST /api/login">
|
|
137
|
+
<div class="form-group">
|
|
138
|
+
<input name="email" data-validate="required,email" placeholder="Your Email" />
|
|
139
|
+
<!-- Automatically reads and displays validation error directly in the UI -->
|
|
140
|
+
<span class="error-msg" data-rt-bind="errors/email"></span>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="form-group">
|
|
144
|
+
<input name="password" type="password" data-validate="required,min:8" placeholder="Password" />
|
|
145
|
+
<span class="error-msg" data-rt-bind="errors/password"></span>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<button type="submit">Log In</button>
|
|
149
|
+
</form>
|
|
87
150
|
```
|
|
88
151
|
|
|
89
|
-
|
|
152
|
+
### 4. Global State & Declarative Store Actions (No-JS Actions!)
|
|
153
|
+
Manage local stores and run complex calculations, conditions, and toggles **directly in HTML attributes**:
|
|
90
154
|
|
|
91
|
-
### Pushing value changes
|
|
92
155
|
```html
|
|
93
|
-
|
|
156
|
+
<!-- 1. Auto-sync inputs directly into store key 'app.username' -->
|
|
157
|
+
<input data-store-write="app.username" placeholder="Type name..." />
|
|
158
|
+
<h3 data-store-read="app.username"></h3>
|
|
159
|
+
|
|
160
|
+
<!-- 2. Pure HTML Mathematical Counter -->
|
|
161
|
+
<div class="counter-box">
|
|
162
|
+
<div class="counter-value" data-store-read="app.count">0</div>
|
|
163
|
+
<!-- Updates state directly in HTML click. Dolphin updates the UI dynamically! -->
|
|
164
|
+
<button data-store-click="app.count = (app.count || 0) + 1">+</button>
|
|
165
|
+
<button data-store-click="app.count = (app.count || 0) - 1">-</button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- 3. Complex Calculations (e.g. Area & Billing) -->
|
|
169
|
+
<button data-store-click="
|
|
170
|
+
app.area = 3.14159 * (app.radius * app.radius);
|
|
171
|
+
app.circumference = 2 * 3.14159 * app.radius
|
|
172
|
+
">
|
|
173
|
+
Calculate Circle
|
|
174
|
+
</button>
|
|
175
|
+
|
|
176
|
+
<!-- 4. Logic Toggles (e.g. Dark Mode Toggle) -->
|
|
177
|
+
<button data-store-click="app.darkMode = !app.darkMode">Toggle Dark Mode</button>
|
|
94
178
|
```
|
|
95
179
|
|
|
96
|
-
###
|
|
180
|
+
### 5. Silent Zero-Configuration Auto-Initialization
|
|
181
|
+
When loaded via a standard `<script>` tag in browser environments, Dolphin Client automatically boots up a default client instance as `window.dolphin` on `DOMContentLoaded`.
|
|
182
|
+
|
|
183
|
+
For debugging, pass `data-debug="true"` on your script tag to turn on gorgeous, color-coded logging in your developer console for all API calls, WebSocket events, and Store updates:
|
|
97
184
|
```html
|
|
98
|
-
<
|
|
99
|
-
<div data-rt-type="context">
|
|
100
|
-
<h3>{{id}}</h3>
|
|
101
|
-
<button onclick="dialPeer('{{id}}')">Dial</button>
|
|
102
|
-
</div>
|
|
103
|
-
'></div>
|
|
185
|
+
<script src="js/dolphin-client.js" data-debug="true"></script>
|
|
104
186
|
```
|
|
105
187
|
|
|
188
|
+
---
|
|
189
|
+
|
|
106
190
|
## License
|
|
107
191
|
|
|
108
192
|
ISC License
|
package/dist/dolphin-client.js
CHANGED
|
@@ -108,6 +108,9 @@ var DolphinModule = (() => {
|
|
|
108
108
|
async requestDirect(method, path, body = null, options = {}) {
|
|
109
109
|
const _isRetry = options._isRetry === true;
|
|
110
110
|
const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
111
|
+
if (this.client.options.debug) {
|
|
112
|
+
console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
|
|
113
|
+
}
|
|
111
114
|
const controller = new AbortController();
|
|
112
115
|
const timeoutId = setTimeout(
|
|
113
116
|
() => controller.abort(),
|
|
@@ -140,6 +143,9 @@ var DolphinModule = (() => {
|
|
|
140
143
|
const contentType = response.headers.get("content-type") || "";
|
|
141
144
|
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
142
145
|
if (!response.ok) throw { status: response.status, data };
|
|
146
|
+
if (this.client.options.debug) {
|
|
147
|
+
console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
|
|
148
|
+
}
|
|
143
149
|
if (data && typeof data === "object") {
|
|
144
150
|
if (data.accessToken) {
|
|
145
151
|
this.client.setToken(data.accessToken);
|
|
@@ -153,6 +159,9 @@ var DolphinModule = (() => {
|
|
|
153
159
|
return data;
|
|
154
160
|
} catch (err) {
|
|
155
161
|
clearTimeout(timeoutId);
|
|
162
|
+
if (this.client.options.debug) {
|
|
163
|
+
console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
|
|
164
|
+
}
|
|
156
165
|
if (err.name === "AbortError") {
|
|
157
166
|
throw { status: 408, data: { error: "Request timed out" } };
|
|
158
167
|
}
|
|
@@ -437,6 +446,7 @@ var DolphinModule = (() => {
|
|
|
437
446
|
// 64 KB
|
|
438
447
|
maxReconnect: 5,
|
|
439
448
|
autoRefreshToken: true,
|
|
449
|
+
debug: false,
|
|
440
450
|
...options
|
|
441
451
|
};
|
|
442
452
|
this.socket = null;
|
|
@@ -519,6 +529,9 @@ var DolphinModule = (() => {
|
|
|
519
529
|
_handleMessage(data) {
|
|
520
530
|
try {
|
|
521
531
|
const msg = JSON.parse(data);
|
|
532
|
+
if (this.options.debug) {
|
|
533
|
+
console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
|
|
534
|
+
}
|
|
522
535
|
if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
|
|
523
536
|
if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
|
|
524
537
|
this.signalHandlers.forEach((h) => h(msg));
|
|
@@ -556,6 +569,9 @@ var DolphinModule = (() => {
|
|
|
556
569
|
}
|
|
557
570
|
/** @private */
|
|
558
571
|
_sendRaw(msg) {
|
|
572
|
+
if (this.options.debug) {
|
|
573
|
+
console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
|
|
574
|
+
}
|
|
559
575
|
const str = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
560
576
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
561
577
|
this.socket.send(str);
|
|
@@ -1013,6 +1029,9 @@ var DolphinModule = (() => {
|
|
|
1013
1029
|
}
|
|
1014
1030
|
const store = this.uiStores.get(storeName);
|
|
1015
1031
|
store[key] = val;
|
|
1032
|
+
if (this.options.debug) {
|
|
1033
|
+
console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
|
|
1034
|
+
}
|
|
1016
1035
|
if (typeof document !== "undefined") {
|
|
1017
1036
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
1018
1037
|
readElements.forEach((el) => {
|
|
@@ -1092,6 +1111,42 @@ var DolphinModule = (() => {
|
|
|
1092
1111
|
}
|
|
1093
1112
|
return null;
|
|
1094
1113
|
};
|
|
1114
|
+
clientProto._executeStoreAction = function(expression, element) {
|
|
1115
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1116
|
+
const context = new Proxy({}, {
|
|
1117
|
+
has: (target, prop) => {
|
|
1118
|
+
return true;
|
|
1119
|
+
},
|
|
1120
|
+
get: (target, prop) => {
|
|
1121
|
+
if (typeof prop === "string") {
|
|
1122
|
+
return new Proxy({}, {
|
|
1123
|
+
get: (subTarget, subProp) => {
|
|
1124
|
+
if (typeof subProp === "string") {
|
|
1125
|
+
return this.getStoreState(prop, subProp);
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
set: (subTarget, subProp, val) => {
|
|
1129
|
+
if (typeof subProp === "string") {
|
|
1130
|
+
this.setStoreState(prop, subProp, val);
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
try {
|
|
1140
|
+
const fn = new Function("ctx", `with(ctx) { ${expression} }`);
|
|
1141
|
+
fn(context);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
|
|
1144
|
+
if (element) {
|
|
1145
|
+
console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
|
|
1146
|
+
}
|
|
1147
|
+
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1095
1150
|
clientProto._initDOMBinding = function() {
|
|
1096
1151
|
if (this._domInitialized) return;
|
|
1097
1152
|
this._domInitialized = true;
|
|
@@ -1265,6 +1320,14 @@ var DolphinModule = (() => {
|
|
|
1265
1320
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1266
1321
|
}
|
|
1267
1322
|
}
|
|
1323
|
+
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
1324
|
+
if (storeActionBtn) {
|
|
1325
|
+
if (evtName === "submit") e.preventDefault();
|
|
1326
|
+
const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
|
|
1327
|
+
if (expr) {
|
|
1328
|
+
this._executeStoreAction(expr, storeActionBtn);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1268
1331
|
});
|
|
1269
1332
|
});
|
|
1270
1333
|
this.subscribe("#", (payload, topic) => {
|
|
@@ -2119,6 +2182,21 @@ var DolphinModule = (() => {
|
|
|
2119
2182
|
attachTesting(DolphinClient.prototype);
|
|
2120
2183
|
if (typeof window !== "undefined") {
|
|
2121
2184
|
window.DolphinClient = DolphinClient;
|
|
2185
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2186
|
+
if (!window.dolphin) {
|
|
2187
|
+
const scriptEl = document.querySelector('script[src*="dolphin-client"]');
|
|
2188
|
+
const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
|
|
2189
|
+
const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
|
|
2190
|
+
window.dolphin = dolphin;
|
|
2191
|
+
if (debugMode) {
|
|
2192
|
+
console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
|
|
2193
|
+
console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
|
|
2194
|
+
}
|
|
2195
|
+
if (document.querySelector('[data-store-write="app.username"]')) {
|
|
2196
|
+
dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2122
2200
|
}
|
|
2123
2201
|
return __toCommonJS(index_exports);
|
|
2124
2202
|
})();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var DolphinModule=(()=>{var $=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var J=(p,t)=>{for(var e in t)$(p,e,{get:t[e],enumerable:!0})},Q=(p,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!V.call(p,r)&&r!==e&&$(p,r,{get:()=>t[r],enumerable:!(n=z(t,r))||n.enumerable});return p};var X=p=>Q($({},"__esModule",{value:!0}),p);var Z={};J(Z,{DolphinClient:()=>v});var x=class{client;constructor(t){return this.client=t,this._createProxy([])}_createProxy(t){let e=t.join("/"),n=i=>this.request("GET",e,null,i);n.get=(i,a)=>typeof i=="string"?this.request("GET",i,null,a):this.request("GET",e,null,i),n.post=(i,a,l)=>typeof i=="string"?this.request("POST",i,a,l):this.request("POST",e,i,a),n.put=(i,a,l)=>typeof i=="string"?this.request("PUT",i,a,l):this.request("PUT",e,i,a),n.patch=(i,a,l)=>typeof i=="string"?this.request("PATCH",i,a,l):this.request("PATCH",e,i,a),n.del=(i,a)=>typeof i=="string"?this.request("DELETE",i,null,a):this.request("DELETE",e,null,i),n.request=(i,a,l,b)=>{let T=a?`${e}/${a.startsWith("/")?a.slice(1):a}`:e;return this.request(i,T,l,b)},n.requestDirect=(i,a,l,b)=>this.requestDirect(i,a,l,b);let r=["get","post","put","patch","del","request","requestDirect"];return new Proxy(n,{get:(i,a)=>typeof a=="string"&&!r.includes(a)?this._createProxy([...t,a]):i[a]})}async request(t,e,n=null,r={}){if(this.client.offline){let i=this.client.offline.isOnline,a=`${t.toUpperCase()}:${e}`;if(t.toUpperCase()==="GET")if(i)try{let l=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(a,l),l}catch(l){let b=await this.client.offline.getCache(a);if(b!=null)return b;throw l}else{let l=await this.client.offline.getCache(a);if(l!=null)return l;throw{status:503,data:{error:"Offline, no cache available"}}}else return i?this.requestDirect(t,e,n,r):(await this.client.offline.queueMutation(t,e,n),this.client._dispatch("offline:queued",{method:t,path:e,body:n}),{success:!0,offline:!0,message:"Mutation queued offline"})}return this.requestDirect(t,e,n,r)}async requestDirect(t,e,n=null,r={}){let i=r._isRetry===!0,a=`${this.client.httpUrl}${e.startsWith("/")?e:"/"+e}`;this.client.options.debug&&console.log("%c\u{1F680} [Dolphin API Request]:","color: #3b82f6; font-weight: bold;",t.toUpperCase(),e,n||"");let l=new AbortController,b=setTimeout(()=>l.abort(),this.client.options.timeout),T={"Content-Type":"application/json",...r.headers||{}};this.client.accessToken&&(T.Authorization=`Bearer ${this.client.accessToken}`);let h={...r};delete h._isRetry;try{let o=await fetch(a,{method:t,headers:T,signal:l.signal,...n?{body:JSON.stringify(n)}:{},...h});if(clearTimeout(b),o.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(o.headers.get("content-type")||"").includes("application/json")?await o.json():await o.text();if(!o.ok)throw{status:o.status,data:s};if(this.client.options.debug&&console.log("%c\u2705 [Dolphin API Success]:","color: #10b981; font-weight: bold;",t.toUpperCase(),e,s),s&&typeof s=="object"&&s.accessToken&&(this.client.setToken(s.accessToken),s.user&&(this.client.auth.user=s.user)),this.client.options.autoBroadcast&&["POST","PUT","PATCH","DELETE"].includes(t.toUpperCase())){let c=e.startsWith("/")?e.substring(1):e;this.client.publish(c,{method:t.toUpperCase(),payload:n,result:s})}return s}catch(o){throw clearTimeout(b),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,o),o.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:o}}};var C=class{client;user;_refreshing;constructor(t){this.client=t,this.user=null,this._refreshing=!1}async login(t,e){let n=await this.client.api.post("/api/auth/login",{email:t,password:e});return n.accessToken&&(this.client.setToken(n.accessToken),this.user=n.user||null),n}async register(t){return this.client.api.post("/api/auth/register",t)}async me(){let t=await this.client.api.get("/api/auth/me");return t.success&&(this.user=t.data),t}async logout(){try{await this.client.api.post("/api/auth/logout")}catch{}this.client.setToken(null),this.user=null}async refresh(){return this._silentRefresh()}async _silentRefresh(){if(this._refreshing)return!1;this._refreshing=!0;try{let t=await this.client.api.post("/api/auth/refresh",null,{_isRetry:!0});return t.accessToken?(this.client.setToken(t.accessToken),!0):!1}catch{return this.client.setToken(null),!1}finally{this._refreshing=!1}}async verify2FA(t,e){let n={code:t,email:e||this.user?.email},r=await this.client.api.post("/api/auth/2fa/verify",n);return r.accessToken&&(this.client.setToken(r.accessToken),r.user&&(this.user=r.user)),r}async enable2FA(){return this.client.api.post("/api/auth/2fa/enable")}async disable2FA(t){return this.client.api.post("/api/auth/2fa/disable",{code:t})}async forgotPassword(t){return this.client.api.post("/api/auth/forgot-password",{email:t})}async resetPassword(t,e){return this.client.api.post("/api/auth/reset-password",{token:t,newPassword:e})}};var M=class{client;data;listeners;subscribed;constructor(t){return this.client=t,this.data=new Map,this.listeners=new Set,this.subscribed=new Set,new Proxy(this,{get:(e,n)=>{if(n in e)return e[n];if(typeof n=="string")return this._getCollection(n)}})}_getCollection(t){if(!this.data.has(t)){let e={_rawItems:[],items:[],loading:!0,error:null,success:!1,_filter:null,_sort:null,where:n=>(e._filter=n,this._applyTransform(e),e),orderBy:(n,r="asc")=>(e._sort={key:n,direction:r},this._applyTransform(e),e),reset:()=>(e._filter=null,e._sort=null,this._applyTransform(e),e)};this.data.set(t,e),this._fetchAndSync(t)}return this.data.get(t)}async _fetchAndSync(t){let e=this.data.get(t);try{let n=await this.client.api.get(`/${t.toLowerCase()}`);e._rawItems=Array.isArray(n)?n:n.data||[],e.loading=!1,e.success=!0,e.error=null,this._applyTransform(e),this.subscribed.has(t)||(this.client.subscribe(`db:sync/${t.toLowerCase()}`,r=>{this._handleRemoteUpdate(t,r)}),this.subscribed.add(t))}catch(n){e.loading=!1,e.success=!1,e.error=n.data?.error||n.message||"Fetch failed",this._notify()}}_applyTransform(t){let e=[...t._rawItems];if(t._filter&&(e=e.filter(t._filter)),t._sort){let{key:n,direction:r}=t._sort;e.sort((i,a)=>i[n]===a[n]?0:(i[n]>a[n]?1:-1)*(r==="asc"?1:-1))}t.items=e,this._notify()}_handleRemoteUpdate(t,e){let n=this.data.get(t);if(!n)return;let{type:r,data:i}=e,a=n._rawItems;r==="create"?a=[...a,i]:r==="update"?a=a.map(l=>l.id===i.id||l._id===i._id?{...l,...i}:l):r==="delete"&&(a=a.filter(l=>!(i.id!=null&&l.id===i.id||i._id!=null&&l._id===i._id))),n._rawItems=a,this._applyTransform(n)}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(t){return this.data.get(t)||{items:[],loading:!1,error:null,success:!1}}_notify(){this.listeners.forEach(t=>t())}};var v=class{host;httpUrl;deviceId;options;socket;storage;accessToken;api;auth;store;handlers;signalHandlers;fileHandlers;_offlineQueue;reconnectAttempts;_attachedListeners;constructor(t="",e="",n={}){!t&&typeof window<"u"&&(t=window.location.host);let r="http:";t.startsWith("https://")?r="https:":t.startsWith("http://")?r="http:":typeof window<"u"&&(r=window.location.protocol),this.host=(t||"localhost").replace(/\/$/,"").replace(/^https?:\/\//,""),this.httpUrl=`${r}//${this.host}`,this.deviceId=e||"web_"+Math.random().toString(36).substr(2,8),this.options={timeout:15e3,chunkSize:65536,maxReconnect:5,autoRefreshToken:!0,debug:!1,...n},this.socket=null,this.storage=typeof localStorage<"u"?localStorage:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}},this.accessToken=this.storage.getItem("dolphin_token"),this.api=new x(this),this.auth=new C(this),this.store=new M(this),this.handlers=new Map,this.signalHandlers=new Set,this.fileHandlers=new Set,this._offlineQueue=[],this.reconnectAttempts=0,this._attachedListeners=[],typeof window<"u"&&typeof this._initDOMBinding=="function"&&this._initDOMBinding(),typeof this._initOffline=="function"&&this._initOffline(),typeof this._initA11y=="function"&&this._initA11y(),typeof this._initI18n=="function"&&this._initI18n(),typeof this._initDragDrop=="function"&&this._initDragDrop(),typeof this._initCollab=="function"&&this._initCollab()}setToken(t){this.accessToken=t,t?this.storage.setItem("dolphin_token",t):this.storage.removeItem("dolphin_token")}async connect(){return new Promise((t,e)=>{let r=`${this.httpUrl.startsWith("https")?"wss:":"ws:"}//${this.host}/realtime?deviceId=${this.deviceId}`;console.log(`[Dolphin] Connecting to ${r}...`),this.socket=new WebSocket(r),this.socket.onopen=()=>{console.log(`[Dolphin] Connected as "${this.deviceId}" \u{1F42C}`),this.reconnectAttempts=0,this._flushOfflineQueue(),t()},this.socket.onmessage=i=>this._handleMessage(i.data),this.socket.onclose=()=>{console.warn("[Dolphin] Connection closed"),this._maybeReconnect()},this.socket.onerror=i=>{console.error("[Dolphin] WebSocket error:",i),e(i)}})}disconnect(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null),this.cleanupDomListeners()}_handleMessage(t){try{let e=JSON.parse(t);this.options.debug&&console.log("%c\u{1F4E5} [Dolphin WS Incoming]:","color: #eab308; font-weight: bold;",e),e.type&&e.from&&(e.to===this.deviceId||e.to==="all")&&(e.msgId&&e.type!=="ACK"&&this._sendAck(e.from,e.msgId),this.signalHandlers.forEach(n=>n(e))),e.type==="FILE_AVAILABLE"&&this.fileHandlers.forEach(n=>n(e)),e.type==="FILE_CHUNK"&&(this.saveFileProgress(e.fileId,e.chunkIndex),this._dispatch("file:chunk",e),this._dispatch(`file:chunk/${e.fileId}`,e)),e.type==="FILE_UPLOAD_ACK"&&this._dispatch(`file:upload:ack/${e.fileId}`,e),e.type==="PULL_RESPONSE"&&(this._dispatch("pull:response",e.payload,e.topic),this._dispatch(`pull:response/${e.topic}`,e.payload,e.topic)),e.topic&&e.payload!==void 0&&this.handlers.forEach((n,r)=>{this._matchTopic(r,e.topic)&&n.forEach(i=>i(e.payload,e.topic))})}catch{this._dispatch("raw",t)}}_dispatch(t,e,n){let r=this.handlers.get(t);r&&r.forEach(i=>i(e,n||t))}_sendRaw(t){this.options.debug&&console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:","color: #8b5cf6; font-weight: bold;",t);let e=typeof t=="string"?t:JSON.stringify(t);this.socket&&this.socket.readyState===WebSocket.OPEN?this.socket.send(e):this._offlineQueue.length<100&&this._offlineQueue.push(e)}_flushOfflineQueue(){for(;this._offlineQueue.length>0;){let t=this._offlineQueue.shift();this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(t)}}_sendAck(t,e){this._sendRaw({type:"ACK",from:this.deviceId,to:t,data:{ackId:e},timestamp:Date.now()})}_matchTopic(t,e){if(t===e||t==="#")return!0;let n=t.split("/"),r=e.split("/");if(n.length!==r.length&&!t.includes("#"))return!1;for(let i=0;i<n.length;i++){if(n[i]==="#")return!0;if(n[i]!=="+"&&n[i]!==r[i])return!1}return n.length===r.length}_maybeReconnect(){if(this.reconnectAttempts<this.options.maxReconnect){this.reconnectAttempts++;let t=Math.pow(2,this.reconnectAttempts)*1e3;console.log(`[Dolphin] Reconnecting in ${t/1e3}s (attempt ${this.reconnectAttempts})...`),setTimeout(()=>this.connect().catch(()=>{}),t)}else console.error("[Dolphin] Max reconnect attempts reached.")}subscribe(t,e){this.handlers.has(t)||(this.handlers.set(t,new Set),this._sendRaw({type:"sub",topic:t})),this.handlers.get(t).add(e)}unsubscribe(t,e){if(this.handlers.has(t)){let n=this.handlers.get(t);n.delete(e),n.size===0&&(this.handlers.delete(t),this._sendRaw({type:"unsub",topic:t}))}}publish(t,e){this._sendRaw({topic:t,payload:e})}pubPush(t,e){this._sendRaw({type:"pub",topic:t,payload:e})}subPull(t,e=10){this._sendRaw({type:"PULL_REQUEST",topic:t,count:e})}async pubFile(t,e,n="",r){let i;e instanceof Blob?i=await e.arrayBuffer():e instanceof ArrayBuffer?i=e:i=e.buffer||e;let a=new Uint8Array(i),l=this.options.chunkSize,b=Math.ceil(a.length/l);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:a.length,totalChunks:b,chunkSize:l});for(let T=0;T<b;T++){let h=a.slice(T*l,(T+1)*l),o=this._uint8ToBase64(h);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:T,totalChunks:b,data:o}),r&&r(Math.round((T+1)/b*100)),T%10===0&&await new Promise(u=>setTimeout(u,0))}this._sendRaw({type:"FILE_UPLOAD_DONE",fileId:t})}_uint8ToBase64(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return typeof btoa<"u"?btoa(e):Buffer.from(e,"binary").toString("base64")}subFile(t,e=0){this._sendRaw({type:"FILE_REQUEST",fileId:t,startChunk:e})}resumeFile(t){let e=parseInt(this.storage.getItem(`dolphin_file_${t}`)||"-1");this.subFile(t,e+1)}saveFileProgress(t,e){this.storage.setItem(`dolphin_file_${t}`,e.toString())}onSignal(t){this.signalHandlers.add(t)}offSignal(t){this.signalHandlers.delete(t)}onFileAvailable(t){this.fileHandlers.add(t)}offFileAvailable(t){this.fileHandlers.delete(t)}addDomListener(t,e,n){t&&(t.addEventListener(e,n),this._attachedListeners=this._attachedListeners||[],this._attachedListeners.push({target:t,event:e,cb:n}))}cleanupDomListeners(){this._attachedListeners&&(this._attachedListeners.forEach(({target:t,event:e,cb:n})=>{try{t.removeEventListener(e,n)}catch{}}),this._attachedListeners=[])}};function R(p){function t(h){return h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(h){let o=h.getAttribute("data-rt-template");if(!o)return null;if(typeof document<"u"&&!o.includes("<"))try{let u=document.querySelector(o);if(u)return u.innerHTML}catch{}return o}function n(h,o){if(!h.includes("{#if")&&!h.includes("{#each")){let u=h;for(let s in o){let c=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");u=u.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),o[s]!==void 0&&o[s]!==null?o[s]:"")}return u}try{let u=g=>g.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
|
|
2
|
+
`,c=0,m=/(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g,f=[],d;for(;(d=m.exec(h))!==null;){let g=h.slice(c,d.index);g&&(s+=`out += "${u(g)}";
|
|
3
|
+
`);let w=d[0];if(w.startsWith("{{")){let A=d[2];s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
|
|
4
|
+
`}else if(w.startsWith("{#if")){let A=d[3];s+=`if (${A}) {
|
|
5
|
+
`}else if(w.startsWith("{:else if")){let A=d[4];s+=`} else if (${A}) {
|
|
6
|
+
`}else if(w.startsWith("{:else}"))s+=`} else {
|
|
7
|
+
`;else if(w.startsWith("{/if}"))s+=`}
|
|
8
|
+
`;else if(w.startsWith("{#each")){let A=d[5],D=d[6],S=d[7];f.push({indexVar:S}),s+=`if (typeof ${A} !== "undefined" && ${A} !== null && Array.isArray(${A})) {
|
|
9
|
+
`,S&&(s+=` let ${S} = 0;
|
|
10
|
+
`),s+=` for (let ${D} of ${A}) {
|
|
11
|
+
`}else if(w.startsWith("{/each}")){let A=f.pop();A&&A.indexVar&&(s+=` ${A.indexVar}++;
|
|
12
|
+
`),s+=` }
|
|
13
|
+
}
|
|
14
|
+
`}else if(w.startsWith("{")){let A=d[8];A&&(s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
|
|
15
|
+
`)}c=m.lastIndex}let y=h.slice(c);y&&(s+=`out += "${u(y)}";
|
|
16
|
+
`),s+=`return out;
|
|
17
|
+
`;let _=`
|
|
18
|
+
with (context) {
|
|
19
|
+
try {
|
|
20
|
+
${s}
|
|
21
|
+
} catch (innerErr) {
|
|
22
|
+
console.warn('[Dolphin Template Eval Warning]:', innerErr);
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;return new Function("context",_)(o)}catch(u){console.error("[Dolphin Template Compiler Error]:",u);let s=h;for(let c in o){let m=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");s=s.replace(new RegExp(`\\{\\{${m}\\}\\}`,"g"),o[c]!==void 0&&o[c]!==null?o[c]:"")}return s}}function r(h){if(typeof document>"u")return h;try{let s=new DOMParser().parseFromString(h,"text/html").body,c=m=>{let f=m.tagName.toLowerCase();if(["script","iframe","object","embed","link","style","meta","applet","svg"].includes(f)){m.parentNode?.removeChild(m);return}let d=m.attributes;for(let y=d.length-1;y>=0;y--){let _=d[y].name.toLowerCase(),E=d[y].value.toLowerCase();(_.startsWith("on")||["src","href","data"].includes(_)&&(E.includes("javascript:")||E.includes("data:text/html")))&&m.removeAttribute(d[y].name)}Array.from(m.children).forEach(c)};return Array.from(s.children).forEach(c),s.innerHTML}catch{return h}}function i(h,o){if(h.nodeType!==o.nodeType){h.parentNode?.replaceChild(o.cloneNode(!0),h);return}if(h.nodeType===Node.TEXT_NODE){h.textContent!==o.textContent&&(h.textContent=o.textContent);return}if(h.nodeType===Node.ELEMENT_NODE){let u=h,s=o;if(u.tagName!==s.tagName){u.parentNode?.replaceChild(s.cloneNode(!0),u);return}let c=u.attributes,m=s.attributes;for(let g=c.length-1;g>=0;g--){let w=c[g].name;s.hasAttribute(w)||u.removeAttribute(w)}for(let g=0;g<m.length;g++){let w=m[g].name,A=m[g].value;u.getAttribute(w)!==A&&u.setAttribute(w,A)}u.tagName==="INPUT"||u.tagName==="TEXTAREA"?(u.value!==s.value&&(u.value=s.value),u.checked!==s.checked&&(u.checked=s.checked)):u.tagName==="SELECT"&&u.value!==s.value&&(u.value=s.value);let f=Array.from(u.childNodes),d=Array.from(s.childNodes),y=f.length,_=d.length,E=Math.max(y,_);for(let g=0;g<E;g++)g>=y?u.appendChild(d[g].cloneNode(!0)):g>=_?u.removeChild(f[g]):i(f[g],d[g])}}function a(h,o){if(typeof document>"u")return;let u=document.createElement(h.tagName);u.innerHTML=o;let s=Array.from(h.childNodes),c=Array.from(u.childNodes),m=s.length,f=c.length,d=Math.max(m,f);for(let y=0;y<d;y++)y>=m?h.appendChild(c[y].cloneNode(!0)):y>=f?h.removeChild(s[y]):i(s[y],c[y])}let l=new Map,b=!1;function T(h,o){l.set(h,o),b||(b=!0,(typeof requestAnimationFrame<"u"?requestAnimationFrame:s=>setTimeout(s,0))(()=>{l.forEach((s,c)=>{a(c,s)}),l.clear(),b=!1}))}p.setStoreState=function(h,o,u){this.uiStores=this.uiStores||new Map,this.uiStores.has(h)||this.uiStores.set(h,{});let s=this.uiStores.get(h);s[o]=u,this.options.debug&&console.log("%c\u{1F4BE} [Dolphin Store Update]:","color: #ec4899; font-weight: bold;",`${h}.${o}`,"=",u),typeof document<"u"&&document.querySelectorAll(`[data-store-read="${h}.${o}"]`).forEach(m=>{m.tagName==="INPUT"||m.tagName==="TEXTAREA"?m.type==="checkbox"?m.checked=!!u:m.value=u??"":m.textContent=u??""}),this.publish(`store/${h}`,s)},p.getStoreState=function(h,o){this.uiStores=this.uiStores||new Map;let u=this.uiStores.get(h);return u?u[o]:void 0},p._scanStoreBinds=function(){if(typeof document>"u")return;document.querySelectorAll("[data-store-write]").forEach(u=>{let s=u.getAttribute("data-store-write");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=u.type==="checkbox"?u.checked:u.value;this.uiStores=this.uiStores||new Map,this.uiStores.has(m)||this.uiStores.set(m,{});let y=this.uiStores.get(m);y[f]===void 0&&(y[f]=d)}}}),document.querySelectorAll("[data-store-read]").forEach(u=>{let s=u.getAttribute("data-store-read");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=this.getStoreState(m,f);d!=null&&(u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u.type==="checkbox"?u.checked=!!d:u.value=d:u.textContent=d)}}})},p.getClosestContext=function(h,o){let u=h;for(;u;){if(u._rtContext){let s=u._rtContext;return o?s[o]:s}u=u.parentElement}return null},p._executeStoreAction=function(h,o){this.uiStores=this.uiStores||new Map;let u=new Proxy({},{has:(s,c)=>!0,get:(s,c)=>{if(typeof c=="string")return new Proxy({},{get:(m,f)=>{if(typeof f=="string")return this.getStoreState(c,f)},set:(m,f,d)=>typeof f=="string"?(this.setStoreState(c,f,d),!0):!1})}});try{new Function("ctx",`with(ctx) { ${h} }`)(u)}catch(s){console.error("%c[Dolphin Store Action Error]:","color: #ef4444; font-weight: bold;",s),o&&console.error("%cFailed Element:","color: #f97316; font-weight: bold;",o),console.error("%cFailed Expression:","color: #3b82f6; font-style: italic;",h)}},p._initDOMBinding=function(){if(this._domInitialized)return;this._domInitialized=!0;let h=["input","change","keyup","paste","blur"],o=new Map;h.forEach(s=>{this.addDomListener(document,s,c=>{if(!c.target||!c.target.getAttribute)return;let m=c.target.getAttribute("data-store-write");if(m){let _=m.split(".");if(_.length===2){let E=_[0],g=_[1],w=c.target.type==="checkbox"?c.target.checked:c.target.value;this.setStoreState(E,g,w)}}let f=c.target.getAttribute("data-rt-validate"),d=c.target.name;if(f&&d&&typeof this.validateField=="function"){let _=c.target.closest("form"),E=_?Object.fromEntries(new FormData(_).entries()):{},g=this.validateField(c.target.value,f,E);g?(c.target.classList.add("invalid"),this.publish(`errors/${d}`,g)):(c.target.classList.remove("invalid"),this.publish(`errors/${d}`,""))}let y=c.target.getAttribute("data-rt-push");if(y){let _=c.target.getAttribute("data-rt-debounce"),E=_?parseInt(_,10):0,g=()=>{let w={name:c.target.name,value:c.target.value};this.pubPush(y,w)};if(E>0){o.has(c.target)&&clearTimeout(o.get(c.target));let w=setTimeout(g,E);o.set(c.target,w)}else g()}})}),this.addDomListener(document,"submit",async s=>{if(!s.target||!s.target.getAttribute)return;let c=s.target.getAttribute("data-rt-submit"),m=s.target.getAttribute("data-api-submit");if(c||m){let f=s.target.querySelectorAll("[data-rt-validate]"),d=!0;if(f.length>0&&typeof this.validateField=="function"){let g=Object.fromEntries(new FormData(s.target).entries());f.forEach(w=>{let A=w.getAttribute("data-rt-validate"),D=w.name;if(A&&D){let S=this.validateField(w.value,A,g);S?(d=!1,w.classList.add("invalid"),this.publish(`errors/${D}`,S)):(w.classList.remove("invalid"),this.publish(`errors/${D}`,""))}})}if(!d){s.preventDefault(),s.stopPropagation();return}s.preventDefault();let y=this.getClosestContext(s.target)||{},_=new FormData(s.target),E=Object.fromEntries(_.entries());if(c){let g=c;for(let w in y){let A=t(w);g=g.replace(new RegExp(`\\{\\{${A}\\}\\}`,"g"),y[w]!==void 0&&y[w]!==null?y[w]:"")}this.publish(g,E)}else if(m){let g=m;for(let S in y){let k=t(S);g=g.replace(new RegExp(`\\{\\{${k}\\}\\}`,"g"),y[S]!==void 0&&y[S]!==null?y[S]:"")}let w=g.trim().split(" "),A=w.length>1?w[0].toUpperCase():"POST",D=w.length>1?w[1]:w[0];try{let S=await this.api.request(A,D,E),k=s.target.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=s.target.getAttribute("data-api-redirect");L&&(window.location.href=L),s.target.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error("[Dolphin] API Submit Error:",S)}}}}),["click","change","submit","input","keydown","keyup","dblclick","focus","blur","mouseenter","mouseleave"].forEach(s=>{this.addDomListener(document,s,async c=>{if(!c.target||!c.target.closest)return;let m=c.target.closest(`[data-rt-${s}]`),f=c.target.closest(`[data-api-${s}]`);if(m){s==="submit"&&c.preventDefault();let y=m.getAttribute(`data-rt-${s}`),_=m.getAttribute("data-rt-payload"),E=this.getClosestContext(m)||{},g={};if(_){let w=_;for(let A in E){let D=t(A);w=w.replace(new RegExp(`\\{\\{${D}\\}\\}`,"g"),E[A]!==void 0&&E[A]!==null?E[A]:"")}try{g=JSON.parse(w)}catch{g={}}}this.publish(y,g)}if(f){s==="submit"&&c.preventDefault();let y=f.getAttribute(`data-api-${s}`),_=f.getAttribute("data-api-payload"),E=this.getClosestContext(f)||{},g=y.trim().split(" "),w=g.length>1?g[0].toUpperCase():"POST",A=g.length>1?g[1]:g[0],D=null;if(_){let S=_;for(let k in E){let L=t(k);S=S.replace(new RegExp(`\\{\\{${L}\\}\\}`,"g"),E[k]!==void 0&&E[k]!==null?E[k]:"")}try{D=JSON.parse(S)}catch{D=null}}try{let S=await this.api.request(w,A,D),k=f.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=f.getAttribute("data-api-redirect");L&&(window.location.href=L),f.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error(`[Dolphin] API ${s} Error:`,S)}}let d=c.target.closest(`[data-store-${s}]`);if(d){s==="submit"&&c.preventDefault();let y=d.getAttribute(`data-store-${s}`);y&&this._executeStoreAction(y,d)}})}),this.subscribe("#",(s,c)=>{this._updateDOM(c,s)}),this._scanAndFetchAPIBinds(),this._scanStoreBinds()},p._scanAndFetchAPIBinds=async function(){if(typeof document>"u")return;let h=document.querySelectorAll("[data-api-get]");for(let o of Array.from(h)){let u=o.getAttribute("data-api-get");if(u)try{let s=await this.api.get(u),c=o.getAttribute("data-rt-bind");if(c)this._updateDOM(c,s);else{let m=e(o);if(m&&typeof s=="object"&&s!==null)if(Array.isArray(s)){let f="";for(let d of s)f+=n(m,d);T(o,f)}else T(o,n(m,s));else o.tagName==="INPUT"||o.tagName==="TEXTAREA"?o.value=typeof s=="object"?s.value!==void 0?s.value:"":s:o.innerHTML=typeof s=="object"?s.html||s.text||JSON.stringify(s):s}}catch(s){console.error("[Dolphin] API Get Error:",s)}}},p._updateDOM=function(h,o){if(typeof document>"u")return;document.querySelectorAll(`[data-rt-bind="${h}"]`).forEach(s=>{if(s.getAttribute("data-rt-type")==="context"&&typeof o=="object"&&o!==null){s._rtContext=o;let m=f=>{if(f.hasAttribute("data-rt-text")){let d=f.getAttribute("data-rt-text");d&&o[d]!==void 0&&o[d]!==null&&(f.textContent=o[d])}if(f.hasAttribute("data-rt-html")){let d=f.getAttribute("data-rt-html");d&&o[d]!==void 0&&o[d]!==null&&(f.innerHTML=r(o[d]))}if(f.hasAttribute("data-rt-attr")){let d=f.getAttribute("data-rt-attr");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();E&&g&&o[g]!==void 0&&o[g]!==null&&f.setAttribute(E,o[g])}})}if(f.hasAttribute("data-rt-class")){let d=f.getAttribute("data-rt-class");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();o[g]?f.classList.add(E):f.classList.remove(E)}})}if(f.hasAttribute("data-rt-if")){let d=f.getAttribute("data-rt-if");d&&(o[d]?f.style.display="":f.style.display="none")}if(f.hasAttribute("data-rt-hide")){let d=f.getAttribute("data-rt-hide");d&&(o[d]?f.style.display="none":f.style.display="")}};m(s),s.querySelectorAll("[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]").forEach(m);return}let c=e(s);if(c&&typeof o=="object"&&o!==null){if(Array.isArray(o)){let m="";for(let f of o)m+=n(c,f);T(s,m)}else T(s,n(c,o));return}s.tagName==="INPUT"||s.tagName==="TEXTAREA"?s.value=typeof o=="object"?o.value!==void 0?o.value:"":o:s.innerHTML=typeof o=="object"?o.html||o.text||JSON.stringify(o):o})}}var I=class{client;db;isOnline;memoryCache=new Map;memoryMutations=[];constructor(t){this.client=t,this.isOnline=typeof window<"u"&&typeof navigator<"u"?navigator.onLine:!0,this.initDB(),this.setupNetworkListeners()}initDB(){if(!(typeof indexedDB>"u"))try{let t=indexedDB.open("dolphin_offline",1);t.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains("cache")||n.createObjectStore("cache"),n.objectStoreNames.contains("mutations")||n.createObjectStore("mutations",{keyPath:"id",autoIncrement:!0})},t.onsuccess=e=>{this.db=e.target.result,this.isOnline&&this.syncMutations()}}catch(t){console.warn("[Dolphin Offline] Failed to initialize IndexedDB:",t)}}setupNetworkListeners(){typeof window>"u"||(this.client.addDomListener(window,"online",()=>{this.isOnline=!0,this.client._dispatch("network:status",{online:!0}),this.syncMutations()}),this.client.addDomListener(window,"offline",()=>{this.isOnline=!1,this.client._dispatch("network:status",{online:!1})}))}async getCache(t){return this.db?new Promise(e=>{try{let i=this.db.transaction("cache","readonly").objectStore("cache").get(t);i.onsuccess=()=>e(i.result?i.result.data:null),i.onerror=()=>e(null)}catch{e(null)}}):this.memoryCache.get(t)}async setCache(t,e){if(!this.db){this.memoryCache.set(t,e);return}return new Promise(n=>{try{let r=this.db.transaction("cache","readwrite");r.objectStore("cache").put({data:e,timestamp:Date.now()},t),r.oncomplete=()=>n()}catch{n()}})}async queueMutation(t,e,n){let r={method:t,path:e,payload:n,timestamp:Date.now()};if(!this.db){this.memoryMutations.push(r);return}return new Promise(i=>{try{let a=this.db.transaction("mutations","readwrite");a.objectStore("mutations").add(r),a.oncomplete=()=>i()}catch{i()}})}async getMutations(){return this.db?new Promise(t=>{try{let r=this.db.transaction("mutations","readonly").objectStore("mutations").getAll();r.onsuccess=()=>t(r.result||[]),r.onerror=()=>t([])}catch{t([])}}):[...this.memoryMutations]}async removeMutation(t){if(!this.db){this.memoryMutations=this.memoryMutations.filter(e=>e.id!==t);return}return new Promise(e=>{try{let n=this.db.transaction("mutations","readwrite");n.objectStore("mutations").delete(t),n.oncomplete=()=>e()}catch{e()}})}async syncMutations(){let t=await this.getMutations();if(t.length!==0){console.log(`[Dolphin Offline] Syncing ${t.length} queued mutations...`);for(let e of t)try{await this.client.api.requestDirect(e.method,e.path,e.payload),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift()}catch(n){if(console.error(`[Dolphin Offline] Sync failed for mutation ${e.method} ${e.path}:`,n),n&&n.status&&n.status>=400&&n.status<500)console.warn("[Dolphin Offline] Discarding invalid mutation."),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift();else break}}}};function q(p){p._initOffline=function(){this.offline=new I(this)}}function Y(p,t,e){let n=t.split(",");for(let r of n){let i=r.trim().split(":"),a=i[0],l=i[1];if(a==="required"){if(!p||p.trim()==="")return"This field is required"}else if(a==="email"){if(p&&p.trim()!==""&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p))return"Please enter a valid email address"}else if(a==="min"){let b=parseInt(l,10);if(!p||p.length<b)return`Must be at least ${b} characters`}else if(a==="match"&&e&&p!==e[l])return`Must match ${l}`}return null}function O(p){p.validateField=Y}function N(p){p.animateElement=function(t,e,n=300){if(typeof t.animate!="function"){t.classList.add(e),setTimeout(()=>t.classList.remove(e),n);return}e==="fade-in"?t.animate([{opacity:0,transform:"translateY(10px)"},{opacity:1,transform:"translateY(0)"}],{duration:n,easing:"ease-out"}):e==="fade-out"&&t.animate([{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(10px)"}],{duration:n,easing:"ease-in"})},p.staggerListItems=function(t,e,n=50){if(typeof document>"u")return;t.querySelectorAll(e).forEach((i,a)=>{i.style.animationDelay=`${a*n}ms`,i.classList.add("staggered-item")})}}function U(p){p._initA11y=function(){typeof document>"u"||(this.addDomListener(document,"keydown",t=>{if(t.key!=="Tab")return;document.querySelectorAll("[data-rt-a11y-focus-trap]").forEach(n=>{if(n.style.display==="none"||n.hasAttribute("aria-hidden")&&n.getAttribute("aria-hidden")==="true")return;let i=Array.from(n.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]'));if(i.length===0)return;let a=i[0],l=i[i.length-1];t.shiftKey?document.activeElement===a&&(l.focus(),t.preventDefault()):document.activeElement===l&&(a.focus(),t.preventDefault())})}),this.addDomListener(document,"keydown",t=>{if(!["ArrowUp","ArrowDown","Enter"].includes(t.key))return;document.querySelectorAll("[data-rt-keynav]").forEach(n=>{let r=Array.from(n.children);if(r.length===0)return;let i=r.findIndex(a=>a.classList.contains("active")||document.activeElement===a);t.key==="ArrowDown"?(i=(i+1)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="ArrowUp"?(i=(i-1+r.length)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="Enter"&&i!==-1&&(r[i].click(),t.preventDefault())})}))},p.autoAriaModal=function(t,e){e?(t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-hidden","false"),t.focus()):t.setAttribute("aria-hidden","true")}}function F(p){p._initI18n=function(){if(this.i18n=this.i18n||{locale:"en",dicts:{}},typeof document>"u")return;if(document.querySelectorAll("[data-i18n-dict]").forEach(e=>{let n=e.getAttribute("data-i18n-dict");if(n)try{let r=JSON.parse(e.textContent||"{}");this.i18n.dicts[n]={...this.i18n.dicts[n]||{},...r}}catch(r){console.warn("[Dolphin i18n] Failed to parse dictionary for locale:",n,r)}}),!this.i18n.locale&&typeof navigator<"u"){let e=navigator.language.split("-")[0];this.i18n.dicts[e]&&(this.i18n.locale=e)}this.addDomListener(document,"click",e=>{let n=e.target.closest("[data-i18n-switch]");if(n){let r=n.getAttribute("data-i18n-switch");r&&this.setLocale(r)}}),this.translateDOM()},p.setLocale=function(t){this.i18n=this.i18n||{locale:"en",dicts:{}},this.i18n.locale=t,this.translateDOM(),this.publish("i18n/locale",t)},p.translateDOM=function(){if(typeof document>"u")return;this.i18n=this.i18n||{locale:"en",dicts:{}};let t=this.i18n.locale||"en",e=this.i18n.dicts[t]||{};document.querySelectorAll("[data-i18n-key]").forEach(r=>{let i=r.getAttribute("data-i18n-key");if(!i)return;let a=i.split(".").reduce((b,T)=>b?b[T]:null,e);a==null&&(a=i);let l=r.getAttribute("data-i18n-params");if(l)try{let b=JSON.parse(l),T=h=>h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");for(let h in b){let o=T(h);a=a.replace(new RegExp(`\\{\\{${o}\\}\\}`,"g"),b[h])}}catch{}r.tagName==="INPUT"||r.tagName==="TEXTAREA"?r.placeholder=a:r.textContent=a})}}function H(p){p._initDragDrop=function(){typeof document>"u"||(this.addDomListener(document,"dragstart",t=>{let e=t.target.closest("[data-drag]");if(!e)return;let n=e.getAttribute("data-drag");n&&(t.dataTransfer.setData("text/plain",n),t.dataTransfer.effectAllowed="move",e.classList.add("dragging"))}),this.addDomListener(document,"dragend",t=>{let e=t.target.closest("[data-drag]");e&&e.classList.remove("dragging")}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-drop]");e&&(t.preventDefault(),e.classList.add("drag-over"))}),this.addDomListener(document,"dragleave",t=>{let e=t.target.closest("[data-drop]");e&&e.classList.remove("drag-over")}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-drop]");if(!e)return;t.preventDefault(),e.classList.remove("drag-over");let n=e.getAttribute("data-drop"),r=t.dataTransfer.getData("text/plain");if(n&&r)try{let i=JSON.parse(r);this.publish(n,i)}catch{this.publish(n,{value:r})}}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;t.preventDefault();let n=e.querySelector(".dragging");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]:not(.dragging)")).find(a=>{let l=a.getBoundingClientRect();return t.clientY-l.top-l.height/2<0});i?e.insertBefore(n,i):e.appendChild(n)}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;let n=e.getAttribute("data-sortable");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]")).map((a,l)=>{let b=a.getAttribute("data-drag");try{return{index:l,payload:JSON.parse(b||"{}")}}catch{return{index:l,payload:b}}});this.publish(n,i)}))}}function W(p){p._initCollab=function(){typeof document>"u"||(this.addDomListener(document,"mousemove",t=>{document.querySelectorAll("[data-rt-cursor-share]").forEach(n=>{let r=n.getAttribute("data-rt-cursor-share");if(!r)return;let i=n.getBoundingClientRect(),a=(t.clientX-i.left)/i.width,l=(t.clientY-i.top)/i.height,b=Date.now();(!n._lastSent||b-n._lastSent>50)&&(n._lastSent=b,this.pubPush(`collab/${r}/cursor/${this.deviceId}`,{deviceId:this.deviceId,x:a,y:l}))})}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-typing");if(!e)return;let n=e,r=i=>{this.pubPush(`collab/${n}/typing/${this.deviceId}`,{deviceId:this.deviceId,typing:i})};t.target._isTyping||(t.target._isTyping=!0,r(!0)),t.target._typingTimer&&clearTimeout(t.target._typingTimer),t.target._typingTimer=setTimeout(()=>{t.target._isTyping=!1,r(!1)},2e3)}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-crdt");if(!e)return;let n=e,r=t.target.value,i=Date.now();this.publish(`collab/${n}/crdt`,{deviceId:this.deviceId,value:r,timestamp:i,cursorPos:t.target.selectionStart})}),this.subscribe("collab/+/cursor/+",(t,e)=>{let n=e.split("/"),r=n[1],i=n[3];if(i===this.deviceId)return;let a=document.querySelector(`[data-rt-cursor-share="${r}"]`);if(!a)return;let l=a.querySelector(`.rt-cursor-${i}`);l||(l=document.createElement("div"),l.className=`rt-cursor rt-cursor-${i}`,l.style.position="absolute",l.style.width="10px",l.style.height="10px",l.style.borderRadius="50%",l.style.backgroundColor="#"+Math.floor(Math.random()*16777215).toString(16),l.style.pointerEvents="none",a.appendChild(l));let b=a.getBoundingClientRect();l.style.left=t.x*b.width+"px",l.style.top=t.y*b.height+"px"}),this.subscribe("collab/+/crdt",(t,e)=>{if(t.deviceId===this.deviceId)return;let r=e.split("/")[1];document.querySelectorAll(`[data-rt-crdt="${r}"]`).forEach(a=>{if(!a._lastUpdate||t.timestamp>a._lastUpdate){a._lastUpdate=t.timestamp;let l=a.selectionStart;a.value=t.value,document.activeElement===a&&a.setSelectionRange(l,l)}})}))}}function j(p){p.registerServiceWorker=async function(t="/sw.js"){if(typeof navigator>"u"||!("serviceWorker"in navigator))return console.warn("[Dolphin PWA] Service Workers are not supported in this browser."),null;try{let e=await navigator.serviceWorker.register(t);return console.log("[Dolphin PWA] Service Worker registered successfully with scope:",e.scope),e}catch(e){return console.error("[Dolphin PWA] Service Worker registration failed:",e),null}},p.subscribePushNotifications=async function(t){if(typeof window>"u"||!("serviceWorker"in navigator)||!("PushManager"in window))return console.warn("[Dolphin PWA] Push notifications are not supported in this browser."),null;try{let e=await navigator.serviceWorker.ready,n=await e.pushManager.getSubscription();if(!n){let r="=".repeat((4-t.length%4)%4),i=(t+r).replace(/\-/g,"+").replace(/_/g,"/"),a=window.atob(i),l=new Uint8Array(a.length);for(let b=0;b<a.length;++b)l[b]=a.charCodeAt(b);n=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:l})}return console.log("[Dolphin PWA] Subscribed to push notifications:",n),n}catch(e){return console.error("[Dolphin PWA] Push notification subscription failed:",e),null}}}var P=class{static render(t){if(typeof document>"u")throw new Error("DolphinTestUtils.render requires a DOM document environment to execute.");let e=document.createElement("div");return e.innerHTML=t,document.body.appendChild(e),{container:e,find:n=>e.querySelector(n),fireEvent:(n,r)=>{let i=document.createEvent("Event");i.initEvent(r,!0,!0),n.dispatchEvent(i)}}}static mockWebSocket(){let t=[],e={readyState:1,send:n=>{t.push(n)},close:jest.fn(),onopen:jest.fn(),onmessage:jest.fn(),onclose:jest.fn(),onerror:jest.fn(),sentMessages:t};return global.WebSocket=class{static OPEN=1;readyState=e.readyState;send=e.send;close=e.close;set onopen(n){e.onopen=n}get onopen(){return e.onopen}set onmessage(n){e.onmessage=n}get onmessage(){return e.onmessage}set onclose(n){e.onclose=n}get getonclose(){return e.onclose}constructor(){setTimeout(()=>e.onopen&&e.onopen(),0)}},e}static simulateClick(t){let e={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.click||[]).forEach(r=>r(e))}static simulateChange(t,e){t.value=e;let n={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.change||[]).forEach(i=>i(n))}};function B(p){p.testing=P}R(v.prototype);q(v.prototype);O(v.prototype);N(v.prototype);U(v.prototype);F(v.prototype);H(v.prototype);W(v.prototype);j(v.prototype);B(v.prototype);typeof window<"u"&&(window.DolphinClient=v,document.addEventListener("DOMContentLoaded",()=>{if(!window.dolphin){let p=document.querySelector('script[src*="dolphin-client"]'),t=p?p.getAttribute("data-debug")==="true":!1,e=new v(void 0,void 0,{debug:t});window.dolphin=e,t&&(console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!","color: #06b6d4; font-weight: bold; font-size: 14px;"),console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.',"color: #94a3b8; font-style: italic;")),document.querySelector('[data-store-write="app.username"]')&&e.setStoreState("app","username","\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!")}}));return X(Z);})();
|
package/dist/index.cjs
CHANGED
|
@@ -108,6 +108,9 @@ var APIHandler = class {
|
|
|
108
108
|
async requestDirect(method, path, body = null, options = {}) {
|
|
109
109
|
const _isRetry = options._isRetry === true;
|
|
110
110
|
const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
111
|
+
if (this.client.options.debug) {
|
|
112
|
+
console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
|
|
113
|
+
}
|
|
111
114
|
const controller = new AbortController();
|
|
112
115
|
const timeoutId = setTimeout(
|
|
113
116
|
() => controller.abort(),
|
|
@@ -140,6 +143,9 @@ var APIHandler = class {
|
|
|
140
143
|
const contentType = response.headers.get("content-type") || "";
|
|
141
144
|
const data = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
142
145
|
if (!response.ok) throw { status: response.status, data };
|
|
146
|
+
if (this.client.options.debug) {
|
|
147
|
+
console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
|
|
148
|
+
}
|
|
143
149
|
if (data && typeof data === "object") {
|
|
144
150
|
if (data.accessToken) {
|
|
145
151
|
this.client.setToken(data.accessToken);
|
|
@@ -153,6 +159,9 @@ var APIHandler = class {
|
|
|
153
159
|
return data;
|
|
154
160
|
} catch (err) {
|
|
155
161
|
clearTimeout(timeoutId);
|
|
162
|
+
if (this.client.options.debug) {
|
|
163
|
+
console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
|
|
164
|
+
}
|
|
156
165
|
if (err.name === "AbortError") {
|
|
157
166
|
throw { status: 408, data: { error: "Request timed out" } };
|
|
158
167
|
}
|
|
@@ -437,6 +446,7 @@ var DolphinClient = class {
|
|
|
437
446
|
// 64 KB
|
|
438
447
|
maxReconnect: 5,
|
|
439
448
|
autoRefreshToken: true,
|
|
449
|
+
debug: false,
|
|
440
450
|
...options
|
|
441
451
|
};
|
|
442
452
|
this.socket = null;
|
|
@@ -519,6 +529,9 @@ var DolphinClient = class {
|
|
|
519
529
|
_handleMessage(data) {
|
|
520
530
|
try {
|
|
521
531
|
const msg = JSON.parse(data);
|
|
532
|
+
if (this.options.debug) {
|
|
533
|
+
console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
|
|
534
|
+
}
|
|
522
535
|
if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
|
|
523
536
|
if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
|
|
524
537
|
this.signalHandlers.forEach((h) => h(msg));
|
|
@@ -556,6 +569,9 @@ var DolphinClient = class {
|
|
|
556
569
|
}
|
|
557
570
|
/** @private */
|
|
558
571
|
_sendRaw(msg) {
|
|
572
|
+
if (this.options.debug) {
|
|
573
|
+
console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
|
|
574
|
+
}
|
|
559
575
|
const str = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
560
576
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
561
577
|
this.socket.send(str);
|
|
@@ -1013,6 +1029,9 @@ function attachDOMBinding(clientProto) {
|
|
|
1013
1029
|
}
|
|
1014
1030
|
const store = this.uiStores.get(storeName);
|
|
1015
1031
|
store[key] = val;
|
|
1032
|
+
if (this.options.debug) {
|
|
1033
|
+
console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
|
|
1034
|
+
}
|
|
1016
1035
|
if (typeof document !== "undefined") {
|
|
1017
1036
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
1018
1037
|
readElements.forEach((el) => {
|
|
@@ -1092,6 +1111,42 @@ function attachDOMBinding(clientProto) {
|
|
|
1092
1111
|
}
|
|
1093
1112
|
return null;
|
|
1094
1113
|
};
|
|
1114
|
+
clientProto._executeStoreAction = function(expression, element) {
|
|
1115
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1116
|
+
const context = new Proxy({}, {
|
|
1117
|
+
has: (target, prop) => {
|
|
1118
|
+
return true;
|
|
1119
|
+
},
|
|
1120
|
+
get: (target, prop) => {
|
|
1121
|
+
if (typeof prop === "string") {
|
|
1122
|
+
return new Proxy({}, {
|
|
1123
|
+
get: (subTarget, subProp) => {
|
|
1124
|
+
if (typeof subProp === "string") {
|
|
1125
|
+
return this.getStoreState(prop, subProp);
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
set: (subTarget, subProp, val) => {
|
|
1129
|
+
if (typeof subProp === "string") {
|
|
1130
|
+
this.setStoreState(prop, subProp, val);
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
try {
|
|
1140
|
+
const fn = new Function("ctx", `with(ctx) { ${expression} }`);
|
|
1141
|
+
fn(context);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
|
|
1144
|
+
if (element) {
|
|
1145
|
+
console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
|
|
1146
|
+
}
|
|
1147
|
+
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1095
1150
|
clientProto._initDOMBinding = function() {
|
|
1096
1151
|
if (this._domInitialized) return;
|
|
1097
1152
|
this._domInitialized = true;
|
|
@@ -1265,6 +1320,14 @@ function attachDOMBinding(clientProto) {
|
|
|
1265
1320
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1266
1321
|
}
|
|
1267
1322
|
}
|
|
1323
|
+
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
1324
|
+
if (storeActionBtn) {
|
|
1325
|
+
if (evtName === "submit") e.preventDefault();
|
|
1326
|
+
const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
|
|
1327
|
+
if (expr) {
|
|
1328
|
+
this._executeStoreAction(expr, storeActionBtn);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1268
1331
|
});
|
|
1269
1332
|
});
|
|
1270
1333
|
this.subscribe("#", (payload, topic) => {
|
|
@@ -2119,4 +2182,19 @@ attachPwa(DolphinClient.prototype);
|
|
|
2119
2182
|
attachTesting(DolphinClient.prototype);
|
|
2120
2183
|
if (typeof window !== "undefined") {
|
|
2121
2184
|
window.DolphinClient = DolphinClient;
|
|
2185
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2186
|
+
if (!window.dolphin) {
|
|
2187
|
+
const scriptEl = document.querySelector('script[src*="dolphin-client"]');
|
|
2188
|
+
const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
|
|
2189
|
+
const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
|
|
2190
|
+
window.dolphin = dolphin;
|
|
2191
|
+
if (debugMode) {
|
|
2192
|
+
console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
|
|
2193
|
+
console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
|
|
2194
|
+
}
|
|
2195
|
+
if (document.querySelector('[data-store-write="app.username"]')) {
|
|
2196
|
+
dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2122
2200
|
}
|