@hamak/event-channel 0.5.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/dist/api/actions/event-channel-actions.d.ts +39 -0
- package/dist/api/actions/event-channel-actions.d.ts.map +1 -0
- package/dist/api/actions/event-channel-actions.js +29 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +9 -0
- package/dist/api/tokens/service-tokens.d.ts +6 -0
- package/dist/api/tokens/service-tokens.d.ts.map +1 -0
- package/dist/api/tokens/service-tokens.js +5 -0
- package/dist/api/types/connection-types.d.ts +17 -0
- package/dist/api/types/connection-types.d.ts.map +1 -0
- package/dist/api/types/connection-types.js +4 -0
- package/dist/api/types/event-types.d.ts +64 -0
- package/dist/api/types/event-types.d.ts.map +1 -0
- package/dist/api/types/event-types.js +4 -0
- package/dist/impl/core/event-channel.d.ts +35 -0
- package/dist/impl/core/event-channel.d.ts.map +1 -0
- package/dist/impl/core/event-channel.js +177 -0
- package/dist/impl/index.d.ts +16 -0
- package/dist/impl/index.d.ts.map +1 -0
- package/dist/impl/index.js +19 -0
- package/dist/impl/middleware/event-channel-middleware.d.ts +14 -0
- package/dist/impl/middleware/event-channel-middleware.d.ts.map +1 -0
- package/dist/impl/middleware/event-channel-middleware.js +33 -0
- package/dist/impl/plugin/event-channel-plugin-factory.d.ts +27 -0
- package/dist/impl/plugin/event-channel-plugin-factory.d.ts.map +1 -0
- package/dist/impl/plugin/event-channel-plugin-factory.js +136 -0
- package/dist/impl/security/action-whitelist-filter.d.ts +15 -0
- package/dist/impl/security/action-whitelist-filter.d.ts.map +1 -0
- package/dist/impl/security/action-whitelist-filter.js +34 -0
- package/dist/impl/store/event-channel-reducer.d.ts +17 -0
- package/dist/impl/store/event-channel-reducer.d.ts.map +1 -0
- package/dist/impl/store/event-channel-reducer.js +38 -0
- package/dist/impl/transport/sse-transport.d.ts +25 -0
- package/dist/impl/transport/sse-transport.d.ts.map +1 -0
- package/dist/impl/transport/sse-transport.js +119 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/spi/config/event-channel-plugin-config.d.ts +59 -0
- package/dist/spi/config/event-channel-plugin-config.d.ts.map +1 -0
- package/dist/spi/config/event-channel-plugin-config.js +4 -0
- package/dist/spi/index.d.ts +10 -0
- package/dist/spi/index.d.ts.map +1 -0
- package/dist/spi/index.js +7 -0
- package/dist/spi/security/i-action-filter.d.ts +12 -0
- package/dist/spi/security/i-action-filter.d.ts.map +1 -0
- package/dist/spi/security/i-action-filter.js +4 -0
- package/dist/spi/transport/i-event-transport.d.ts +20 -0
- package/dist/spi/transport/i-event-transport.d.ts.map +1 -0
- package/dist/spi/transport/i-event-transport.js +5 -0
- package/package.json +63 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redux action types and creators for the event channel.
|
|
3
|
+
*/
|
|
4
|
+
import type { ConnectionInfo } from '../types/connection-types';
|
|
5
|
+
import type { EventChannelEvent, RemoteAction } from '../types/event-types';
|
|
6
|
+
export declare const EventChannelActionTypes: {
|
|
7
|
+
readonly CONNECTION_STATUS_CHANGED: "eventChannel/connectionStatusChanged";
|
|
8
|
+
readonly EVENT_RECEIVED: "eventChannel/eventReceived";
|
|
9
|
+
readonly REMOTE_ACTION_RECEIVED: "eventChannel/remoteActionReceived";
|
|
10
|
+
readonly CONNECT: "eventChannel/connect";
|
|
11
|
+
readonly DISCONNECT: "eventChannel/disconnect";
|
|
12
|
+
};
|
|
13
|
+
export interface ConnectionStatusChangedAction {
|
|
14
|
+
type: typeof EventChannelActionTypes.CONNECTION_STATUS_CHANGED;
|
|
15
|
+
payload: ConnectionInfo;
|
|
16
|
+
}
|
|
17
|
+
export interface EventReceivedAction {
|
|
18
|
+
type: typeof EventChannelActionTypes.EVENT_RECEIVED;
|
|
19
|
+
payload: EventChannelEvent;
|
|
20
|
+
}
|
|
21
|
+
export interface RemoteActionReceivedAction {
|
|
22
|
+
type: typeof EventChannelActionTypes.REMOTE_ACTION_RECEIVED;
|
|
23
|
+
payload: RemoteAction;
|
|
24
|
+
}
|
|
25
|
+
export interface ConnectAction {
|
|
26
|
+
type: typeof EventChannelActionTypes.CONNECT;
|
|
27
|
+
}
|
|
28
|
+
export interface DisconnectAction {
|
|
29
|
+
type: typeof EventChannelActionTypes.DISCONNECT;
|
|
30
|
+
}
|
|
31
|
+
export type EventChannelAction = ConnectionStatusChangedAction | EventReceivedAction | RemoteActionReceivedAction | ConnectAction | DisconnectAction;
|
|
32
|
+
export declare const eventChannelActions: {
|
|
33
|
+
connectionStatusChanged(info: ConnectionInfo): ConnectionStatusChangedAction;
|
|
34
|
+
eventReceived(event: EventChannelEvent): EventReceivedAction;
|
|
35
|
+
remoteActionReceived(action: RemoteAction): RemoteActionReceivedAction;
|
|
36
|
+
connect(): ConnectAction;
|
|
37
|
+
disconnect(): DisconnectAction;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=event-channel-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel-actions.d.ts","sourceRoot":"","sources":["../../../src/api/actions/event-channel-actions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG5E,eAAO,MAAM,uBAAuB;;;;;;CAM1B,CAAC;AAGX,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,OAAO,uBAAuB,CAAC,yBAAyB,CAAC;IAC/D,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,uBAAuB,CAAC,cAAc,CAAC;IACpD,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,uBAAuB,CAAC,sBAAsB,CAAC;IAC5D,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,uBAAuB,CAAC,OAAO,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,uBAAuB,CAAC,UAAU,CAAC;CACjD;AAED,MAAM,MAAM,kBAAkB,GAC1B,6BAA6B,GAC7B,mBAAmB,GACnB,0BAA0B,GAC1B,aAAa,GACb,gBAAgB,CAAC;AAGrB,eAAO,MAAM,mBAAmB;kCACA,cAAc,GAAG,6BAA6B;yBAIvD,iBAAiB,GAAG,mBAAmB;iCAI/B,YAAY,GAAG,0BAA0B;eAI3D,aAAa;kBAIV,gBAAgB;CAG/B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redux action types and creators for the event channel.
|
|
3
|
+
*/
|
|
4
|
+
// Action type constants
|
|
5
|
+
export const EventChannelActionTypes = {
|
|
6
|
+
CONNECTION_STATUS_CHANGED: 'eventChannel/connectionStatusChanged',
|
|
7
|
+
EVENT_RECEIVED: 'eventChannel/eventReceived',
|
|
8
|
+
REMOTE_ACTION_RECEIVED: 'eventChannel/remoteActionReceived',
|
|
9
|
+
CONNECT: 'eventChannel/connect',
|
|
10
|
+
DISCONNECT: 'eventChannel/disconnect',
|
|
11
|
+
};
|
|
12
|
+
// Action creators
|
|
13
|
+
export const eventChannelActions = {
|
|
14
|
+
connectionStatusChanged(info) {
|
|
15
|
+
return { type: EventChannelActionTypes.CONNECTION_STATUS_CHANGED, payload: info };
|
|
16
|
+
},
|
|
17
|
+
eventReceived(event) {
|
|
18
|
+
return { type: EventChannelActionTypes.EVENT_RECEIVED, payload: event };
|
|
19
|
+
},
|
|
20
|
+
remoteActionReceived(action) {
|
|
21
|
+
return { type: EventChannelActionTypes.REMOTE_ACTION_RECEIVED, payload: action };
|
|
22
|
+
},
|
|
23
|
+
connect() {
|
|
24
|
+
return { type: EventChannelActionTypes.CONNECT };
|
|
25
|
+
},
|
|
26
|
+
disconnect() {
|
|
27
|
+
return { type: EventChannelActionTypes.DISCONNECT };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/event-channel API
|
|
3
|
+
*
|
|
4
|
+
* Public types, interfaces, tokens, and action creators.
|
|
5
|
+
*/
|
|
6
|
+
export type { ConnectionStatus, ConnectionInfo, } from './types/connection-types';
|
|
7
|
+
export type { EventChannelEvent, RemoteAction, IEventChannel, } from './types/event-types';
|
|
8
|
+
export { EVENT_CHANNEL_TOKEN, EVENT_CHANNEL_CONFIG_TOKEN, } from './tokens/service-tokens';
|
|
9
|
+
export { EventChannelActionTypes, eventChannelActions, type EventChannelAction, type ConnectionStatusChangedAction, type EventReceivedAction, type RemoteActionReceivedAction, type ConnectAction, type DisconnectAction, } from './actions/event-channel-actions';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,gBAAgB,EAChB,cAAc,GACf,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/event-channel API
|
|
3
|
+
*
|
|
4
|
+
* Public types, interfaces, tokens, and action creators.
|
|
5
|
+
*/
|
|
6
|
+
// Tokens
|
|
7
|
+
export { EVENT_CHANNEL_TOKEN, EVENT_CHANNEL_CONFIG_TOKEN, } from './tokens/service-tokens';
|
|
8
|
+
// Actions
|
|
9
|
+
export { EventChannelActionTypes, eventChannelActions, } from './actions/event-channel-actions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-tokens.d.ts","sourceRoot":"","sources":["../../../src/api/tokens/service-tokens.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,mBAAmB,eAAkD,CAAC;AACnF,eAAO,MAAM,0BAA0B,eAA4C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection status and info types for the event channel.
|
|
3
|
+
*/
|
|
4
|
+
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
|
|
5
|
+
export interface ConnectionInfo {
|
|
6
|
+
/** Current connection status */
|
|
7
|
+
status: ConnectionStatus;
|
|
8
|
+
/** Number of reconnect attempts so far */
|
|
9
|
+
reconnectAttempts: number;
|
|
10
|
+
/** ISO timestamp of last successful connection */
|
|
11
|
+
lastConnectedAt?: string;
|
|
12
|
+
/** Error message if status is 'error' */
|
|
13
|
+
error?: string;
|
|
14
|
+
/** URL of the event stream */
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=connection-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-types.d.ts","sourceRoot":"","sources":["../../../src/api/types/connection-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,OAAO,CAAC;AAEtG,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,0CAA0C;IAC1C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core event channel types.
|
|
3
|
+
*/
|
|
4
|
+
import type { ConnectionStatus } from './connection-types';
|
|
5
|
+
/**
|
|
6
|
+
* A typed event from the event channel.
|
|
7
|
+
*/
|
|
8
|
+
export interface EventChannelEvent<T = unknown> {
|
|
9
|
+
/** Unique event ID (server-generated) */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Event type discriminator */
|
|
12
|
+
type: string;
|
|
13
|
+
/** Event payload */
|
|
14
|
+
data: T;
|
|
15
|
+
/** ISO timestamp from server */
|
|
16
|
+
timestamp: string;
|
|
17
|
+
/** Source identifier (which backend service emitted this) */
|
|
18
|
+
source?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A remote Redux action dispatched from the backend.
|
|
22
|
+
* Delivered as an EventChannelEvent with type='remote-action'.
|
|
23
|
+
*/
|
|
24
|
+
export interface RemoteAction {
|
|
25
|
+
/** The Redux action type to dispatch */
|
|
26
|
+
actionType: string;
|
|
27
|
+
/** The action payload */
|
|
28
|
+
payload?: unknown;
|
|
29
|
+
/** Optional metadata */
|
|
30
|
+
meta?: {
|
|
31
|
+
source: string;
|
|
32
|
+
correlationId?: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Event channel service interface.
|
|
38
|
+
*/
|
|
39
|
+
export interface IEventChannel {
|
|
40
|
+
/** Connect to the event stream */
|
|
41
|
+
connect(): void;
|
|
42
|
+
/** Disconnect from the event stream */
|
|
43
|
+
disconnect(): void;
|
|
44
|
+
/** Current connection status */
|
|
45
|
+
getStatus(): ConnectionStatus;
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to events of a specific type.
|
|
48
|
+
* @returns unsubscribe function
|
|
49
|
+
*/
|
|
50
|
+
on<T = unknown>(eventType: string, handler: (event: EventChannelEvent<T>) => void): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to all events.
|
|
53
|
+
* @returns unsubscribe function
|
|
54
|
+
*/
|
|
55
|
+
onAny(handler: (event: EventChannelEvent) => void): () => void;
|
|
56
|
+
/**
|
|
57
|
+
* Subscribe to connection status changes.
|
|
58
|
+
* @returns unsubscribe function
|
|
59
|
+
*/
|
|
60
|
+
onStatusChange(handler: (status: ConnectionStatus) => void): () => void;
|
|
61
|
+
/** Destroy the channel and clean up all subscriptions */
|
|
62
|
+
destroy(): void;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=event-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-types.d.ts","sourceRoot":"","sources":["../../../src/api/types/event-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC5C,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wBAAwB;IACxB,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,OAAO,IAAI,IAAI,CAAC;IAEhB,uCAAuC;IACvC,UAAU,IAAI,IAAI,CAAC;IAEnB,gCAAgC;IAChC,SAAS,IAAI,gBAAgB,CAAC;IAE9B;;;OAGG;IACH,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAE/F;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAE/D;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAExE,yDAAyD;IACzD,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Service
|
|
3
|
+
*
|
|
4
|
+
* Manages the event stream connection with auto-reconnect
|
|
5
|
+
* and typed event subscriptions.
|
|
6
|
+
*/
|
|
7
|
+
import type { EventChannelEvent, ConnectionStatus, IEventChannel } from '../../api';
|
|
8
|
+
import type { IEventTransport } from '../../spi/transport/i-event-transport';
|
|
9
|
+
import type { EventChannelPluginConfig } from '../../spi/config/event-channel-plugin-config';
|
|
10
|
+
export declare class EventChannel implements IEventChannel {
|
|
11
|
+
private transport;
|
|
12
|
+
private config;
|
|
13
|
+
private typedHandlers;
|
|
14
|
+
private anyHandlers;
|
|
15
|
+
private statusHandlers;
|
|
16
|
+
private reconnectTimer;
|
|
17
|
+
private reconnectAttempts;
|
|
18
|
+
private destroyed;
|
|
19
|
+
private unsubscribers;
|
|
20
|
+
constructor(pluginConfig: EventChannelPluginConfig, transport: IEventTransport);
|
|
21
|
+
connect(): void;
|
|
22
|
+
disconnect(): void;
|
|
23
|
+
getStatus(): ConnectionStatus;
|
|
24
|
+
on<T = unknown>(eventType: string, handler: (event: EventChannelEvent<T>) => void): () => void;
|
|
25
|
+
onAny(handler: (event: EventChannelEvent) => void): () => void;
|
|
26
|
+
onStatusChange(handler: (status: ConnectionStatus) => void): () => void;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
private handleEvent;
|
|
29
|
+
private handleStatusChange;
|
|
30
|
+
private handleError;
|
|
31
|
+
private scheduleReconnect;
|
|
32
|
+
private cancelReconnect;
|
|
33
|
+
private log;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=event-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel.d.ts","sourceRoot":"","sources":["../../../src/impl/core/event-channel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAC;AAE7F,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAgJ;IAC9J,OAAO,CAAC,aAAa,CAA8D;IACnF,OAAO,CAAC,WAAW,CAAiD;IACpE,OAAO,CAAC,cAAc,CAAiD;IACvE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAyB;gBAElC,YAAY,EAAE,wBAAwB,EAAE,SAAS,EAAE,eAAe;IAmB9E,OAAO,IAAI,IAAI;IAQf,UAAU,IAAI,IAAI;IAMlB,SAAS,IAAI,gBAAgB;IAI7B,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAS9F,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAK9D,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvE,OAAO,IAAI,IAAI;IAcf,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,GAAG;CAKZ"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Service
|
|
3
|
+
*
|
|
4
|
+
* Manages the event stream connection with auto-reconnect
|
|
5
|
+
* and typed event subscriptions.
|
|
6
|
+
*/
|
|
7
|
+
export class EventChannel {
|
|
8
|
+
constructor(pluginConfig, transport) {
|
|
9
|
+
Object.defineProperty(this, "transport", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: void 0
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "config", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: void 0
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "typedHandlers", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: new Map()
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(this, "anyHandlers", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
value: new Set()
|
|
32
|
+
});
|
|
33
|
+
Object.defineProperty(this, "statusHandlers", {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
writable: true,
|
|
37
|
+
value: new Set()
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(this, "reconnectTimer", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: null
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(this, "reconnectAttempts", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: 0
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(this, "destroyed", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
value: false
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(this, "unsubscribers", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: []
|
|
62
|
+
});
|
|
63
|
+
this.transport = transport;
|
|
64
|
+
this.config = {
|
|
65
|
+
url: pluginConfig.url ?? '/api/events',
|
|
66
|
+
autoReconnect: pluginConfig.autoReconnect ?? true,
|
|
67
|
+
maxReconnectAttempts: pluginConfig.maxReconnectAttempts ?? 0,
|
|
68
|
+
reconnectDelay: pluginConfig.reconnectDelay ?? 1000,
|
|
69
|
+
maxReconnectDelay: pluginConfig.maxReconnectDelay ?? 30000,
|
|
70
|
+
debug: pluginConfig.debug ?? false,
|
|
71
|
+
};
|
|
72
|
+
// Wire transport events
|
|
73
|
+
this.unsubscribers.push(this.transport.onEvent((event) => this.handleEvent(event)), this.transport.onStatusChange((status) => this.handleStatusChange(status)), this.transport.onError((error) => this.handleError(error)));
|
|
74
|
+
}
|
|
75
|
+
connect() {
|
|
76
|
+
if (this.destroyed)
|
|
77
|
+
return;
|
|
78
|
+
this.reconnectAttempts = 0;
|
|
79
|
+
this.cancelReconnect();
|
|
80
|
+
this.transport.connect(this.config.url);
|
|
81
|
+
this.log('Connecting to', this.config.url);
|
|
82
|
+
}
|
|
83
|
+
disconnect() {
|
|
84
|
+
this.cancelReconnect();
|
|
85
|
+
this.transport.disconnect();
|
|
86
|
+
this.log('Disconnected');
|
|
87
|
+
}
|
|
88
|
+
getStatus() {
|
|
89
|
+
return this.transport.getStatus();
|
|
90
|
+
}
|
|
91
|
+
on(eventType, handler) {
|
|
92
|
+
if (!this.typedHandlers.has(eventType)) {
|
|
93
|
+
this.typedHandlers.set(eventType, new Set());
|
|
94
|
+
}
|
|
95
|
+
const handlers = this.typedHandlers.get(eventType);
|
|
96
|
+
handlers.add(handler);
|
|
97
|
+
return () => { handlers.delete(handler); };
|
|
98
|
+
}
|
|
99
|
+
onAny(handler) {
|
|
100
|
+
this.anyHandlers.add(handler);
|
|
101
|
+
return () => { this.anyHandlers.delete(handler); };
|
|
102
|
+
}
|
|
103
|
+
onStatusChange(handler) {
|
|
104
|
+
this.statusHandlers.add(handler);
|
|
105
|
+
return () => { this.statusHandlers.delete(handler); };
|
|
106
|
+
}
|
|
107
|
+
destroy() {
|
|
108
|
+
this.destroyed = true;
|
|
109
|
+
this.cancelReconnect();
|
|
110
|
+
this.transport.disconnect();
|
|
111
|
+
for (const unsub of this.unsubscribers) {
|
|
112
|
+
unsub();
|
|
113
|
+
}
|
|
114
|
+
this.unsubscribers = [];
|
|
115
|
+
this.typedHandlers.clear();
|
|
116
|
+
this.anyHandlers.clear();
|
|
117
|
+
this.statusHandlers.clear();
|
|
118
|
+
this.log('Destroyed');
|
|
119
|
+
}
|
|
120
|
+
handleEvent(event) {
|
|
121
|
+
// Notify typed handlers
|
|
122
|
+
const handlers = this.typedHandlers.get(event.type);
|
|
123
|
+
if (handlers) {
|
|
124
|
+
for (const handler of handlers) {
|
|
125
|
+
handler(event);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Notify wildcard handlers
|
|
129
|
+
for (const handler of this.anyHandlers) {
|
|
130
|
+
handler(event);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
handleStatusChange(status) {
|
|
134
|
+
if (status === 'connected') {
|
|
135
|
+
this.reconnectAttempts = 0;
|
|
136
|
+
this.log('Connected');
|
|
137
|
+
}
|
|
138
|
+
if ((status === 'error' || status === 'reconnecting') && this.config.autoReconnect) {
|
|
139
|
+
this.scheduleReconnect();
|
|
140
|
+
}
|
|
141
|
+
for (const handler of this.statusHandlers) {
|
|
142
|
+
handler(status);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
handleError(error) {
|
|
146
|
+
this.log('Error:', error.message);
|
|
147
|
+
}
|
|
148
|
+
scheduleReconnect() {
|
|
149
|
+
if (this.destroyed || this.reconnectTimer)
|
|
150
|
+
return;
|
|
151
|
+
if (this.config.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
152
|
+
this.log('Max reconnect attempts reached');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const jitter = Math.random() * 500;
|
|
156
|
+
const delay = Math.min(this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts) + jitter, this.config.maxReconnectDelay);
|
|
157
|
+
this.reconnectAttempts++;
|
|
158
|
+
this.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts})`);
|
|
159
|
+
this.reconnectTimer = setTimeout(() => {
|
|
160
|
+
this.reconnectTimer = null;
|
|
161
|
+
if (!this.destroyed) {
|
|
162
|
+
this.transport.connect(this.config.url);
|
|
163
|
+
}
|
|
164
|
+
}, delay);
|
|
165
|
+
}
|
|
166
|
+
cancelReconnect() {
|
|
167
|
+
if (this.reconnectTimer) {
|
|
168
|
+
clearTimeout(this.reconnectTimer);
|
|
169
|
+
this.reconnectTimer = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
log(...args) {
|
|
173
|
+
if (this.config.debug) {
|
|
174
|
+
console.log('[event-channel]', ...args);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/event-channel Implementation
|
|
3
|
+
*
|
|
4
|
+
* Re-exports API + SPI + concrete implementations.
|
|
5
|
+
*/
|
|
6
|
+
export * from '../api';
|
|
7
|
+
export type { IEventTransport } from '../spi/transport/i-event-transport';
|
|
8
|
+
export type { IRemoteActionFilter } from '../spi/security/i-action-filter';
|
|
9
|
+
export type { EventChannelPluginConfig } from '../spi/config/event-channel-plugin-config';
|
|
10
|
+
export { SseTransport } from './transport/sse-transport';
|
|
11
|
+
export { EventChannel } from './core/event-channel';
|
|
12
|
+
export { ActionWhitelistFilter } from './security/action-whitelist-filter';
|
|
13
|
+
export { eventChannelReducer, type EventChannelState } from './store/event-channel-reducer';
|
|
14
|
+
export { createEventChannelMiddleware, type EventChannelMiddlewareConfig } from './middleware/event-channel-middleware';
|
|
15
|
+
export { createEventChannelPlugin } from './plugin/event-channel-plugin-factory';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/impl/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,QAAQ,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,YAAY,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,YAAY,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AAG1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAG3E,OAAO,EAAE,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAG5F,OAAO,EAAE,4BAA4B,EAAE,KAAK,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AAGxH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/event-channel Implementation
|
|
3
|
+
*
|
|
4
|
+
* Re-exports API + SPI + concrete implementations.
|
|
5
|
+
*/
|
|
6
|
+
// Re-export API and SPI
|
|
7
|
+
export * from '../api';
|
|
8
|
+
// Transport
|
|
9
|
+
export { SseTransport } from './transport/sse-transport';
|
|
10
|
+
// Core
|
|
11
|
+
export { EventChannel } from './core/event-channel';
|
|
12
|
+
// Security
|
|
13
|
+
export { ActionWhitelistFilter } from './security/action-whitelist-filter';
|
|
14
|
+
// Store
|
|
15
|
+
export { eventChannelReducer } from './store/event-channel-reducer';
|
|
16
|
+
// Middleware
|
|
17
|
+
export { createEventChannelMiddleware } from './middleware/event-channel-middleware';
|
|
18
|
+
// Plugin
|
|
19
|
+
export { createEventChannelPlugin } from './plugin/event-channel-plugin-factory';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Middleware
|
|
3
|
+
*
|
|
4
|
+
* Intercepts REMOTE_ACTION_RECEIVED events and dispatches
|
|
5
|
+
* the actual Redux action after checking the security filter.
|
|
6
|
+
*/
|
|
7
|
+
import type { Middleware } from '@reduxjs/toolkit';
|
|
8
|
+
import type { IRemoteActionFilter } from '../../spi/security/i-action-filter';
|
|
9
|
+
export interface EventChannelMiddlewareConfig {
|
|
10
|
+
actionFilter: IRemoteActionFilter;
|
|
11
|
+
debug?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function createEventChannelMiddleware(config: EventChannelMiddlewareConfig): Middleware;
|
|
14
|
+
//# sourceMappingURL=event-channel-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/middleware/event-channel-middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE9E,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,mBAAmB,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,GACnC,UAAU,CA4BZ"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Middleware
|
|
3
|
+
*
|
|
4
|
+
* Intercepts REMOTE_ACTION_RECEIVED events and dispatches
|
|
5
|
+
* the actual Redux action after checking the security filter.
|
|
6
|
+
*/
|
|
7
|
+
import { EventChannelActionTypes } from '../../api/actions/event-channel-actions';
|
|
8
|
+
export function createEventChannelMiddleware(config) {
|
|
9
|
+
return (store) => (next) => (action) => {
|
|
10
|
+
const result = next(action);
|
|
11
|
+
const typedAction = action;
|
|
12
|
+
if (typedAction.type === EventChannelActionTypes.REMOTE_ACTION_RECEIVED) {
|
|
13
|
+
const remoteAction = typedAction.payload;
|
|
14
|
+
if (config.actionFilter.isAllowed(remoteAction)) {
|
|
15
|
+
if (config.debug) {
|
|
16
|
+
console.log('[event-channel] Dispatching remote action:', remoteAction.actionType);
|
|
17
|
+
}
|
|
18
|
+
store.dispatch({
|
|
19
|
+
type: remoteAction.actionType,
|
|
20
|
+
payload: remoteAction.payload,
|
|
21
|
+
meta: {
|
|
22
|
+
...remoteAction.meta,
|
|
23
|
+
remote: true,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else if (config.debug) {
|
|
28
|
+
console.warn('[event-channel] Blocked remote action:', remoteAction.actionType);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Plugin Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a microkernel plugin for the event channel.
|
|
5
|
+
*/
|
|
6
|
+
import type { PluginModule } from '@hamak/microkernel-spi';
|
|
7
|
+
import type { EventChannelPluginConfig } from '../../spi/config/event-channel-plugin-config';
|
|
8
|
+
/**
|
|
9
|
+
* Create an event channel plugin for the microkernel.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { createEventChannelPlugin } from '@hamak/event-channel';
|
|
14
|
+
*
|
|
15
|
+
* host.registerPlugin(
|
|
16
|
+
* 'event-channel',
|
|
17
|
+
* { name: 'event-channel', version: '1.0.0', entry: '', dependsOn: ['store'] },
|
|
18
|
+
* createEventChannelPlugin({
|
|
19
|
+
* url: '/api/events',
|
|
20
|
+
* allowedActionTypes: ['todos/*', 'notification/*'],
|
|
21
|
+
* debug: true,
|
|
22
|
+
* })
|
|
23
|
+
* );
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createEventChannelPlugin(config?: EventChannelPluginConfig): PluginModule;
|
|
27
|
+
//# sourceMappingURL=event-channel-plugin-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel-plugin-factory.d.ts","sourceRoot":"","sources":["../../../src/impl/plugin/event-channel-plugin-factory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC;AAIlF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAC;AAQ7F;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,GAAE,wBAA6B,GACpC,YAAY,CA6Hd"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Plugin Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a microkernel plugin for the event channel.
|
|
5
|
+
*/
|
|
6
|
+
import { EVENT_CHANNEL_TOKEN } from '../../api/tokens/service-tokens';
|
|
7
|
+
import { eventChannelActions } from '../../api/actions/event-channel-actions';
|
|
8
|
+
import { SseTransport } from '../transport/sse-transport';
|
|
9
|
+
import { EventChannel } from '../core/event-channel';
|
|
10
|
+
import { ActionWhitelistFilter } from '../security/action-whitelist-filter';
|
|
11
|
+
import { eventChannelReducer } from '../store/event-channel-reducer';
|
|
12
|
+
import { createEventChannelMiddleware } from '../middleware/event-channel-middleware';
|
|
13
|
+
/**
|
|
14
|
+
* Create an event channel plugin for the microkernel.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createEventChannelPlugin } from '@hamak/event-channel';
|
|
19
|
+
*
|
|
20
|
+
* host.registerPlugin(
|
|
21
|
+
* 'event-channel',
|
|
22
|
+
* { name: 'event-channel', version: '1.0.0', entry: '', dependsOn: ['store'] },
|
|
23
|
+
* createEventChannelPlugin({
|
|
24
|
+
* url: '/api/events',
|
|
25
|
+
* allowedActionTypes: ['todos/*', 'notification/*'],
|
|
26
|
+
* debug: true,
|
|
27
|
+
* })
|
|
28
|
+
* );
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createEventChannelPlugin(config = {}) {
|
|
32
|
+
let channel;
|
|
33
|
+
const debug = config.debug ?? false;
|
|
34
|
+
return {
|
|
35
|
+
async initialize(ctx) {
|
|
36
|
+
// Build action filter
|
|
37
|
+
let actionFilter;
|
|
38
|
+
if (!config.allowedActionTypes) {
|
|
39
|
+
actionFilter = { isAllowed: () => true };
|
|
40
|
+
}
|
|
41
|
+
else if (Array.isArray(config.allowedActionTypes)) {
|
|
42
|
+
actionFilter = new ActionWhitelistFilter(config.allowedActionTypes);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
actionFilter = config.allowedActionTypes;
|
|
46
|
+
}
|
|
47
|
+
// Build transport
|
|
48
|
+
const transport = config.transport ?? new SseTransport();
|
|
49
|
+
// Build channel
|
|
50
|
+
channel = new EventChannel(config, transport);
|
|
51
|
+
// Register in DI
|
|
52
|
+
ctx.provide({ provide: EVENT_CHANNEL_TOKEN, useValue: channel });
|
|
53
|
+
// Register reducer and middleware with store (optional dependency)
|
|
54
|
+
try {
|
|
55
|
+
const STORE_EXTENSIONS_TOKEN = Symbol.for('@hamak/ui-store:StoreExtensionsRegistry');
|
|
56
|
+
const storeExtensions = ctx.resolve(STORE_EXTENSIONS_TOKEN);
|
|
57
|
+
if (storeExtensions && typeof storeExtensions.register === 'function') {
|
|
58
|
+
storeExtensions.register('event-channel', {
|
|
59
|
+
reducers: {
|
|
60
|
+
eventChannel: eventChannelReducer,
|
|
61
|
+
},
|
|
62
|
+
middleware: [{
|
|
63
|
+
id: 'event-channel',
|
|
64
|
+
middleware: createEventChannelMiddleware({ actionFilter, debug }),
|
|
65
|
+
priority: config.middlewarePriority ?? 500,
|
|
66
|
+
plugin: 'event-channel',
|
|
67
|
+
description: 'Dispatches remote actions from backend event stream',
|
|
68
|
+
}],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
if (debug) {
|
|
74
|
+
console.log('[event-channel] Store extensions not available, skipping reducer/middleware registration');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Register commands
|
|
78
|
+
ctx.commands.register('eventChannel.connect', () => channel.connect());
|
|
79
|
+
ctx.commands.register('eventChannel.disconnect', () => channel.disconnect());
|
|
80
|
+
ctx.commands.register('eventChannel.status', () => channel.getStatus());
|
|
81
|
+
},
|
|
82
|
+
async activate(ctx) {
|
|
83
|
+
const url = config.url ?? '/api/events';
|
|
84
|
+
// Wire channel events to hooks and store
|
|
85
|
+
channel.onStatusChange((status) => {
|
|
86
|
+
ctx.hooks.emit('event-channel:status', status);
|
|
87
|
+
// Dispatch connection status to store
|
|
88
|
+
try {
|
|
89
|
+
const STORE_MANAGER_TOKEN = Symbol.for('StoreManager');
|
|
90
|
+
const storeManager = ctx.resolve(STORE_MANAGER_TOKEN);
|
|
91
|
+
if (storeManager) {
|
|
92
|
+
storeManager.dispatch(eventChannelActions.connectionStatusChanged({
|
|
93
|
+
status,
|
|
94
|
+
reconnectAttempts: 0,
|
|
95
|
+
url,
|
|
96
|
+
lastConnectedAt: status === 'connected' ? new Date().toISOString() : undefined,
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Store not available
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
channel.onAny((event) => {
|
|
105
|
+
ctx.hooks.emit('event-channel:event', event);
|
|
106
|
+
// Dispatch to store
|
|
107
|
+
try {
|
|
108
|
+
const STORE_MANAGER_TOKEN = Symbol.for('StoreManager');
|
|
109
|
+
const storeManager = ctx.resolve(STORE_MANAGER_TOKEN);
|
|
110
|
+
if (storeManager) {
|
|
111
|
+
// Store the event
|
|
112
|
+
storeManager.dispatch(eventChannelActions.eventReceived(event));
|
|
113
|
+
// If it's a remote action, dispatch so middleware can process it
|
|
114
|
+
if (event.type === 'remote-action') {
|
|
115
|
+
storeManager.dispatch(eventChannelActions.remoteActionReceived(event.data));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Store not available
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Auto-connect if configured
|
|
124
|
+
if (config.autoConnect !== false) {
|
|
125
|
+
channel.connect();
|
|
126
|
+
}
|
|
127
|
+
ctx.hooks.emit('event-channel:ready', { channel });
|
|
128
|
+
if (debug) {
|
|
129
|
+
console.log('[event-channel] Plugin activated', config.autoConnect !== false ? '(auto-connecting)' : '');
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
async deactivate() {
|
|
133
|
+
channel?.destroy();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Whitelist Filter
|
|
3
|
+
*
|
|
4
|
+
* Filters remote actions based on a whitelist of allowed action type patterns.
|
|
5
|
+
* Supports exact match and simple glob patterns (e.g., 'todos/*').
|
|
6
|
+
*/
|
|
7
|
+
import type { RemoteAction } from '../../api';
|
|
8
|
+
import type { IRemoteActionFilter } from '../../spi/security/i-action-filter';
|
|
9
|
+
export declare class ActionWhitelistFilter implements IRemoteActionFilter {
|
|
10
|
+
private readonly patterns;
|
|
11
|
+
constructor(allowedPatterns: string[]);
|
|
12
|
+
isAllowed(action: RemoteAction): boolean;
|
|
13
|
+
private matches;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=action-whitelist-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-whitelist-filter.d.ts","sourceRoot":"","sources":["../../../src/impl/security/action-whitelist-filter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE9E,qBAAa,qBAAsB,YAAW,mBAAmB;IAC/D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAExB,eAAe,EAAE,MAAM,EAAE;IAIrC,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO;IAIxC,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Whitelist Filter
|
|
3
|
+
*
|
|
4
|
+
* Filters remote actions based on a whitelist of allowed action type patterns.
|
|
5
|
+
* Supports exact match and simple glob patterns (e.g., 'todos/*').
|
|
6
|
+
*/
|
|
7
|
+
export class ActionWhitelistFilter {
|
|
8
|
+
constructor(allowedPatterns) {
|
|
9
|
+
Object.defineProperty(this, "patterns", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: void 0
|
|
14
|
+
});
|
|
15
|
+
this.patterns = allowedPatterns;
|
|
16
|
+
}
|
|
17
|
+
isAllowed(action) {
|
|
18
|
+
return this.patterns.some((pattern) => this.matches(pattern, action.actionType));
|
|
19
|
+
}
|
|
20
|
+
matches(pattern, actionType) {
|
|
21
|
+
// Exact match
|
|
22
|
+
if (pattern === actionType)
|
|
23
|
+
return true;
|
|
24
|
+
// Simple glob: 'prefix/*' matches 'prefix/anything'
|
|
25
|
+
if (pattern.endsWith('/*')) {
|
|
26
|
+
const prefix = pattern.slice(0, -2);
|
|
27
|
+
return actionType.startsWith(prefix + '/') || actionType === prefix;
|
|
28
|
+
}
|
|
29
|
+
// Wildcard-only: '*' matches everything
|
|
30
|
+
if (pattern === '*')
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Reducer
|
|
3
|
+
*
|
|
4
|
+
* Manages connection state and recent events in the Redux store.
|
|
5
|
+
*/
|
|
6
|
+
import type { ConnectionInfo } from '../../api/types/connection-types';
|
|
7
|
+
import type { EventChannelEvent } from '../../api/types/event-types';
|
|
8
|
+
import { type EventChannelAction } from '../../api/actions/event-channel-actions';
|
|
9
|
+
export interface EventChannelState {
|
|
10
|
+
connection: ConnectionInfo;
|
|
11
|
+
recentEvents: EventChannelEvent[];
|
|
12
|
+
totalEventsReceived: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function eventChannelReducer(state: EventChannelState | undefined, action: EventChannelAction | {
|
|
15
|
+
type: string;
|
|
16
|
+
}): EventChannelState;
|
|
17
|
+
//# sourceMappingURL=event-channel-reducer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel-reducer.d.ts","sourceRoot":"","sources":["../../../src/impl/store/event-channel-reducer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAI3G,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,cAAc,CAAC;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAYD,wBAAgB,mBAAmB,CACjC,KAAK,+BAAkC,EACvC,MAAM,EAAE,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C,iBAAiB,CAuBnB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Channel Reducer
|
|
3
|
+
*
|
|
4
|
+
* Manages connection state and recent events in the Redux store.
|
|
5
|
+
*/
|
|
6
|
+
import { EventChannelActionTypes } from '../../api/actions/event-channel-actions';
|
|
7
|
+
const MAX_RECENT_EVENTS = 50;
|
|
8
|
+
const initialState = {
|
|
9
|
+
connection: {
|
|
10
|
+
status: 'disconnected',
|
|
11
|
+
reconnectAttempts: 0,
|
|
12
|
+
url: '',
|
|
13
|
+
},
|
|
14
|
+
recentEvents: [],
|
|
15
|
+
totalEventsReceived: 0,
|
|
16
|
+
};
|
|
17
|
+
export function eventChannelReducer(state = initialState, action) {
|
|
18
|
+
switch (action.type) {
|
|
19
|
+
case EventChannelActionTypes.CONNECTION_STATUS_CHANGED: {
|
|
20
|
+
const { payload } = action;
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
connection: payload,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
case EventChannelActionTypes.EVENT_RECEIVED: {
|
|
27
|
+
const { payload } = action;
|
|
28
|
+
const recentEvents = [payload, ...state.recentEvents].slice(0, MAX_RECENT_EVENTS);
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
recentEvents,
|
|
32
|
+
totalEventsReceived: state.totalEventsReceived + 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE Transport
|
|
3
|
+
*
|
|
4
|
+
* Browser EventSource-based transport for the event channel.
|
|
5
|
+
*/
|
|
6
|
+
import type { EventChannelEvent, ConnectionStatus } from '../../api';
|
|
7
|
+
import type { IEventTransport } from '../../spi';
|
|
8
|
+
export declare class SseTransport implements IEventTransport {
|
|
9
|
+
private eventSource;
|
|
10
|
+
private status;
|
|
11
|
+
private eventHandlers;
|
|
12
|
+
private statusHandlers;
|
|
13
|
+
private errorHandlers;
|
|
14
|
+
connect(url: string): void;
|
|
15
|
+
/** Add a listener for a specific SSE event type */
|
|
16
|
+
addEventType(type: string): void;
|
|
17
|
+
disconnect(): void;
|
|
18
|
+
getStatus(): ConnectionStatus;
|
|
19
|
+
onEvent(handler: (event: EventChannelEvent) => void): () => void;
|
|
20
|
+
onStatusChange(handler: (status: ConnectionStatus) => void): () => void;
|
|
21
|
+
onError(handler: (error: Error) => void): () => void;
|
|
22
|
+
private handleMessage;
|
|
23
|
+
private setStatus;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sse-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-transport.d.ts","sourceRoot":"","sources":["../../../src/impl/transport/sse-transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,qBAAa,YAAa,YAAW,eAAe;IAClD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,aAAa,CAAiD;IACtE,OAAO,CAAC,cAAc,CAAiD;IACvE,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAqC1B,mDAAmD;IACnD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhC,UAAU,IAAI,IAAI;IAQlB,SAAS,IAAI,gBAAgB;IAI7B,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKhE,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvE,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI;IAKpD,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,SAAS;CAOlB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE Transport
|
|
3
|
+
*
|
|
4
|
+
* Browser EventSource-based transport for the event channel.
|
|
5
|
+
*/
|
|
6
|
+
export class SseTransport {
|
|
7
|
+
constructor() {
|
|
8
|
+
Object.defineProperty(this, "eventSource", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: null
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "status", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: 'disconnected'
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "eventHandlers", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: new Set()
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "statusHandlers", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: new Set()
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "errorHandlers", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: new Set()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
connect(url) {
|
|
40
|
+
if (this.eventSource) {
|
|
41
|
+
this.disconnect();
|
|
42
|
+
}
|
|
43
|
+
this.setStatus('connecting');
|
|
44
|
+
this.eventSource = new EventSource(url);
|
|
45
|
+
this.eventSource.onopen = () => {
|
|
46
|
+
this.setStatus('connected');
|
|
47
|
+
};
|
|
48
|
+
this.eventSource.onerror = () => {
|
|
49
|
+
// EventSource auto-reconnects on error; we report the status change
|
|
50
|
+
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
|
51
|
+
this.setStatus('error');
|
|
52
|
+
for (const handler of this.errorHandlers) {
|
|
53
|
+
handler(new Error('EventSource connection closed'));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (this.eventSource?.readyState === EventSource.CONNECTING) {
|
|
57
|
+
this.setStatus('reconnecting');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
// Listen for all named events via a generic message listener
|
|
61
|
+
this.eventSource.onmessage = (messageEvent) => {
|
|
62
|
+
this.handleMessage(messageEvent);
|
|
63
|
+
};
|
|
64
|
+
// Also listen for named events by intercepting addEventListener
|
|
65
|
+
// EventSource fires named events that onmessage doesn't catch
|
|
66
|
+
// We use a proxy pattern: listen on the most common event types
|
|
67
|
+
// The server sends events with `event: <type>`, which EventSource routes to named listeners
|
|
68
|
+
// We'll use the 'message' event as fallback and add specific listeners for known types
|
|
69
|
+
}
|
|
70
|
+
/** Add a listener for a specific SSE event type */
|
|
71
|
+
addEventType(type) {
|
|
72
|
+
if (!this.eventSource)
|
|
73
|
+
return;
|
|
74
|
+
this.eventSource.addEventListener(type, ((event) => {
|
|
75
|
+
this.handleMessage(event);
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
disconnect() {
|
|
79
|
+
if (this.eventSource) {
|
|
80
|
+
this.eventSource.close();
|
|
81
|
+
this.eventSource = null;
|
|
82
|
+
}
|
|
83
|
+
this.setStatus('disconnected');
|
|
84
|
+
}
|
|
85
|
+
getStatus() {
|
|
86
|
+
return this.status;
|
|
87
|
+
}
|
|
88
|
+
onEvent(handler) {
|
|
89
|
+
this.eventHandlers.add(handler);
|
|
90
|
+
return () => { this.eventHandlers.delete(handler); };
|
|
91
|
+
}
|
|
92
|
+
onStatusChange(handler) {
|
|
93
|
+
this.statusHandlers.add(handler);
|
|
94
|
+
return () => { this.statusHandlers.delete(handler); };
|
|
95
|
+
}
|
|
96
|
+
onError(handler) {
|
|
97
|
+
this.errorHandlers.add(handler);
|
|
98
|
+
return () => { this.errorHandlers.delete(handler); };
|
|
99
|
+
}
|
|
100
|
+
handleMessage(messageEvent) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(messageEvent.data);
|
|
103
|
+
for (const handler of this.eventHandlers) {
|
|
104
|
+
handler(parsed);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Non-JSON message (e.g., initial connection message), ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
setStatus(newStatus) {
|
|
112
|
+
if (this.status === newStatus)
|
|
113
|
+
return;
|
|
114
|
+
this.status = newStatus;
|
|
115
|
+
for (const handler of this.statusHandlers) {
|
|
116
|
+
handler(newStatus);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the event channel plugin.
|
|
3
|
+
*/
|
|
4
|
+
import type { IEventTransport } from '../transport/i-event-transport';
|
|
5
|
+
import type { IRemoteActionFilter } from '../security/i-action-filter';
|
|
6
|
+
export interface EventChannelPluginConfig {
|
|
7
|
+
/**
|
|
8
|
+
* URL of the SSE endpoint.
|
|
9
|
+
* @default '/api/events'
|
|
10
|
+
*/
|
|
11
|
+
url?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to connect automatically on plugin activation.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
autoConnect?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Enable auto-reconnect on connection loss.
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
autoReconnect?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Max reconnect attempts (0 = infinite).
|
|
24
|
+
* @default 0
|
|
25
|
+
*/
|
|
26
|
+
maxReconnectAttempts?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Base reconnect delay in ms (exponential backoff applied).
|
|
29
|
+
* @default 1000
|
|
30
|
+
*/
|
|
31
|
+
reconnectDelay?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum reconnect delay in ms.
|
|
34
|
+
* @default 30000
|
|
35
|
+
*/
|
|
36
|
+
maxReconnectDelay?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Allowed action types for remote dispatch.
|
|
39
|
+
* String array supports glob patterns (e.g., 'todos/*').
|
|
40
|
+
* Or provide a custom IRemoteActionFilter.
|
|
41
|
+
* If not provided, ALL remote actions are allowed.
|
|
42
|
+
*/
|
|
43
|
+
allowedActionTypes?: string[] | IRemoteActionFilter;
|
|
44
|
+
/**
|
|
45
|
+
* Custom transport (defaults to SseTransport).
|
|
46
|
+
*/
|
|
47
|
+
transport?: IEventTransport;
|
|
48
|
+
/**
|
|
49
|
+
* Priority for the event-channel middleware.
|
|
50
|
+
* @default 500
|
|
51
|
+
*/
|
|
52
|
+
middlewarePriority?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Enable debug logging.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=event-channel-plugin-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-channel-plugin-config.d.ts","sourceRoot":"","sources":["../../../src/spi/config/event-channel-plugin-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC;IAEpD;;OAEG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/event-channel SPI
|
|
3
|
+
*
|
|
4
|
+
* Extension points and provider interfaces.
|
|
5
|
+
*/
|
|
6
|
+
export * from '../api';
|
|
7
|
+
export type { IEventTransport } from './transport/i-event-transport';
|
|
8
|
+
export type { IRemoteActionFilter } from './security/i-action-filter';
|
|
9
|
+
export type { EventChannelPluginConfig } from './config/event-channel-plugin-config';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spi/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,QAAQ,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,YAAY,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security filter for remote actions.
|
|
3
|
+
*/
|
|
4
|
+
import type { RemoteAction } from '../../api';
|
|
5
|
+
/**
|
|
6
|
+
* Determines which action types the backend is allowed to dispatch.
|
|
7
|
+
*/
|
|
8
|
+
export interface IRemoteActionFilter {
|
|
9
|
+
/** Return true if this remote action should be dispatched */
|
|
10
|
+
isAllowed(action: RemoteAction): boolean;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=i-action-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-action-filter.d.ts","sourceRoot":"","sources":["../../../src/spi/security/i-action-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC;CAC1C"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport abstraction for the event channel.
|
|
3
|
+
* SSE for Phase 1, WebSocket for Phase 2.
|
|
4
|
+
*/
|
|
5
|
+
import type { EventChannelEvent, ConnectionStatus } from '../../api';
|
|
6
|
+
export interface IEventTransport {
|
|
7
|
+
/** Open the transport connection */
|
|
8
|
+
connect(url: string): void;
|
|
9
|
+
/** Close the transport connection */
|
|
10
|
+
disconnect(): void;
|
|
11
|
+
/** Current connection status */
|
|
12
|
+
getStatus(): ConnectionStatus;
|
|
13
|
+
/** Register event handler (called for each incoming event) */
|
|
14
|
+
onEvent(handler: (event: EventChannelEvent) => void): () => void;
|
|
15
|
+
/** Register status change handler */
|
|
16
|
+
onStatusChange(handler: (status: ConnectionStatus) => void): () => void;
|
|
17
|
+
/** Register error handler */
|
|
18
|
+
onError(handler: (error: Error) => void): () => void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=i-event-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-event-transport.d.ts","sourceRoot":"","sources":["../../../src/spi/transport/i-event-transport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,qCAAqC;IACrC,UAAU,IAAI,IAAI,CAAC;IAEnB,gCAAgC;IAChC,SAAS,IAAI,gBAAgB,CAAC;IAE9B,8DAA8D;IAC9D,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAEjE,qCAAqC;IACrC,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAExE,6BAA6B;IAC7B,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACtD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hamak/event-channel",
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Reactive event channel with SSE transport and remote action dispatch for microkernel-based applications",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/amah/app-framework.git",
|
|
16
|
+
"directory": "packages/event-channel"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.lib.json",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./api": {
|
|
34
|
+
"types": "./dist/api/index.d.ts",
|
|
35
|
+
"import": "./dist/api/index.js",
|
|
36
|
+
"default": "./dist/api/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./spi": {
|
|
39
|
+
"types": "./dist/spi/index.d.ts",
|
|
40
|
+
"import": "./dist/spi/index.js",
|
|
41
|
+
"default": "./dist/spi/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"typesVersions": {
|
|
45
|
+
"*": {
|
|
46
|
+
"api": [
|
|
47
|
+
"./dist/api/index.d.ts"
|
|
48
|
+
],
|
|
49
|
+
"spi": [
|
|
50
|
+
"./dist/spi/index.d.ts"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@hamak/microkernel-spi": "*",
|
|
56
|
+
"@reduxjs/toolkit": "^2.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"typescript": "~5.4.0",
|
|
60
|
+
"vitest": "^2.0.0",
|
|
61
|
+
"@types/node": "^20.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|