@forge/realtime 0.0.7-next.0 → 0.1.0-experimental-3f68b81
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/CHANGELOG.md +30 -0
- package/out/__test__/publish.test.js +81 -18
- package/out/__test__/signRealtimeToken.test.d.ts +2 -0
- package/out/__test__/signRealtimeToken.test.d.ts.map +1 -0
- package/out/__test__/signRealtimeToken.test.js +80 -0
- package/out/__test__/utils.d.ts.map +1 -1
- package/out/__test__/utils.js +22 -1
- package/out/index.d.ts +1 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +3 -1
- package/out/publish.d.ts +22 -4
- package/out/publish.d.ts.map +1 -1
- package/out/publish.js +66 -22
- package/out/signRealtimeToken.d.ts +10 -0
- package/out/signRealtimeToken.d.ts.map +1 -0
- package/out/signRealtimeToken.js +78 -0
- package/out/utils.d.ts +3 -0
- package/out/utils.d.ts.map +1 -0
- package/out/utils.js +13 -0
- package/package.json +2 -2
- package/src/__test__/__snapshots__/publish.test.ts.snap +51 -6
- package/src/__test__/__snapshots__/signRealtimeToken.test.ts.snap +58 -0
- package/src/__test__/publish.test.ts +95 -19
- package/src/__test__/signRealtimeToken.test.ts +96 -0
- package/src/__test__/utils.ts +22 -1
- package/src/index.ts +1 -0
- package/src/publish.ts +74 -18
- package/src/signRealtimeToken.ts +90 -0
- package/src/utils.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -8,7 +8,8 @@ exports[`publish should publish an event when the payload is an object 1`] = `
|
|
|
8
8
|
},
|
|
9
9
|
"/",
|
|
10
10
|
{
|
|
11
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":false}}",
|
|
11
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":false,"token":"my-token"}}",
|
|
12
|
+
"errors": [],
|
|
12
13
|
"headers": {
|
|
13
14
|
"Content-Type": "application/json",
|
|
14
15
|
"x-forge-context-token": "my.context.token",
|
|
@@ -27,7 +28,28 @@ exports[`publish should publish an event with FCT in header 1`] = `
|
|
|
27
28
|
},
|
|
28
29
|
"/",
|
|
29
30
|
{
|
|
30
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"this is an event payload","isGlobal":false}}",
|
|
31
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"this is an event payload","isGlobal":false,"token":"my-token"}}",
|
|
32
|
+
"errors": [],
|
|
33
|
+
"headers": {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
"x-forge-context-token": "my.context.token",
|
|
36
|
+
},
|
|
37
|
+
"method": "POST",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
]
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
exports[`publish should return null eventId and eventTimestamp when the response has errors 1`] = `
|
|
44
|
+
[
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
"type": "realtime",
|
|
48
|
+
},
|
|
49
|
+
"/",
|
|
50
|
+
{
|
|
51
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":false}}",
|
|
52
|
+
"errors": [],
|
|
31
53
|
"headers": {
|
|
32
54
|
"Content-Type": "application/json",
|
|
33
55
|
"x-forge-context-token": "my.context.token",
|
|
@@ -46,7 +68,8 @@ exports[`publish should throw an error when Forge Outbound Proxy returns an erro
|
|
|
46
68
|
},
|
|
47
69
|
"/",
|
|
48
70
|
{
|
|
49
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":false}}",
|
|
71
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":false,"token":"my-token"}}",
|
|
72
|
+
"errors": [],
|
|
50
73
|
"headers": {
|
|
51
74
|
"Content-Type": "application/json",
|
|
52
75
|
"x-forge-context-token": "my.context.token",
|
|
@@ -65,7 +88,8 @@ exports[`publishGlobal should publish an event 1`] = `
|
|
|
65
88
|
},
|
|
66
89
|
"/",
|
|
67
90
|
{
|
|
68
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"this is an event payload","isGlobal":true}}",
|
|
91
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"this is an event payload","isGlobal":true,"token":"my-token"}}",
|
|
92
|
+
"errors": [],
|
|
69
93
|
"headers": {
|
|
70
94
|
"Content-Type": "application/json",
|
|
71
95
|
},
|
|
@@ -83,7 +107,27 @@ exports[`publishGlobal should publish an event when the payload is an object 1`]
|
|
|
83
107
|
},
|
|
84
108
|
"/",
|
|
85
109
|
{
|
|
86
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":true}}",
|
|
110
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":true,"token":"my-token"}}",
|
|
111
|
+
"errors": [],
|
|
112
|
+
"headers": {
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
},
|
|
115
|
+
"method": "POST",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
]
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
exports[`publishGlobal should return null eventId and eventTimestamp when the response has errors 1`] = `
|
|
122
|
+
[
|
|
123
|
+
[
|
|
124
|
+
{
|
|
125
|
+
"type": "realtime",
|
|
126
|
+
},
|
|
127
|
+
"/",
|
|
128
|
+
{
|
|
129
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":true}}",
|
|
130
|
+
"errors": [],
|
|
87
131
|
"headers": {
|
|
88
132
|
"Content-Type": "application/json",
|
|
89
133
|
},
|
|
@@ -101,7 +145,8 @@ exports[`publishGlobal should throw an error when Forge Outbound Proxy returns a
|
|
|
101
145
|
},
|
|
102
146
|
"/",
|
|
103
147
|
{
|
|
104
|
-
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":true}}",
|
|
148
|
+
"body": "{"query":"mutation publishRealtimeChannel(\\n $installationId: ID!\\n $name: String!\\n $payload: String!\\n $isGlobal: Boolean\\n $token: String\\n){\\n ecosystem {\\n publishRealtimeChannel(\\n installationId: $installationId\\n name: $name\\n payload: $payload\\n isGlobal: $isGlobal\\n token: $token\\n ) {\\n eventId,\\n eventTimestamp\\n }\\n }\\n}","variables":{"installationId":"iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii","name":"my-channel","payload":"{\\"value\\":\\"this is a test payload\\",\\"funLevel\\":100}","isGlobal":true,"token":"my-token"}}",
|
|
149
|
+
"errors": [],
|
|
105
150
|
"headers": {
|
|
106
151
|
"Content-Type": "application/json",
|
|
107
152
|
},
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`signRealtimeToken should send a signRealtimeToken request and recieve a jwt and expiresAt 1`] = `
|
|
4
|
+
[
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"type": "realtime",
|
|
8
|
+
},
|
|
9
|
+
"/",
|
|
10
|
+
{
|
|
11
|
+
"body": "{"query":"mutation signRealtimeToken(\\n $channelName: String!\\n $claims: JSON!\\n){\\n ecosystem {\\n signRealtimeToken(\\n channelName: $channelName\\n claims: $claims\\n ) {\\n errors {\\n message\\n extensions {\\n errorType\\n statusCode\\n }\\n }\\n forgeRealtimeToken {\\n jwt\\n expiresAt\\n }\\n success\\n }\\n }\\n}","variables":{"channelName":"my-channel","claims":{"test-key":"test-value"}}}",
|
|
12
|
+
"errors": [],
|
|
13
|
+
"headers": {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
"method": "POST",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
]
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
exports[`signRealtimeToken should send generic error when errors are returned 1`] = `
|
|
23
|
+
[
|
|
24
|
+
[
|
|
25
|
+
{
|
|
26
|
+
"type": "realtime",
|
|
27
|
+
},
|
|
28
|
+
"/",
|
|
29
|
+
{
|
|
30
|
+
"body": "{"query":"mutation signRealtimeToken(\\n $channelName: String!\\n $claims: JSON!\\n){\\n ecosystem {\\n signRealtimeToken(\\n channelName: $channelName\\n claims: $claims\\n ) {\\n errors {\\n message\\n extensions {\\n errorType\\n statusCode\\n }\\n }\\n forgeRealtimeToken {\\n jwt\\n expiresAt\\n }\\n success\\n }\\n }\\n}","variables":{"channelName":"my-channel","claims":{"test-key":"test-value"}}}",
|
|
31
|
+
"errors": [],
|
|
32
|
+
"headers": {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
"method": "POST",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
]
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
exports[`signRealtimeToken should throw an error when Forge Outbound Proxy returns an error 1`] = `
|
|
42
|
+
[
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
"type": "realtime",
|
|
46
|
+
},
|
|
47
|
+
"/",
|
|
48
|
+
{
|
|
49
|
+
"body": "{"query":"mutation signRealtimeToken(\\n $channelName: String!\\n $claims: JSON!\\n){\\n ecosystem {\\n signRealtimeToken(\\n channelName: $channelName\\n claims: $claims\\n ) {\\n errors {\\n message\\n extensions {\\n errorType\\n statusCode\\n }\\n }\\n forgeRealtimeToken {\\n jwt\\n expiresAt\\n }\\n success\\n }\\n }\\n}","variables":{"channelName":"my-channel","claims":{"test-key":"test-value"}}}",
|
|
50
|
+
"errors": [],
|
|
51
|
+
"headers": {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
},
|
|
54
|
+
"method": "POST",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
]
|
|
58
|
+
`;
|
|
@@ -2,6 +2,36 @@ import * as runtime from '../runtime';
|
|
|
2
2
|
import { publish, publishGlobal } from '../publish';
|
|
3
3
|
import { FORGE_RUNTIME } from './utils';
|
|
4
4
|
|
|
5
|
+
const TEST_EVENT_ID = 'event-id';
|
|
6
|
+
const TEST_EVENT_TIMESTAMP = '1234567890';
|
|
7
|
+
const MOCK_CHANNEL = 'my-channel';
|
|
8
|
+
const MOCK_PUBLISH_OPTIONS = { token: 'my-token' };
|
|
9
|
+
const MOCK_EVENT_PAYLOAD_STRING = 'this is an event payload';
|
|
10
|
+
const MOCK_EVENT_PAYLOAD_OBJECT = { value: 'this is a test payload', funLevel: 100 };
|
|
11
|
+
|
|
12
|
+
const MOCK_FETCH_RESPONSE = {
|
|
13
|
+
errors: [],
|
|
14
|
+
data: {
|
|
15
|
+
ecosystem: {
|
|
16
|
+
publishRealtimeChannel: {
|
|
17
|
+
eventId: TEST_EVENT_ID,
|
|
18
|
+
eventTimestamp: TEST_EVENT_TIMESTAMP
|
|
19
|
+
},
|
|
20
|
+
success: true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MOCK_ERRORS = [
|
|
26
|
+
{
|
|
27
|
+
message: 'Error message',
|
|
28
|
+
extensions: {
|
|
29
|
+
errorType: 'Error type',
|
|
30
|
+
statusCode: 500
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
|
|
5
35
|
describe('publish', () => {
|
|
6
36
|
beforeEach(() => {
|
|
7
37
|
jest.restoreAllMocks();
|
|
@@ -12,16 +42,16 @@ describe('publish', () => {
|
|
|
12
42
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
13
43
|
|
|
14
44
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
15
|
-
|
|
45
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
16
46
|
status: 200,
|
|
17
47
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
18
48
|
});
|
|
19
49
|
|
|
20
50
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
21
51
|
|
|
22
|
-
const response = await publish(
|
|
52
|
+
const response = await publish(MOCK_CHANNEL, MOCK_EVENT_PAYLOAD_STRING, MOCK_PUBLISH_OPTIONS);
|
|
23
53
|
|
|
24
|
-
expect(response
|
|
54
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
25
55
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
26
56
|
});
|
|
27
57
|
|
|
@@ -29,17 +59,16 @@ describe('publish', () => {
|
|
|
29
59
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
30
60
|
|
|
31
61
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
32
|
-
|
|
62
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
33
63
|
status: 200,
|
|
34
64
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
35
65
|
});
|
|
36
66
|
|
|
37
67
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
38
68
|
|
|
39
|
-
const
|
|
40
|
-
const response = await publish('my-channel', JSON.stringify(payload));
|
|
69
|
+
const response = await publish(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
41
70
|
|
|
42
|
-
expect(response
|
|
71
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
43
72
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
44
73
|
});
|
|
45
74
|
|
|
@@ -54,14 +83,38 @@ describe('publish', () => {
|
|
|
54
83
|
|
|
55
84
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
56
85
|
|
|
57
|
-
const
|
|
58
|
-
const response = publish('my-channel', JSON.stringify(payload));
|
|
86
|
+
const response = publish(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
59
87
|
|
|
60
88
|
await expect(response).rejects.toThrow(
|
|
61
89
|
'Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE'
|
|
62
90
|
);
|
|
63
91
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
64
92
|
});
|
|
93
|
+
|
|
94
|
+
it('should return null eventId and eventTimestamp when the response has errors', async () => {
|
|
95
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
96
|
+
|
|
97
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
98
|
+
json: jest.fn().mockResolvedValue({
|
|
99
|
+
errors: MOCK_ERRORS,
|
|
100
|
+
data: {
|
|
101
|
+
ecosystem: {
|
|
102
|
+
publishRealtimeChannel: null,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
status: 200,
|
|
107
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
111
|
+
|
|
112
|
+
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
113
|
+
const response = await publish('my-channel', JSON.stringify(payload));
|
|
114
|
+
|
|
115
|
+
expect(response).toEqual({ eventId: null, eventTimestamp: null, errors: MOCK_ERRORS });
|
|
116
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
117
|
+
});
|
|
65
118
|
});
|
|
66
119
|
|
|
67
120
|
describe('publishGlobal', () => {
|
|
@@ -74,16 +127,16 @@ describe('publishGlobal', () => {
|
|
|
74
127
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
75
128
|
|
|
76
129
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
77
|
-
|
|
130
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
78
131
|
status: 200,
|
|
79
132
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
80
133
|
});
|
|
81
134
|
|
|
82
135
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
83
136
|
|
|
84
|
-
const response = await publishGlobal(
|
|
137
|
+
const response = await publishGlobal(MOCK_CHANNEL, MOCK_EVENT_PAYLOAD_STRING, MOCK_PUBLISH_OPTIONS);
|
|
85
138
|
|
|
86
|
-
expect(response
|
|
139
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
87
140
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
88
141
|
});
|
|
89
142
|
|
|
@@ -91,17 +144,16 @@ describe('publishGlobal', () => {
|
|
|
91
144
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
92
145
|
|
|
93
146
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
94
|
-
|
|
147
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
95
148
|
status: 200,
|
|
96
149
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
97
150
|
});
|
|
98
151
|
|
|
99
152
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
100
153
|
|
|
101
|
-
const
|
|
102
|
-
const response = await publishGlobal('my-channel', JSON.stringify(payload));
|
|
154
|
+
const response = await publishGlobal(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
103
155
|
|
|
104
|
-
expect(response
|
|
156
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
105
157
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
106
158
|
});
|
|
107
159
|
|
|
@@ -116,12 +168,36 @@ describe('publishGlobal', () => {
|
|
|
116
168
|
|
|
117
169
|
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
118
170
|
|
|
119
|
-
const
|
|
120
|
-
const response = publishGlobal('my-channel', JSON.stringify(payload));
|
|
171
|
+
const response = publishGlobal(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
121
172
|
|
|
122
173
|
await expect(response).rejects.toThrow(
|
|
123
174
|
'Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE'
|
|
124
175
|
);
|
|
125
176
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
126
177
|
});
|
|
127
|
-
|
|
178
|
+
|
|
179
|
+
it('should return null eventId and eventTimestamp when the response has errors', async () => {
|
|
180
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
181
|
+
|
|
182
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
183
|
+
json: jest.fn().mockResolvedValue({
|
|
184
|
+
errors: MOCK_ERRORS,
|
|
185
|
+
data: {
|
|
186
|
+
ecosystem: {
|
|
187
|
+
publishRealtimeChannel: null,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}),
|
|
191
|
+
status: 200,
|
|
192
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
196
|
+
|
|
197
|
+
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
198
|
+
const response = await publishGlobal('my-channel', JSON.stringify(payload));
|
|
199
|
+
|
|
200
|
+
expect(response).toEqual({ eventId: null, eventTimestamp: null, errors: MOCK_ERRORS });
|
|
201
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as runtime from '../runtime';
|
|
2
|
+
import { signRealtimeToken } from '../signRealtimeToken';
|
|
3
|
+
import { FORGE_RUNTIME } from './utils';
|
|
4
|
+
|
|
5
|
+
const TEST_TOKEN = 'jwt-token';
|
|
6
|
+
const TEST_EXPIRES_AT = '1234567890';
|
|
7
|
+
const MOCK_ERRORS = [
|
|
8
|
+
{
|
|
9
|
+
message: 'Error message',
|
|
10
|
+
extensions: {
|
|
11
|
+
errorType: 'Error type',
|
|
12
|
+
statusCode: 500
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
describe('signRealtimeToken', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.restoreAllMocks();
|
|
20
|
+
jest.resetAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should send a signRealtimeToken request and recieve a jwt and expiresAt', async () => {
|
|
24
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
25
|
+
|
|
26
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
27
|
+
json: jest.fn().mockResolvedValue({
|
|
28
|
+
errors: [],
|
|
29
|
+
data: {
|
|
30
|
+
ecosystem: {
|
|
31
|
+
signRealtimeToken: {
|
|
32
|
+
errors: [],
|
|
33
|
+
forgeRealtimeToken: {
|
|
34
|
+
jwt: TEST_TOKEN,
|
|
35
|
+
expiresAt: TEST_EXPIRES_AT
|
|
36
|
+
},
|
|
37
|
+
success: true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
status: 200,
|
|
43
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
47
|
+
|
|
48
|
+
const response = await signRealtimeToken('my-channel', { 'test-key': 'test-value' });
|
|
49
|
+
|
|
50
|
+
expect(response).toEqual({ token: TEST_TOKEN, expiresAt: TEST_EXPIRES_AT });
|
|
51
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
55
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
56
|
+
|
|
57
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
58
|
+
text: jest.fn().mockResolvedValue('Error occurred'),
|
|
59
|
+
status: 502,
|
|
60
|
+
headers: { get: jest.fn().mockReturnValue('UPSTREAM_FAILURE') }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
64
|
+
|
|
65
|
+
const response = signRealtimeToken('my-channel', { 'test-key': 'test-value' });
|
|
66
|
+
|
|
67
|
+
await expect(response).rejects.toThrow(
|
|
68
|
+
'Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE'
|
|
69
|
+
);
|
|
70
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should send generic error when errors are returned', async () => {
|
|
74
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
75
|
+
|
|
76
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
77
|
+
json: jest.fn().mockResolvedValue({
|
|
78
|
+
errors: MOCK_ERRORS,
|
|
79
|
+
data: {
|
|
80
|
+
ecosystem: {
|
|
81
|
+
signRealtimeToken: null,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
status: 200,
|
|
86
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
90
|
+
|
|
91
|
+
const response = await signRealtimeToken('my-channel', { 'test-key': 'test-value' });
|
|
92
|
+
|
|
93
|
+
expect(response).toEqual({ token: null, expiresAt: null, errors: MOCK_ERRORS });
|
|
94
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/__test__/utils.ts
CHANGED
|
@@ -15,7 +15,28 @@ export const FORGE_RUNTIME: ForgeRuntime = {
|
|
|
15
15
|
functionKey: 'functionKey',
|
|
16
16
|
moduleType: 'moduleType',
|
|
17
17
|
moduleKey: 'moduleKey',
|
|
18
|
-
license: { isActive: true }
|
|
18
|
+
license: { isActive: true },
|
|
19
|
+
permissions: {
|
|
20
|
+
scopes: ['read:confluence-content.summary', 'write:confluence-content'],
|
|
21
|
+
content: {
|
|
22
|
+
scripts: [],
|
|
23
|
+
styles: []
|
|
24
|
+
},
|
|
25
|
+
external: {
|
|
26
|
+
fetch: {
|
|
27
|
+
backend: [],
|
|
28
|
+
client: []
|
|
29
|
+
},
|
|
30
|
+
fonts: [],
|
|
31
|
+
frames: [],
|
|
32
|
+
navigation: [],
|
|
33
|
+
images: [],
|
|
34
|
+
media: [],
|
|
35
|
+
scripts: [],
|
|
36
|
+
styles: []
|
|
37
|
+
},
|
|
38
|
+
format: () => ''
|
|
39
|
+
}
|
|
19
40
|
},
|
|
20
41
|
tracing: {
|
|
21
42
|
traceId: 'traceId',
|
package/src/index.ts
CHANGED
package/src/publish.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import { ProxyRequestError } from '@forge/api';
|
|
2
|
-
|
|
3
1
|
import { __getRuntime } from './runtime';
|
|
4
|
-
|
|
5
|
-
export const getForgeProxyError = (response: Response) => response.headers.get('forge-proxy-error');
|
|
6
|
-
export const handleProxyResponseErrors = (response: Response): void => {
|
|
7
|
-
const errorReason = getForgeProxyError(response);
|
|
8
|
-
if (errorReason) {
|
|
9
|
-
throw new ProxyRequestError(response.status, errorReason);
|
|
10
|
-
}
|
|
11
|
-
};
|
|
2
|
+
import { handleProxyResponseErrors, prodEnvErrorMessage } from './utils';
|
|
12
3
|
|
|
13
4
|
const graphqlBody = `mutation publishRealtimeChannel(
|
|
14
5
|
$installationId: ID!
|
|
15
6
|
$name: String!
|
|
16
7
|
$payload: String!
|
|
17
8
|
$isGlobal: Boolean
|
|
9
|
+
$token: String
|
|
18
10
|
){
|
|
19
11
|
ecosystem {
|
|
20
12
|
publishRealtimeChannel(
|
|
@@ -22,6 +14,7 @@ const graphqlBody = `mutation publishRealtimeChannel(
|
|
|
22
14
|
name: $name
|
|
23
15
|
payload: $payload
|
|
24
16
|
isGlobal: $isGlobal
|
|
17
|
+
token: $token
|
|
25
18
|
) {
|
|
26
19
|
eventId,
|
|
27
20
|
eventTimestamp
|
|
@@ -29,10 +22,11 @@ const graphqlBody = `mutation publishRealtimeChannel(
|
|
|
29
22
|
}
|
|
30
23
|
}`;
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
interface PublishOptions {
|
|
26
|
+
token?: string;
|
|
27
|
+
}
|
|
34
28
|
|
|
35
|
-
export const publish = async (channelName: string, eventPayload: string) => {
|
|
29
|
+
export const publish = async (channelName: string, eventPayload: string, options?: PublishOptions) => {
|
|
36
30
|
const { appContext, realtime } = __getRuntime();
|
|
37
31
|
|
|
38
32
|
if (appContext?.environmentType === 'PRODUCTION') {
|
|
@@ -53,9 +47,11 @@ export const publish = async (channelName: string, eventPayload: string) => {
|
|
|
53
47
|
installationId: appContext.installationId,
|
|
54
48
|
name: channelName,
|
|
55
49
|
payload: eventPayload,
|
|
56
|
-
isGlobal: false
|
|
50
|
+
isGlobal: false,
|
|
51
|
+
token: options?.token
|
|
57
52
|
}
|
|
58
53
|
}),
|
|
54
|
+
errors: [],
|
|
59
55
|
headers: {
|
|
60
56
|
'Content-Type': 'application/json',
|
|
61
57
|
'x-forge-context-token': realtime?.contextToken
|
|
@@ -65,10 +61,39 @@ export const publish = async (channelName: string, eventPayload: string) => {
|
|
|
65
61
|
|
|
66
62
|
handleProxyResponseErrors(response);
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
const awaitedResponse = await response.json();
|
|
65
|
+
|
|
66
|
+
const { data, errors } = awaitedResponse;
|
|
67
|
+
|
|
68
|
+
if (errors && errors.length > 0) {
|
|
69
|
+
return {
|
|
70
|
+
eventId: null,
|
|
71
|
+
eventTimestamp: null,
|
|
72
|
+
errors
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!data) {
|
|
77
|
+
return {
|
|
78
|
+
eventId: null,
|
|
79
|
+
eventTimestamp: null,
|
|
80
|
+
errors: [
|
|
81
|
+
{
|
|
82
|
+
message: 'Error publishing event to channel.'
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { eventId, eventTimestamp } = awaitedResponse.data.ecosystem.publishRealtimeChannel;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
eventId,
|
|
92
|
+
eventTimestamp
|
|
93
|
+
};
|
|
69
94
|
};
|
|
70
95
|
|
|
71
|
-
export const publishGlobal = async (channelName: string, eventPayload: string) => {
|
|
96
|
+
export const publishGlobal = async (channelName: string, eventPayload: string, options?: PublishOptions) => {
|
|
72
97
|
const { appContext } = __getRuntime();
|
|
73
98
|
|
|
74
99
|
if (appContext?.environmentType === 'PRODUCTION') {
|
|
@@ -89,9 +114,11 @@ export const publishGlobal = async (channelName: string, eventPayload: string) =
|
|
|
89
114
|
installationId: appContext.installationId,
|
|
90
115
|
name: channelName,
|
|
91
116
|
payload: eventPayload,
|
|
92
|
-
isGlobal: true
|
|
117
|
+
isGlobal: true,
|
|
118
|
+
token: options?.token
|
|
93
119
|
}
|
|
94
120
|
}),
|
|
121
|
+
errors: [],
|
|
95
122
|
headers: {
|
|
96
123
|
'Content-Type': 'application/json'
|
|
97
124
|
}
|
|
@@ -100,5 +127,34 @@ export const publishGlobal = async (channelName: string, eventPayload: string) =
|
|
|
100
127
|
|
|
101
128
|
handleProxyResponseErrors(response);
|
|
102
129
|
|
|
103
|
-
|
|
130
|
+
const awaitedResponse = await response.json();
|
|
131
|
+
|
|
132
|
+
const { data, errors } = awaitedResponse;
|
|
133
|
+
|
|
134
|
+
if (errors && errors.length > 0) {
|
|
135
|
+
return {
|
|
136
|
+
eventId: null,
|
|
137
|
+
eventTimestamp: null,
|
|
138
|
+
errors
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!data) {
|
|
143
|
+
return {
|
|
144
|
+
eventId: null,
|
|
145
|
+
eventTimestamp: null,
|
|
146
|
+
errors: [
|
|
147
|
+
{
|
|
148
|
+
message: 'Error publishing global event to channel.'
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const { eventId, eventTimestamp } = data.ecosystem.publishRealtimeChannel;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
eventId,
|
|
158
|
+
eventTimestamp
|
|
159
|
+
};
|
|
104
160
|
};
|