@jskit-ai/realtime 0.1.4

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.
@@ -0,0 +1,147 @@
1
+ import { inject, onBeforeUnmount, ref, unref, watch } from "vue";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { REALTIME_SOCKET_CLIENT_INJECTION_KEY } from "../tokens.js";
4
+
5
+ const EMPTY_REALTIME_SOCKET = Object.freeze({
6
+ on() {},
7
+ off() {},
8
+ onAny() {},
9
+ offAny() {}
10
+ });
11
+
12
+ function isRealtimeSocket(socket) {
13
+ return Boolean(
14
+ socket &&
15
+ typeof socket === "object" &&
16
+ typeof socket.on === "function" &&
17
+ typeof socket.off === "function"
18
+ );
19
+ }
20
+
21
+ function resolveEnabled(value) {
22
+ if (value === undefined) {
23
+ return true;
24
+ }
25
+ return Boolean(unref(value));
26
+ }
27
+
28
+ function resolveEventName(value) {
29
+ const normalized = normalizeText(unref(value));
30
+ return normalized || "*";
31
+ }
32
+
33
+ function useRealtimeSocket({ required = false } = {}) {
34
+ const socket = inject(REALTIME_SOCKET_CLIENT_INJECTION_KEY, null);
35
+ if (isRealtimeSocket(socket)) {
36
+ return socket;
37
+ }
38
+
39
+ if (required) {
40
+ throw new Error("Realtime client socket is not available in Vue injection context.");
41
+ }
42
+
43
+ return EMPTY_REALTIME_SOCKET;
44
+ }
45
+
46
+ function useRealtimeEvent({
47
+ event = "*",
48
+ enabled = true,
49
+ matches = null,
50
+ onEvent
51
+ } = {}) {
52
+ if (typeof onEvent !== "function") {
53
+ throw new TypeError("useRealtimeEvent requires onEvent().");
54
+ }
55
+
56
+ const socket = useRealtimeSocket({ required: false });
57
+ const active = ref(false);
58
+ let release = null;
59
+
60
+ function detach() {
61
+ if (typeof release === "function") {
62
+ try {
63
+ release();
64
+ } catch {}
65
+ }
66
+ release = null;
67
+ active.value = false;
68
+ }
69
+
70
+ function runHandler(eventName, payload) {
71
+ const context = Object.freeze({
72
+ event: eventName,
73
+ payload,
74
+ socket
75
+ });
76
+
77
+ if (typeof matches === "function" && matches(context) !== true) {
78
+ return;
79
+ }
80
+
81
+ Promise.resolve(onEvent(context)).catch((error) => {
82
+ console.error(
83
+ {
84
+ event: eventName,
85
+ error: String(error?.message || error || "unknown error")
86
+ },
87
+ "Realtime event handler failed."
88
+ );
89
+ });
90
+ }
91
+
92
+ function attach() {
93
+ detach();
94
+
95
+ if (socket === EMPTY_REALTIME_SOCKET) {
96
+ return;
97
+ }
98
+ if (!resolveEnabled(enabled)) {
99
+ return;
100
+ }
101
+
102
+ const eventName = resolveEventName(event);
103
+ if (eventName === "*") {
104
+ if (typeof socket.onAny !== "function") {
105
+ return;
106
+ }
107
+ const onAny = (nextEventName, payload) => runHandler(nextEventName, payload);
108
+ socket.onAny(onAny);
109
+ release = () => {
110
+ if (typeof socket.offAny === "function") {
111
+ socket.offAny(onAny);
112
+ }
113
+ };
114
+ active.value = true;
115
+ return;
116
+ }
117
+
118
+ const onEventMessage = (payload) => runHandler(eventName, payload);
119
+ socket.on(eventName, onEventMessage);
120
+ release = () => {
121
+ socket.off(eventName, onEventMessage);
122
+ };
123
+ active.value = true;
124
+ }
125
+
126
+ watch(
127
+ () => [resolveEnabled(enabled), resolveEventName(event)],
128
+ () => {
129
+ attach();
130
+ },
131
+ { immediate: true }
132
+ );
133
+
134
+ onBeforeUnmount(() => {
135
+ detach();
136
+ });
137
+
138
+ return Object.freeze({
139
+ active
140
+ });
141
+ }
142
+
143
+ export {
144
+ EMPTY_REALTIME_SOCKET,
145
+ useRealtimeSocket,
146
+ useRealtimeEvent
147
+ };
@@ -0,0 +1,69 @@
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
+ import { REALTIME_CLIENT_LISTENER_TAG } from "./tokens.js";
3
+
4
+ function normalizeListenerEntries(value) {
5
+ const queue = Array.isArray(value) ? [...value] : [value];
6
+ const listeners = [];
7
+
8
+ while (queue.length > 0) {
9
+ const entry = queue.shift();
10
+ if (Array.isArray(entry)) {
11
+ queue.push(...entry);
12
+ continue;
13
+ }
14
+ if (entry == null) {
15
+ continue;
16
+ }
17
+ listeners.push(entry);
18
+ }
19
+
20
+ return listeners;
21
+ }
22
+
23
+ function normalizeRealtimeClientListener(entry) {
24
+ if (typeof entry === "function") {
25
+ return Object.freeze({
26
+ listenerId: String(entry.name || "anonymous"),
27
+ event: "*",
28
+ matches: null,
29
+ handle: entry
30
+ });
31
+ }
32
+
33
+ if (entry && typeof entry === "object" && typeof entry.handle === "function") {
34
+ const event = normalizeText(entry.event) || "*";
35
+ return Object.freeze({
36
+ ...entry,
37
+ listenerId: String(entry.listenerId || "anonymous"),
38
+ event,
39
+ matches: typeof entry.matches === "function" ? entry.matches : null
40
+ });
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ function registerRealtimeClientListener(app, token, factory) {
47
+ if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
48
+ throw new Error("registerRealtimeClientListener requires application singleton()/tag().");
49
+ }
50
+
51
+ app.singleton(token, factory);
52
+ app.tag(token, REALTIME_CLIENT_LISTENER_TAG);
53
+ }
54
+
55
+ function resolveRealtimeClientListeners(scope) {
56
+ if (!scope || typeof scope.resolveTag !== "function") {
57
+ return [];
58
+ }
59
+
60
+ return normalizeListenerEntries(scope.resolveTag(REALTIME_CLIENT_LISTENER_TAG))
61
+ .map((entry) => normalizeRealtimeClientListener(entry))
62
+ .filter(Boolean);
63
+ }
64
+
65
+ export {
66
+ normalizeRealtimeClientListener,
67
+ registerRealtimeClientListener,
68
+ resolveRealtimeClientListeners
69
+ };
@@ -0,0 +1,37 @@
1
+ import { io as connectSocketIoClient } from "socket.io-client";
2
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+
4
+ const SOCKET_IO_PATH = "/socket.io";
5
+
6
+ function createSocketIoClient({
7
+ url = "",
8
+ options = {},
9
+ connect = connectSocketIoClient
10
+ } = {}) {
11
+ if (typeof connect !== "function") {
12
+ throw new Error("createSocketIoClient requires a valid socket.io client connect function.");
13
+ }
14
+
15
+ const normalizedUrl = normalizeText(url);
16
+ const source = options && typeof options === "object" && !Array.isArray(options) ? options : {};
17
+ const normalizedOptions = {
18
+ ...source,
19
+ path: SOCKET_IO_PATH
20
+ };
21
+ if (normalizedUrl) {
22
+ return connect(normalizedUrl, normalizedOptions);
23
+ }
24
+ return connect(normalizedOptions);
25
+ }
26
+
27
+ function disconnectSocketIoClient(socket) {
28
+ if (!socket || typeof socket.disconnect !== "function") {
29
+ return;
30
+ }
31
+ socket.disconnect();
32
+ }
33
+
34
+ export {
35
+ createSocketIoClient,
36
+ disconnectSocketIoClient
37
+ };
@@ -0,0 +1,11 @@
1
+ const REALTIME_RUNTIME_CLIENT_TOKEN = "runtime.realtime.client";
2
+ const REALTIME_SOCKET_CLIENT_TOKEN = "runtime.realtime.client.socket";
3
+ const REALTIME_SOCKET_CLIENT_INJECTION_KEY = Symbol.for("jskit.realtime.runtime.client.socket");
4
+ const REALTIME_CLIENT_LISTENER_TAG = Symbol.for("jskit.runtime.realtime.client.listeners");
5
+
6
+ export {
7
+ REALTIME_RUNTIME_CLIENT_TOKEN,
8
+ REALTIME_SOCKET_CLIENT_TOKEN,
9
+ REALTIME_SOCKET_CLIENT_INJECTION_KEY,
10
+ REALTIME_CLIENT_LISTENER_TAG
11
+ };