@autoafleveren/ui 1.8.4 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autoafleveren/ui",
3
- "version": "1.8.4",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/*",
@@ -6,6 +6,9 @@ import mitt from 'mitt';
6
6
 
7
7
  import type { Authorizer, Channel } from 'pusher-js';
8
8
 
9
+ type EventHandler<T = any> = (data: T) => unknown;
10
+ type WrappedEventHandler<T = any> = (data: T) => Promise<void>;
11
+
9
12
  interface Listener {
10
13
  name: string;
11
14
  isListening: boolean;
@@ -16,31 +19,48 @@ interface Caster {
16
19
  joining: (callback: CallableFunction) => this;
17
20
  whisper: (eventName: string, data: Record<any, any>) => this;
18
21
  leaving: (callback: CallableFunction) => this;
22
+ listenForWhisper?: (event: string, callback: CallableFunction) => void;
19
23
  }
20
24
 
21
25
  const state = {
22
26
  listeners: ref<Listener[]>([]),
27
+
23
28
  echo: null as Echo<'pusher'> | null,
24
- key: null as string | null,
25
- authorizer: null as ((channel: Channel) => Authorizer) | null,
29
+
26
30
  emitter: mitt(),
27
- casters: ref<Caster[]>([]),
31
+
32
+ casters: {} as Record<string, Caster>,
33
+
34
+ /**
35
+ * Tracks which Echo listeners are already registered.
36
+ */
37
+ subscriptions: new Set<string>(),
38
+
39
+ /**
40
+ * Maps original handler -> wrapped handler.
41
+ */
42
+ wrappedHandlers: new WeakMap<EventHandler, WrappedEventHandler>(),
28
43
  };
29
44
 
30
- export function getOrCreateListener(listerName = 'unique'): Listener {
31
- if (!state.listeners.value.some(listener => listener.name === listerName)) {
32
- state.listeners.value?.push({
33
- name: listerName,
34
- // @ts-expect-error refs unwrapped
35
- isListening: ref<boolean>(false),
36
- });
45
+ export function getOrCreateListener(listenerName = 'unique'): Listener {
46
+ let listener = state.listeners.value.find(stateListener => stateListener.name === listenerName);
47
+
48
+ if (!listener) {
49
+ listener = {
50
+ name: listenerName,
51
+ isListening: false,
52
+ };
53
+
54
+ state.listeners.value.push(listener);
37
55
  }
38
56
 
39
- // @ts-expect-error find always return a listener
40
- return state.listeners.value.find(listener => listener.name === listerName);
57
+ return listener;
41
58
  }
42
59
 
43
- export function registerEcho(key: string, authorizer: (channel: Channel) => Authorizer): void {
60
+ export function registerEcho(
61
+ key: string,
62
+ authorizer: (channel: Channel) => Authorizer,
63
+ ): void {
44
64
  state.echo = new Echo({
45
65
  broadcaster: 'pusher',
46
66
  client: ConnectionManager,
@@ -54,41 +74,52 @@ export function registerEcho(key: string, authorizer: (channel: Channel) => Auth
54
74
  }
55
75
 
56
76
  export function useEcho(listenerName: string | null = null) {
57
- const listener: Listener = getOrCreateListener(listenerName ?? undefined);
77
+ const listener = getOrCreateListener(listenerName ?? undefined);
58
78
 
59
79
  function join(channel: string): Caster {
60
- if (state.echo === null) {
80
+ if (!state.echo) {
61
81
  throw new Error('Echo is not initialized');
62
82
  }
63
83
 
64
- state.casters[channel] = state.echo.join(channel);
84
+ if (!state.casters[channel]) {
85
+ state.casters[channel] = state.echo.join(channel) as Caster;
86
+ }
65
87
 
66
- return state.casters[channel] as Caster;
88
+ return state.casters[channel];
67
89
  }
68
90
 
69
- function whisper<T = unknown>(event: string, data: T, channel?: string): void {
70
- if (state.echo === null) {
91
+ function leave(channel: string): void {
92
+ if (!state.echo) {
71
93
  throw new Error('Echo is not initialized');
72
94
  }
73
95
 
96
+ state.echo.leave(channel);
97
+ delete state.casters[channel];
98
+ }
99
+
100
+ function whisper<T = unknown>(
101
+ event: string,
102
+ data: T,
103
+ channel?: string,
104
+ ): void {
74
105
  const channelName = channel ?? Object.keys(state.casters)[0];
75
106
 
76
107
  try {
77
- state.casters[channelName]?.whisper?.(event, data);
108
+ state.casters[channelName]?.whisper?.(event, data as any);
78
109
  } catch {
79
110
  //
80
111
  }
81
112
  }
82
113
 
83
- function listen<T = unknown>(event: string, callback: (data: T) => void, channel?: string): void {
84
- if (state.echo === null) {
85
- throw new Error('Echo is not initialized');
86
- }
87
-
114
+ function listen<T = unknown>(
115
+ event: string,
116
+ callback: (data: T) => void,
117
+ channel?: string,
118
+ ): void {
88
119
  const channelName = channel ?? Object.keys(state.casters)[0];
89
120
 
90
121
  if (typeof state.casters[channelName]?.listenForWhisper === 'function') {
91
- state.casters[channelName]?.listenForWhisper?.(event, callback);
122
+ state.casters[channelName].listenForWhisper?.(event, callback);
92
123
 
93
124
  return;
94
125
  }
@@ -96,64 +127,79 @@ export function useEcho(listenerName: string | null = null) {
96
127
  throw new Error(`Nothing to whisper to on this channel ${event}-${channelName}`);
97
128
  }
98
129
 
99
- function leave(channel: string): void {
100
- if (state.echo === null) {
101
- throw new Error('Echo is not initialized');
102
- }
103
-
104
- state.echo.leave(channel);
105
-
106
- delete state.casters[channel];
107
- }
108
-
109
- function on(channel: string, event: string, handler: (data: any) => unknown, privateChannel = true, unique = false): void {
110
- if (state.echo === null) {
130
+ function on(
131
+ channel: string,
132
+ event: string,
133
+ handler: EventHandler,
134
+ privateChannel = true,
135
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/naming-convention
136
+ _unique = false,
137
+ ): void {
138
+ if (!state.echo) {
111
139
  throw new Error('Echo is not initialized');
112
140
  }
113
141
 
114
- const channelEventKey = `${channel}.${event}`;
115
- const isSubscribed = state.emitter.all.has(channelEventKey);
142
+ const key = `${channel}.${event}`;
116
143
 
117
- if (listener) {
118
- listener.isListening = true;
119
- }
144
+ listener.isListening = true;
120
145
 
121
- if (isSubscribed && unique) {
122
- return;
123
- }
146
+ if (!state.subscriptions.has(key)) {
147
+ state.subscriptions.add(key);
124
148
 
125
- if (!isSubscribed) {
126
- const channelConnection = privateChannel
149
+ const connection = privateChannel
127
150
  ? state.echo.private(channel)
128
151
  : state.echo.channel(channel);
129
152
 
130
- channelConnection.listen(`.${event}`, (data: any) => {
131
- state.emitter.emit(channelEventKey, data);
153
+ connection.listen(`.${event}`, (data: any) => {
154
+ state.emitter.emit(key, data);
132
155
  });
133
156
  }
134
157
 
135
- state.emitter.on(channelEventKey, async (data: any) => {
136
- const closeOnCompletion = await handler(data) ?? false;
158
+ const wrappedHandler: WrappedEventHandler = async (data): Promise<void> => {
159
+ const closeOnCompletion = (await handler(data)) ?? false;
137
160
 
138
- if (listener) {
139
- listener.isListening = false;
140
- }
161
+ listener.isListening = false;
141
162
 
142
163
  if (closeOnCompletion) {
143
- off(channel, event);
164
+ off(channel, event, handler);
144
165
  }
145
- });
166
+ };
167
+
168
+ state.wrappedHandlers.set(handler, wrappedHandler);
169
+ state.emitter.on(key, wrappedHandler);
146
170
  }
147
171
 
148
- function off(channel: string, event: string): void {
149
- if (state.echo === null) {
172
+ function off(
173
+ channel: string,
174
+ event: string,
175
+ handler?: EventHandler,
176
+ ): void {
177
+ if (!state.echo) {
150
178
  throw new Error('Echo is not initialized');
151
179
  }
152
180
 
153
- state.emitter.off(`${channel}.${event}`);
154
- state.emitter.all.delete(`${channel}.${event}`);
181
+ const key = `${channel}.${event}`;
155
182
 
156
- state.echo.leave(channel);
183
+ if (handler) {
184
+ const wrapped = state.wrappedHandlers.get(handler);
185
+
186
+ if (wrapped) {
187
+ state.emitter.off(key, wrapped as any);
188
+ state.wrappedHandlers.delete(handler);
189
+ }
190
+ } else {
191
+ state.emitter.off(key);
192
+ }
193
+
194
+ const remainingHandlers = state.emitter.all.get(key);
195
+
196
+ if (!remainingHandlers || remainingHandlers.length === 0) {
197
+ state.subscriptions.delete(key);
198
+
199
+ state.emitter.all.delete(key);
200
+
201
+ state.echo.leave(channel);
202
+ }
157
203
 
158
204
  listener.isListening = false;
159
205
  }