nvent 1.0.0-alpha.10 → 1.0.0-alpha.11

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/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nvent",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.11",
4
4
  "configKey": "nvent",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
@@ -1,20 +1,43 @@
1
1
  /**
2
2
  * useIiiStream — subscribe to an iii stream group via WebSocket.
3
3
  *
4
- * The iii stream module serves WebSocket connections at:
4
+ * Connects to the iii engine's built-in stream WebSocket endpoint:
5
5
  * ws://host/_iii/stream/{stream_name}/{group_id}/
6
6
  *
7
7
  * Nitro proxies `/_iii/stream/**` → `ws://iii-engine:3112/`, so connecting
8
8
  * to the current page host is sufficient — no hardcoded ports needed.
9
9
  *
10
+ * **Authentication**: The browser WebSocket API does not support custom
11
+ * headers. The iii engine uses cookie-based auth for WebSocket connections
12
+ * (same session cookies as the rest of the app), so auth is handled
13
+ * automatically without any additional configuration.
14
+ *
15
+ * **Reconnection**: The composable implements exponential backoff with jitter.
16
+ * When the connection drops unexpectedly it will retry up to `maxRetries`
17
+ * times (default: 10) before giving up and setting status to `'error'`.
18
+ * Calling `subscribe()` again with new arguments or after an intentional
19
+ * `close()` always resets the retry counter and starts fresh.
20
+ *
21
+ * **No unnecessary reconnects**: Calling `subscribe()` with the same
22
+ * `streamName`/`groupId` while already connected is a no-op.
23
+ *
24
+ *
10
25
  * ```ts
11
26
  * const { messages, status, subscribe, close } = useIiiStream<MyEvent>()
12
- * subscribe('pipeline', jobId) // start listening
27
+ * subscribe('pipeline', jobId) // start listening; auto-reconnects on drop
13
28
  * // messages.value grows as events arrive
14
29
  * ```
15
30
  */
16
31
  export type IiiStreamStatus = 'idle' | 'connecting' | 'connected' | 'closed' | 'error';
17
- export declare function useIiiStream<TMessage = unknown>(): {
32
+ export interface IiiStreamOptions {
33
+ /** Maximum number of reconnect attempts before giving up. Default: 10. */
34
+ maxRetries?: number;
35
+ /** Base delay in ms for the first reconnect attempt. Default: 500. */
36
+ baseDelayMs?: number;
37
+ /** Maximum delay in ms between reconnect attempts. Default: 30_000. */
38
+ maxDelayMs?: number;
39
+ }
40
+ export declare function useIiiStream<TMessage = unknown>(options?: IiiStreamOptions): {
18
41
  messages: import("vue").Ref<import("@vue/reactivity").UnwrapRefSimple<TMessage>[], TMessage[] | import("@vue/reactivity").UnwrapRefSimple<TMessage>[]>;
19
42
  status: import("vue").Ref<IiiStreamStatus, IiiStreamStatus>;
20
43
  subscribe: (streamName: string, groupId: string) => void;
@@ -1,19 +1,39 @@
1
1
  import { ref, onUnmounted } from "vue";
2
- export function useIiiStream() {
2
+ export function useIiiStream(options = {}) {
3
+ const {
4
+ maxRetries = 10,
5
+ baseDelayMs = 500,
6
+ maxDelayMs = 3e4
7
+ } = options;
3
8
  const messages = ref([]);
4
9
  const status = ref("idle");
5
10
  let ws = null;
6
- function subscribe(streamName, groupId) {
7
- close();
8
- messages.value = [];
11
+ let currentStreamName = null;
12
+ let currentGroupId = null;
13
+ let retryCount = 0;
14
+ let retryTimer = null;
15
+ let intentionalClose = false;
16
+ function buildUrl(streamName, groupId) {
17
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
18
+ const host = window.location.host;
19
+ return `${protocol}//${host}/_iii/stream/${encodeURIComponent(streamName)}/${encodeURIComponent(groupId)}/`;
20
+ }
21
+ function clearRetryTimer() {
22
+ if (retryTimer !== null) {
23
+ clearTimeout(retryTimer);
24
+ retryTimer = null;
25
+ }
26
+ }
27
+ function connect(streamName, groupId) {
28
+ if (typeof window === "undefined") return;
9
29
  status.value = "connecting";
10
- const protocol = typeof window !== "undefined" && window.location.protocol === "https:" ? "wss:" : "ws:";
11
- const host = typeof window !== "undefined" ? window.location.host : "localhost";
12
- ws = new WebSocket(`${protocol}//${host}/_iii/stream/${streamName}/${groupId}/`);
13
- ws.onopen = () => {
30
+ const socket = new WebSocket(buildUrl(streamName, groupId));
31
+ ws = socket;
32
+ socket.onopen = () => {
33
+ retryCount = 0;
14
34
  status.value = "connected";
15
35
  };
16
- ws.onmessage = (e) => {
36
+ socket.onmessage = (e) => {
17
37
  try {
18
38
  const envelope = JSON.parse(e.data);
19
39
  let payload;
@@ -33,20 +53,56 @@ export function useIiiStream() {
33
53
  messages.value = [...messages.value, e.data];
34
54
  }
35
55
  };
36
- ws.onerror = () => {
37
- status.value = "error";
56
+ socket.onerror = () => {
38
57
  };
39
- ws.onclose = () => {
40
- if (status.value !== "error") status.value = "closed";
58
+ socket.onclose = (event) => {
41
59
  ws = null;
60
+ if (intentionalClose) {
61
+ status.value = "closed";
62
+ return;
63
+ }
64
+ if (retryCount >= maxRetries) {
65
+ status.value = "error";
66
+ currentStreamName = null;
67
+ currentGroupId = null;
68
+ return;
69
+ }
70
+ const delay = Math.min(baseDelayMs * 2 ** retryCount, maxDelayMs);
71
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
72
+ retryCount++;
73
+ status.value = "connecting";
74
+ retryTimer = setTimeout(() => {
75
+ retryTimer = null;
76
+ if (!intentionalClose && currentStreamName && currentGroupId) {
77
+ connect(currentStreamName, currentGroupId);
78
+ }
79
+ }, Math.max(0, delay + jitter));
42
80
  };
43
81
  }
82
+ function subscribe(streamName, groupId) {
83
+ if (!intentionalClose && ws !== null && ws.readyState === WebSocket.OPEN && currentStreamName === streamName && currentGroupId === groupId) {
84
+ return;
85
+ }
86
+ close();
87
+ intentionalClose = false;
88
+ retryCount = 0;
89
+ messages.value = [];
90
+ currentStreamName = streamName;
91
+ currentGroupId = groupId;
92
+ connect(streamName, groupId);
93
+ }
44
94
  function close() {
95
+ intentionalClose = true;
96
+ clearRetryTimer();
45
97
  if (ws) {
46
98
  ws.close();
47
99
  ws = null;
48
100
  }
49
- if (status.value !== "idle") status.value = "closed";
101
+ if (status.value !== "idle") {
102
+ status.value = "closed";
103
+ }
104
+ currentStreamName = null;
105
+ currentGroupId = null;
50
106
  }
51
107
  onUnmounted(close);
52
108
  return { messages, status, subscribe, close };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "nvent",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.11",
4
+
4
5
  "description": "Event-driven workflows for Nuxt",
5
6
  "repository": "nhealthorg/nvent",
6
7
  "license": "MIT",