@connexis/transport-sse 1.0.1 → 1.0.2

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/index.d.mts CHANGED
@@ -4,6 +4,7 @@ interface SSETransportOptions {
4
4
  publishUrl?: string;
5
5
  publish?: (topic: string, data: any) => Promise<void>;
6
6
  withCredentials?: boolean;
7
+ authToken?: string | (() => string | Promise<string>);
7
8
  }
8
9
  declare class SSETransport implements Transport {
9
10
  private url;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ interface SSETransportOptions {
4
4
  publishUrl?: string;
5
5
  publish?: (topic: string, data: any) => Promise<void>;
6
6
  withCredentials?: boolean;
7
+ authToken?: string | (() => string | Promise<string>);
7
8
  }
8
9
  declare class SSETransport implements Transport {
9
10
  private url;
package/dist/index.js CHANGED
@@ -44,9 +44,15 @@ var SSETransport = class _SSETransport {
44
44
  return;
45
45
  }
46
46
  this.updateState("connecting");
47
+ let connectionUrl = this.url;
48
+ if (this.options.authToken) {
49
+ const token = typeof this.options.authToken === "function" ? await this.options.authToken() : this.options.authToken;
50
+ const separator = connectionUrl.includes("?") ? "&" : "?";
51
+ connectionUrl = `${connectionUrl}${separator}token=${encodeURIComponent(token)}`;
52
+ }
47
53
  return new Promise((resolve, reject) => {
48
54
  try {
49
- this.eventSource = new EventSource(this.url, {
55
+ this.eventSource = new EventSource(connectionUrl, {
50
56
  withCredentials: this.options.withCredentials
51
57
  });
52
58
  } catch (err) {
@@ -95,11 +101,16 @@ var SSETransport = class _SSETransport {
95
101
  return;
96
102
  }
97
103
  if (this.options.publishUrl) {
104
+ const headers = {
105
+ "Content-Type": "application/json"
106
+ };
107
+ if (this.options.authToken) {
108
+ const token = typeof this.options.authToken === "function" ? await this.options.authToken() : this.options.authToken;
109
+ headers["Authorization"] = `Bearer ${token}`;
110
+ }
98
111
  const response = await fetch(this.options.publishUrl, {
99
112
  method: "POST",
100
- headers: {
101
- "Content-Type": "application/json"
102
- },
113
+ headers,
103
114
  body: JSON.stringify({ topic, data })
104
115
  });
105
116
  if (!response.ok) {
package/dist/index.mjs CHANGED
@@ -20,9 +20,15 @@ var SSETransport = class _SSETransport {
20
20
  return;
21
21
  }
22
22
  this.updateState("connecting");
23
+ let connectionUrl = this.url;
24
+ if (this.options.authToken) {
25
+ const token = typeof this.options.authToken === "function" ? await this.options.authToken() : this.options.authToken;
26
+ const separator = connectionUrl.includes("?") ? "&" : "?";
27
+ connectionUrl = `${connectionUrl}${separator}token=${encodeURIComponent(token)}`;
28
+ }
23
29
  return new Promise((resolve, reject) => {
24
30
  try {
25
- this.eventSource = new EventSource(this.url, {
31
+ this.eventSource = new EventSource(connectionUrl, {
26
32
  withCredentials: this.options.withCredentials
27
33
  });
28
34
  } catch (err) {
@@ -71,11 +77,16 @@ var SSETransport = class _SSETransport {
71
77
  return;
72
78
  }
73
79
  if (this.options.publishUrl) {
80
+ const headers = {
81
+ "Content-Type": "application/json"
82
+ };
83
+ if (this.options.authToken) {
84
+ const token = typeof this.options.authToken === "function" ? await this.options.authToken() : this.options.authToken;
85
+ headers["Authorization"] = `Bearer ${token}`;
86
+ }
74
87
  const response = await fetch(this.options.publishUrl, {
75
88
  method: "POST",
76
- headers: {
77
- "Content-Type": "application/json"
78
- },
89
+ headers,
79
90
  body: JSON.stringify({ topic, data })
80
91
  });
81
92
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connexis/transport-sse",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Server-Sent Events (SSE) transport for @connexis",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@connexis/core": "1.0.1"
16
+ "@connexis/core": "1.0.2"
17
17
  },
18
18
  "devDependencies": {
19
19
  "typescript": "^5.5.2"
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { SSETransport } from '../index.js';
3
+
4
+ // Setup Mock EventSource class
5
+ class MockEventSource {
6
+ public static lastInstance: MockEventSource | null = null;
7
+ public onopen: (() => void) | null = null;
8
+ public onerror: (() => void) | null = null;
9
+ public onmessage: ((e: any) => void) | null = null;
10
+ public url: string;
11
+ public options: any;
12
+
13
+ constructor(url: string, options?: any) {
14
+ this.url = url;
15
+ this.options = options;
16
+ MockEventSource.lastInstance = this;
17
+
18
+ setTimeout(() => {
19
+ this.onopen?.();
20
+ }, 10);
21
+ }
22
+
23
+ close() {}
24
+ }
25
+
26
+ describe('SSETransport Auth', () => {
27
+ beforeEach(() => {
28
+ vi.stubGlobal('EventSource', MockEventSource);
29
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, status: 200 }));
30
+ MockEventSource.lastInstance = null;
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.unstubAllGlobals();
35
+ vi.restoreAllMocks();
36
+ });
37
+
38
+ it('should append token as query parameter for static authToken', async () => {
39
+ const transport = new SSETransport('http://localhost/sse', {
40
+ authToken: 'static-sse-token'
41
+ });
42
+
43
+ await transport.connect();
44
+
45
+ expect(MockEventSource.lastInstance).not.toBeNull();
46
+ expect(MockEventSource.lastInstance!.url).toBe('http://localhost/sse?token=static-sse-token');
47
+
48
+ await transport.disconnect();
49
+ });
50
+
51
+ it('should resolve and append token for dynamic async getAuthToken callback', async () => {
52
+ const mockGetToken = vi.fn().mockResolvedValue('dynamic-sse-token');
53
+ const transport = new SSETransport('http://localhost/sse', {
54
+ authToken: mockGetToken
55
+ });
56
+
57
+ await transport.connect();
58
+
59
+ expect(mockGetToken).toHaveBeenCalled();
60
+ expect(MockEventSource.lastInstance).not.toBeNull();
61
+ expect(MockEventSource.lastInstance!.url).toBe('http://localhost/sse?token=dynamic-sse-token');
62
+
63
+ await transport.disconnect();
64
+ });
65
+
66
+ it('should include Authorization Bearer header when publishing via HTTP publishUrl', async () => {
67
+ const transport = new SSETransport('http://localhost/sse', {
68
+ publishUrl: 'http://localhost/publish',
69
+ authToken: 'publish-sse-token'
70
+ });
71
+
72
+ await transport.connect();
73
+ await transport.publish('test_topic', { value: 123 });
74
+
75
+ expect(globalThis.fetch).toHaveBeenCalledWith(
76
+ 'http://localhost/publish',
77
+ expect.objectContaining({
78
+ method: 'POST',
79
+ headers: expect.objectContaining({
80
+ 'Authorization': 'Bearer publish-sse-token',
81
+ 'Content-Type': 'application/json'
82
+ }),
83
+ body: JSON.stringify({ topic: 'test_topic', data: { value: 123 } })
84
+ })
85
+ );
86
+
87
+ await transport.disconnect();
88
+ });
89
+ });
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export interface SSETransportOptions {
4
4
  publishUrl?: string;
5
5
  publish?: (topic: string, data: any) => Promise<void>;
6
6
  withCredentials?: boolean;
7
+ authToken?: string | (() => string | Promise<string>);
7
8
  }
8
9
 
9
10
  export class SSETransport implements Transport {
@@ -31,9 +32,18 @@ export class SSETransport implements Transport {
31
32
 
32
33
  this.updateState('connecting');
33
34
 
35
+ let connectionUrl = this.url;
36
+ if (this.options.authToken) {
37
+ const token = typeof this.options.authToken === 'function'
38
+ ? await this.options.authToken()
39
+ : this.options.authToken;
40
+ const separator = connectionUrl.includes('?') ? '&' : '?';
41
+ connectionUrl = `${connectionUrl}${separator}token=${encodeURIComponent(token)}`;
42
+ }
43
+
34
44
  return new Promise<void>((resolve, reject) => {
35
45
  try {
36
- this.eventSource = new EventSource(this.url, {
46
+ this.eventSource = new EventSource(connectionUrl, {
37
47
  withCredentials: this.options.withCredentials
38
48
  });
39
49
  } catch (err) {
@@ -94,11 +104,20 @@ export class SSETransport implements Transport {
94
104
  }
95
105
 
96
106
  if (this.options.publishUrl) {
107
+ const headers: Record<string, string> = {
108
+ 'Content-Type': 'application/json'
109
+ };
110
+
111
+ if (this.options.authToken) {
112
+ const token = typeof this.options.authToken === 'function'
113
+ ? await this.options.authToken()
114
+ : this.options.authToken;
115
+ headers['Authorization'] = `Bearer ${token}`;
116
+ }
117
+
97
118
  const response = await fetch(this.options.publishUrl, {
98
119
  method: 'POST',
99
- headers: {
100
- 'Content-Type': 'application/json'
101
- },
120
+ headers,
102
121
  body: JSON.stringify({ topic, data })
103
122
  });
104
123
  if (!response.ok) {