@fui-org/fui-cli 2.1.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/fui-qrb1j9jz.js +397 -0
- package/dist/fui.js +1 -1
- package/package.json +1 -1
- package/skills/fui/SKILL.md +143 -38
- package/skills/fui/agents/fui-builder.md +62 -0
- package/skills/fui/agents/fui-tester.md +77 -0
- package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
- package/skills/{fui-skill → fui}/examples/component.vue +162 -162
- package/skills/{fui-skill → fui}/references/INDEX.md +5 -4
- package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
- package/skills/{fui-skill → fui}/references/component-design.md +2 -2
- package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
- package/skills/{fui-skill → fui}/references/component-table.md +287 -287
- package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
- package/skills/{fui-skill → fui}/references/components-display.md +141 -141
- package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
- package/skills/{fui-skill → fui}/references/components-input.md +335 -335
- package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
- package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
- package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
- package/skills/{fui-skill → fui}/references/default-function.md +456 -456
- package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
- package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
- package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
- package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
- package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
- package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
- package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
- package/skills/{fui-skill → fui}/references/new-session.md +1 -2
- package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
- package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
- package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
- package/skills/{fui-skill → fui}/references/project-config.md +303 -303
- package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
- package/skills/{fui-skill → fui}/references/script-map.md +262 -262
- package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
- package/skills/{fui-skill → fui}/references/system-design.md +2 -2
- package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
- package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
- package/skills/fui/references/team-run.md +141 -0
- package/skills/fui/references/tools-registry.md +211 -0
- package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
- package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
- package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
- package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
- package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
- package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
- package/skills/{fui-skill → fui}/references/verification.md +11 -11
- package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
- package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
- package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
- package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
- package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
- package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
- package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
- package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
- package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
- package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
- package/dist/fui-h4z7zhk2.js +0 -381
- package/skills/fui-skill/SKILL.md +0 -139
- package/skills/fui-skill/references/tools-registry.md +0 -84
- /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
- /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
- /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
- /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
- /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
- /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
- /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
- /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
- /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
- /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
|
@@ -1,271 +1,271 @@
|
|
|
1
|
-
# FUI Real-time Messaging — WebSocket (SignalR)
|
|
2
|
-
|
|
3
|
-
> Owns: **real-time/SignalR: `webSocketJoinGroup`, `webSocket_Send`**.
|
|
4
|
-
|
|
5
|
-
Real-time uses **SignalR** (not the browser WebSocket API), hosted at `ws.fui.vn`. Client logic is already global in `defaultfunction.js` — do not rewrite it. Declare the right structure in `module.json` and use the 3 built-ins: `webSocketConnection`, `webSocketJoinGroup`, `webSocket_Send`.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Required imports
|
|
10
|
-
|
|
11
|
-
Add 2 JS files with `fui import new` — **in this order** (second depends on first):
|
|
12
|
-
|
|
13
|
-
| Order | File | Note |
|
|
14
|
-
| --------------- | ------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
15
|
-
| 1 (lower sort) | `https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js` | SignalR client core |
|
|
16
|
-
| 2 (higher sort) | `https://ws.fui.vn/signalr/hubs` | Server-generated hub proxy — JS content despite no `.js` extension |
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
fui import new --type .js --content-type link --name "https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js" --sort 100
|
|
20
|
-
fui import new --type .js --content-type link --name "https://ws.fui.vn/signalr/hubs" --sort 110
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
See `fui import new --help`.
|
|
24
|
-
|
|
25
|
-
> jQuery is already in the FUI runtime — do not import it. Run `fui import list` first to avoid duplicates (same flow as `f-echart` in [components-echart.md](components-echart.md)).
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 2. Auto-connect on module load
|
|
30
|
-
|
|
31
|
-
FUI bootstrap (`buildApp` in `fastproject.js`) checks `vueData.webSocket` right after `data[]` runs:
|
|
32
|
-
|
|
33
|
-
- If `vueData.webSocket` exists → creates `vueData.webSocketState = { code, text }` and calls `webSocketConnection('https://ws.fui.vn/signalr', vueData.webSocket, vueData.webSocketState)` + `webSocketJoinGroup(vueData.webSocket)`. **Do not call these yourself at bootstrap** — just declare `webSocket` in `data[]`.
|
|
34
|
-
- `webSocketState.text`: `Reconnecting` / `Connected` / `Disconnected`. Bind to UI for debugging (`innerHTML: "{{webSocketState}}"`).
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## 3. Declare groups to join — in `data[]`
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
"data": [
|
|
42
|
-
{
|
|
43
|
-
"TenNhomDong": "`ThuNha",
|
|
44
|
-
"DGroup": "`device-abc",
|
|
45
|
-
"webSocket": {
|
|
46
|
-
"nhom1": [],
|
|
47
|
-
"nhom2": null,
|
|
48
|
-
"{{DGroup}}": {}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
- Each **key** of `webSocket` = a group joined on load.
|
|
55
|
-
- **Value type sets receive behavior:**
|
|
56
|
-
- **array** (`[]`) → new messages **pushed** to the end (history: chat log, event list...).
|
|
57
|
-
- **`null` or object** (`{}`) → new message **overwrites** (latest only: status/counter/single notification).
|
|
58
|
-
- Group name: literal (`nhom1`) or **dynamic via `{{ }}` in the key** — `"{{DGroup}}"` resolves to `DGroup`'s current value (e.g. `device-abc`), for per-user/device groups.
|
|
59
|
-
- `` `ThuNha `` / `` `device-abc `` use backtick to force a literal string — see [Value Resolution Rules](controls-patterns.md#0-value-resolution-rules-fundamental).
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## 4. Join more groups at runtime — `FUN: webSocketJoinGroup`
|
|
64
|
-
|
|
65
|
-
Join any time, including as an auto-run action in `data[]` (see [ui-patterns.md §1](ui-patterns.md)):
|
|
66
|
-
|
|
67
|
-
```json
|
|
68
|
-
{
|
|
69
|
-
"FUN": "webSocketJoinGroup",
|
|
70
|
-
"IN": {
|
|
71
|
-
"Nhom3": null,
|
|
72
|
-
"{{TenNhomDong}}": []
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- `IN` **is the `groupObj`** (first param of `webSocketJoinGroup(groupObj, timeout, callBackFunc)`), not mapped by param name. So `FUN` + `IN` **cannot pass `timeout`/`callBackFunc`** — for those use raw `EXE`: `"EXE": "webSocketJoinGroup({Nhom3:null}, 3000, function(){ ... })"`.
|
|
78
|
-
- Use for groups that are **conditional** or known only **after** load (e.g. after an API returns a room/chat ID), instead of static `webSocket: {}`.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 5. Receive messages
|
|
83
|
-
|
|
84
|
-
### watch (recommended — run logic on new message)
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
"watch": {
|
|
88
|
-
"webSocket.nhom1": { "MESS": "thong bao" },
|
|
89
|
-
"webSocket.device-abc": { "MESS": "thong bao" }
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
> **Important:** `watch` keys **cannot** use `{{ }}` (literal, not interpolated) — write the resolved group (`webSocket.device-abc`, not `webSocket.{{DGroup}}`).
|
|
94
|
-
|
|
95
|
-
### Bind directly to UI (raw, no watch)
|
|
96
|
-
|
|
97
|
-
```json
|
|
98
|
-
{
|
|
99
|
-
"el": "v-textarea",
|
|
100
|
-
"attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", "rows": 5 }
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Debug all state
|
|
105
|
-
|
|
106
|
-
```json
|
|
107
|
-
{ "el": "div", "attr": {}, "innerHTML": "{{webSocket}}" }
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## 6. Send — `webSocket_Send`
|
|
113
|
-
|
|
114
|
-
`webSocket_Send(obj)` takes **one object** `{ group, data }`; `data` may be object, array or string — the function picks the SignalR API (`messageSendObject` / `messageSendArray` / `messageSend`).
|
|
115
|
-
|
|
116
|
-
### Way 1 — `FUN` + `IN` (object payload)
|
|
117
|
-
|
|
118
|
-
```json
|
|
119
|
-
{
|
|
120
|
-
"FUN": "webSocket_Send",
|
|
121
|
-
"IN": {
|
|
122
|
-
"group": "`nhom1",
|
|
123
|
-
"data": { "text": "doi tuong 1", "text2": "doi tuong 2" }
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Way 2 — raw `EXE` (read dynamic vueData values)
|
|
129
|
-
|
|
130
|
-
```json
|
|
131
|
-
{ "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText })" }
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Way 3 — array payload
|
|
135
|
-
|
|
136
|
-
```json
|
|
137
|
-
{
|
|
138
|
-
"FUN": "webSocket_Send",
|
|
139
|
-
"IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] }
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Directly in `script.js` (outside the action engine)
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
webSocket_Send({ group: "Nhom3", data: "Gui noi dung từ JS" });
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Way 4 — HTTP API (no client SignalR connection needed)
|
|
150
|
-
|
|
151
|
-
REST endpoint to send to a group:
|
|
152
|
-
|
|
153
|
-
```json
|
|
154
|
-
{
|
|
155
|
-
"API": "https://ws.fui.vn/api/messageSend/nhom1",
|
|
156
|
-
"IN": { "text": "test 1", "text2": "test 2" }
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Group name interpolates in the URL:
|
|
161
|
-
|
|
162
|
-
```json
|
|
163
|
-
{ "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" }
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Use when `FUN`/`EXE` is inconvenient (e.g. server-side sends, or decoupling sending from whether the client holds a SignalR connection).
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## 7. Full example
|
|
171
|
-
|
|
172
|
-
```json
|
|
173
|
-
{
|
|
174
|
-
"data": [
|
|
175
|
-
{
|
|
176
|
-
"TenNhomDong": "`ThuNha",
|
|
177
|
-
"DGroup": "`device-abc",
|
|
178
|
-
"webSocket": { "nhom1": [], "nhom2": null, "{{DGroup}}": {} }
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
"FUN": "webSocketJoinGroup",
|
|
182
|
-
"IN": { "Nhom3": null, "{{TenNhomDong}}": [] }
|
|
183
|
-
}
|
|
184
|
-
],
|
|
185
|
-
"watch": {
|
|
186
|
-
"webSocket.nhom1": { "MESS": "thong bao" },
|
|
187
|
-
"webSocket.device-abc": { "MESS": "thong bao" }
|
|
188
|
-
},
|
|
189
|
-
"controls": [
|
|
190
|
-
{
|
|
191
|
-
"prop": "fluid grid-list-md",
|
|
192
|
-
"rows": [
|
|
193
|
-
{
|
|
194
|
-
"prop": "",
|
|
195
|
-
"cols": [
|
|
196
|
-
{
|
|
197
|
-
"el": "v-textarea",
|
|
198
|
-
"col": { "class": "" },
|
|
199
|
-
"attr": { "v-model": "messText", "label": "noi dung", ":required": false, "rows": 5 },
|
|
200
|
-
"w": ""
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
"el": "f-button",
|
|
204
|
-
"col": { "class": "shrink" },
|
|
205
|
-
"attr": {
|
|
206
|
-
"label": "send - nhom2",
|
|
207
|
-
":action": [
|
|
208
|
-
{
|
|
209
|
-
"FUN": "webSocket_Send",
|
|
210
|
-
"IN": { "group": "`nhom1", "data": { "text": "doi tuong 1", "text2": "doi tuong 2" } }
|
|
211
|
-
},
|
|
212
|
-
{ "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText})" },
|
|
213
|
-
{ "FUN": "webSocket_Send", "IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] } }
|
|
214
|
-
]
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
]
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"prop": "",
|
|
221
|
-
"cols": [
|
|
222
|
-
{
|
|
223
|
-
"el": "v-textarea",
|
|
224
|
-
"col": {},
|
|
225
|
-
"attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", ":required": false, "rows": 5 }
|
|
226
|
-
}
|
|
227
|
-
]
|
|
228
|
-
},
|
|
229
|
-
{ "prop": "", "cols": [{ "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocket}}" }] },
|
|
230
|
-
{
|
|
231
|
-
"prop": "",
|
|
232
|
-
"cols": [
|
|
233
|
-
{
|
|
234
|
-
"el": "f-button",
|
|
235
|
-
"col": {},
|
|
236
|
-
"attr": {
|
|
237
|
-
"label": "send by API - nhom1",
|
|
238
|
-
":action": [
|
|
239
|
-
{ "API": "https://ws.fui.vn/api/messageSend/nhom1", "IN": { "text": "test 1", "text2": "test 2" } }
|
|
240
|
-
]
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
"el": "f-button",
|
|
245
|
-
"col": {},
|
|
246
|
-
"attr": {
|
|
247
|
-
":label": "'send by API - ' + DGroup",
|
|
248
|
-
":action": [{ "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" }]
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
},
|
|
253
|
-
{ "prop": "", "cols": [{ "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocketState}}" }] }
|
|
254
|
-
]
|
|
255
|
-
}
|
|
256
|
-
],
|
|
257
|
-
"set": {}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## 8. Checklist
|
|
264
|
-
|
|
265
|
-
- [ ] `fui import list`: are `jquery.signalR-2.4.1.min.js` + `signalr/hubs` present?
|
|
266
|
-
- [ ] Missing → `fui import new` both, sort order signalR first, hubs second
|
|
267
|
-
- [ ] Declare `data[].webSocket = { groupName: [] | null | {} }` — value type by need (history vs latest)
|
|
268
|
-
- [ ] Runtime/conditional groups → `FUN: webSocketJoinGroup` (no `timeout`/`callBackFunc` via `IN`; use `EXE` if needed)
|
|
269
|
-
- [ ] Receive: `watch` (literal key, no `{{ }}`) or bind `v-model="webSocket.<group>"`
|
|
270
|
-
- [ ] Send: `FUN: webSocket_Send` (object/array), `EXE` for dynamic `vueData`, or `API: https://ws.fui.vn/api/messageSend/<group>` for HTTP instead of client SignalR
|
|
271
|
-
- [ ] Use `vueData.webSocketState` if the user needs to see connection status
|
|
1
|
+
# FUI Real-time Messaging — WebSocket (SignalR)
|
|
2
|
+
|
|
3
|
+
> Owns: **real-time/SignalR: `webSocketJoinGroup`, `webSocket_Send`**.
|
|
4
|
+
|
|
5
|
+
Real-time uses **SignalR** (not the browser WebSocket API), hosted at `ws.fui.vn`. Client logic is already global in `defaultfunction.js` — do not rewrite it. Declare the right structure in `module.json` and use the 3 built-ins: `webSocketConnection`, `webSocketJoinGroup`, `webSocket_Send`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Required imports
|
|
10
|
+
|
|
11
|
+
Add 2 JS files with `fui import new` — **in this order** (second depends on first):
|
|
12
|
+
|
|
13
|
+
| Order | File | Note |
|
|
14
|
+
| --------------- | ------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
15
|
+
| 1 (lower sort) | `https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js` | SignalR client core |
|
|
16
|
+
| 2 (higher sort) | `https://ws.fui.vn/signalr/hubs` | Server-generated hub proxy — JS content despite no `.js` extension |
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
fui import new --type .js --content-type link --name "https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js" --sort 100
|
|
20
|
+
fui import new --type .js --content-type link --name "https://ws.fui.vn/signalr/hubs" --sort 110
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
See `fui import new --help`.
|
|
24
|
+
|
|
25
|
+
> jQuery is already in the FUI runtime — do not import it. Run `fui import list` first to avoid duplicates (same flow as `f-echart` in [components-echart.md](components-echart.md)).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Auto-connect on module load
|
|
30
|
+
|
|
31
|
+
FUI bootstrap (`buildApp` in `fastproject.js`) checks `vueData.webSocket` right after `data[]` runs:
|
|
32
|
+
|
|
33
|
+
- If `vueData.webSocket` exists → creates `vueData.webSocketState = { code, text }` and calls `webSocketConnection('https://ws.fui.vn/signalr', vueData.webSocket, vueData.webSocketState)` + `webSocketJoinGroup(vueData.webSocket)`. **Do not call these yourself at bootstrap** — just declare `webSocket` in `data[]`.
|
|
34
|
+
- `webSocketState.text`: `Reconnecting` / `Connected` / `Disconnected`. Bind to UI for debugging (`innerHTML: "{{webSocketState}}"`).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 3. Declare groups to join — in `data[]`
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
"data": [
|
|
42
|
+
{
|
|
43
|
+
"TenNhomDong": "`ThuNha",
|
|
44
|
+
"DGroup": "`device-abc",
|
|
45
|
+
"webSocket": {
|
|
46
|
+
"nhom1": [],
|
|
47
|
+
"nhom2": null,
|
|
48
|
+
"{{DGroup}}": {}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Each **key** of `webSocket` = a group joined on load.
|
|
55
|
+
- **Value type sets receive behavior:**
|
|
56
|
+
- **array** (`[]`) → new messages **pushed** to the end (history: chat log, event list...).
|
|
57
|
+
- **`null` or object** (`{}`) → new message **overwrites** (latest only: status/counter/single notification).
|
|
58
|
+
- Group name: literal (`nhom1`) or **dynamic via `{{ }}` in the key** — `"{{DGroup}}"` resolves to `DGroup`'s current value (e.g. `device-abc`), for per-user/device groups.
|
|
59
|
+
- `` `ThuNha `` / `` `device-abc `` use backtick to force a literal string — see [Value Resolution Rules](controls-patterns.md#0-value-resolution-rules-fundamental).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 4. Join more groups at runtime — `FUN: webSocketJoinGroup`
|
|
64
|
+
|
|
65
|
+
Join any time, including as an auto-run action in `data[]` (see [ui-patterns.md §1](ui-patterns.md)):
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"FUN": "webSocketJoinGroup",
|
|
70
|
+
"IN": {
|
|
71
|
+
"Nhom3": null,
|
|
72
|
+
"{{TenNhomDong}}": []
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `IN` **is the `groupObj`** (first param of `webSocketJoinGroup(groupObj, timeout, callBackFunc)`), not mapped by param name. So `FUN` + `IN` **cannot pass `timeout`/`callBackFunc`** — for those use raw `EXE`: `"EXE": "webSocketJoinGroup({Nhom3:null}, 3000, function(){ ... })"`.
|
|
78
|
+
- Use for groups that are **conditional** or known only **after** load (e.g. after an API returns a room/chat ID), instead of static `webSocket: {}`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 5. Receive messages
|
|
83
|
+
|
|
84
|
+
### watch (recommended — run logic on new message)
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"watch": {
|
|
88
|
+
"webSocket.nhom1": { "MESS": "thong bao" },
|
|
89
|
+
"webSocket.device-abc": { "MESS": "thong bao" }
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
> **Important:** `watch` keys **cannot** use `{{ }}` (literal, not interpolated) — write the resolved group (`webSocket.device-abc`, not `webSocket.{{DGroup}}`).
|
|
94
|
+
|
|
95
|
+
### Bind directly to UI (raw, no watch)
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"el": "v-textarea",
|
|
100
|
+
"attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", "rows": 5 }
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Debug all state
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{ "el": "div", "attr": {}, "innerHTML": "{{webSocket}}" }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 6. Send — `webSocket_Send`
|
|
113
|
+
|
|
114
|
+
`webSocket_Send(obj)` takes **one object** `{ group, data }`; `data` may be object, array or string — the function picks the SignalR API (`messageSendObject` / `messageSendArray` / `messageSend`).
|
|
115
|
+
|
|
116
|
+
### Way 1 — `FUN` + `IN` (object payload)
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"FUN": "webSocket_Send",
|
|
121
|
+
"IN": {
|
|
122
|
+
"group": "`nhom1",
|
|
123
|
+
"data": { "text": "doi tuong 1", "text2": "doi tuong 2" }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Way 2 — raw `EXE` (read dynamic vueData values)
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{ "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText })" }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Way 3 — array payload
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"FUN": "webSocket_Send",
|
|
139
|
+
"IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] }
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Directly in `script.js` (outside the action engine)
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
webSocket_Send({ group: "Nhom3", data: "Gui noi dung từ JS" });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Way 4 — HTTP API (no client SignalR connection needed)
|
|
150
|
+
|
|
151
|
+
REST endpoint to send to a group:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"API": "https://ws.fui.vn/api/messageSend/nhom1",
|
|
156
|
+
"IN": { "text": "test 1", "text2": "test 2" }
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Group name interpolates in the URL:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{ "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Use when `FUN`/`EXE` is inconvenient (e.g. server-side sends, or decoupling sending from whether the client holds a SignalR connection).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 7. Full example
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"data": [
|
|
175
|
+
{
|
|
176
|
+
"TenNhomDong": "`ThuNha",
|
|
177
|
+
"DGroup": "`device-abc",
|
|
178
|
+
"webSocket": { "nhom1": [], "nhom2": null, "{{DGroup}}": {} }
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"FUN": "webSocketJoinGroup",
|
|
182
|
+
"IN": { "Nhom3": null, "{{TenNhomDong}}": [] }
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"watch": {
|
|
186
|
+
"webSocket.nhom1": { "MESS": "thong bao" },
|
|
187
|
+
"webSocket.device-abc": { "MESS": "thong bao" }
|
|
188
|
+
},
|
|
189
|
+
"controls": [
|
|
190
|
+
{
|
|
191
|
+
"prop": "fluid grid-list-md",
|
|
192
|
+
"rows": [
|
|
193
|
+
{
|
|
194
|
+
"prop": "",
|
|
195
|
+
"cols": [
|
|
196
|
+
{
|
|
197
|
+
"el": "v-textarea",
|
|
198
|
+
"col": { "class": "" },
|
|
199
|
+
"attr": { "v-model": "messText", "label": "noi dung", ":required": false, "rows": 5 },
|
|
200
|
+
"w": ""
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"el": "f-button",
|
|
204
|
+
"col": { "class": "shrink" },
|
|
205
|
+
"attr": {
|
|
206
|
+
"label": "send - nhom2",
|
|
207
|
+
":action": [
|
|
208
|
+
{
|
|
209
|
+
"FUN": "webSocket_Send",
|
|
210
|
+
"IN": { "group": "`nhom1", "data": { "text": "doi tuong 1", "text2": "doi tuong 2" } }
|
|
211
|
+
},
|
|
212
|
+
{ "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText})" },
|
|
213
|
+
{ "FUN": "webSocket_Send", "IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] } }
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"prop": "",
|
|
221
|
+
"cols": [
|
|
222
|
+
{
|
|
223
|
+
"el": "v-textarea",
|
|
224
|
+
"col": {},
|
|
225
|
+
"attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", ":required": false, "rows": 5 }
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{ "prop": "", "cols": [{ "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocket}}" }] },
|
|
230
|
+
{
|
|
231
|
+
"prop": "",
|
|
232
|
+
"cols": [
|
|
233
|
+
{
|
|
234
|
+
"el": "f-button",
|
|
235
|
+
"col": {},
|
|
236
|
+
"attr": {
|
|
237
|
+
"label": "send by API - nhom1",
|
|
238
|
+
":action": [
|
|
239
|
+
{ "API": "https://ws.fui.vn/api/messageSend/nhom1", "IN": { "text": "test 1", "text2": "test 2" } }
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"el": "f-button",
|
|
245
|
+
"col": {},
|
|
246
|
+
"attr": {
|
|
247
|
+
":label": "'send by API - ' + DGroup",
|
|
248
|
+
":action": [{ "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" }]
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
{ "prop": "", "cols": [{ "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocketState}}" }] }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
"set": {}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 8. Checklist
|
|
264
|
+
|
|
265
|
+
- [ ] `fui import list`: are `jquery.signalR-2.4.1.min.js` + `signalr/hubs` present?
|
|
266
|
+
- [ ] Missing → `fui import new` both, sort order signalR first, hubs second
|
|
267
|
+
- [ ] Declare `data[].webSocket = { groupName: [] | null | {} }` — value type by need (history vs latest)
|
|
268
|
+
- [ ] Runtime/conditional groups → `FUN: webSocketJoinGroup` (no `timeout`/`callBackFunc` via `IN`; use `EXE` if needed)
|
|
269
|
+
- [ ] Receive: `watch` (literal key, no `{{ }}`) or bind `v-model="webSocket.<group>"`
|
|
270
|
+
- [ ] Send: `FUN: webSocket_Send` (object/array), `EXE` for dynamic `vueData`, or `API: https://ws.fui.vn/api/messageSend/<group>` for HTTP instead of client SignalR
|
|
271
|
+
- [ ] Use `vueData.webSocketState` if the user needs to see connection status
|