@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 +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -4
- package/dist/index.mjs +15 -4
- package/package.json +2 -2
- package/src/__tests__/sse-transport.test.ts +89 -0
- package/src/index.ts +23 -4
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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(
|
|
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) {
|