@forge/realtime 0.1.1 → 0.2.0-experimental-3ed5db1
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 +22 -0
- package/out/productContext.d.ts +7 -0
- package/out/productContext.d.ts.map +1 -0
- package/out/productContext.js +9 -0
- package/out/publish.d.ts +2 -0
- package/out/publish.d.ts.map +1 -1
- package/out/publish.js +13 -1
- package/package.json +2 -2
- package/out/__test__/publish.test.d.ts +0 -2
- package/out/__test__/publish.test.d.ts.map +0 -1
- package/out/__test__/publish.test.js +0 -157
- package/out/__test__/runtime.test.d.ts +0 -2
- package/out/__test__/runtime.test.d.ts.map +0 -1
- package/out/__test__/runtime.test.js +0 -12
- package/out/__test__/signRealtimeToken.test.d.ts +0 -2
- package/out/__test__/signRealtimeToken.test.d.ts.map +0 -1
- package/out/__test__/signRealtimeToken.test.js +0 -80
- package/out/__test__/utils.d.ts +0 -3
- package/out/__test__/utils.d.ts.map +0 -1
- package/out/__test__/utils.js +0 -34
- package/src/__test__/__snapshots__/publish.test.ts.snap +0 -157
- package/src/__test__/__snapshots__/signRealtimeToken.test.ts.snap +0 -58
- package/src/__test__/publish.test.ts +0 -203
- package/src/__test__/runtime.test.ts +0 -13
- package/src/__test__/signRealtimeToken.test.ts +0 -96
- package/src/__test__/utils.ts +0 -33
- package/src/index.ts +0 -2
- package/src/publish.ts +0 -160
- package/src/runtime.ts +0 -9
- package/src/signRealtimeToken.ts +0 -90
- package/src/utils.ts +0 -12
- package/tsconfig.json +0 -18
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import * as runtime from '../runtime';
|
|
2
|
-
import { publish, publishGlobal } from '../publish';
|
|
3
|
-
import { FORGE_RUNTIME } from './utils';
|
|
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
|
-
|
|
35
|
-
describe('publish', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
jest.restoreAllMocks();
|
|
38
|
-
jest.resetAllMocks();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should publish an event with FCT in header', async () => {
|
|
42
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
43
|
-
|
|
44
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
45
|
-
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
46
|
-
status: 200,
|
|
47
|
-
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
51
|
-
|
|
52
|
-
const response = await publish(MOCK_CHANNEL, MOCK_EVENT_PAYLOAD_STRING, MOCK_PUBLISH_OPTIONS);
|
|
53
|
-
|
|
54
|
-
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
55
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should publish an event when the payload is an object', async () => {
|
|
59
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
60
|
-
|
|
61
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
62
|
-
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
63
|
-
status: 200,
|
|
64
|
-
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
68
|
-
|
|
69
|
-
const response = await publish(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
70
|
-
|
|
71
|
-
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
72
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
76
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
77
|
-
|
|
78
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
79
|
-
text: jest.fn().mockResolvedValue('Error occurred'),
|
|
80
|
-
status: 502,
|
|
81
|
-
headers: { get: jest.fn().mockReturnValue('UPSTREAM_FAILURE') }
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
85
|
-
|
|
86
|
-
const response = publish(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
87
|
-
|
|
88
|
-
await expect(response).rejects.toThrow(
|
|
89
|
-
'Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE'
|
|
90
|
-
);
|
|
91
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
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
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('publishGlobal', () => {
|
|
121
|
-
beforeEach(() => {
|
|
122
|
-
jest.restoreAllMocks();
|
|
123
|
-
jest.resetAllMocks();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should publish an event', async () => {
|
|
127
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
128
|
-
|
|
129
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
130
|
-
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
131
|
-
status: 200,
|
|
132
|
-
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
136
|
-
|
|
137
|
-
const response = await publishGlobal(MOCK_CHANNEL, MOCK_EVENT_PAYLOAD_STRING, MOCK_PUBLISH_OPTIONS);
|
|
138
|
-
|
|
139
|
-
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
140
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should publish an event when the payload is an object', async () => {
|
|
144
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
145
|
-
|
|
146
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
147
|
-
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
148
|
-
status: 200,
|
|
149
|
-
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
153
|
-
|
|
154
|
-
const response = await publishGlobal(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
155
|
-
|
|
156
|
-
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
157
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
161
|
-
jest.spyOn(runtime, '__getRuntime').mockReturnValue(FORGE_RUNTIME);
|
|
162
|
-
|
|
163
|
-
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
164
|
-
text: jest.fn().mockResolvedValue('Error occurred'),
|
|
165
|
-
status: 502,
|
|
166
|
-
headers: { get: jest.fn().mockReturnValue('UPSTREAM_FAILURE') }
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
(global as any).__forge_fetch__ = mockForgeFetch;
|
|
170
|
-
|
|
171
|
-
const response = publishGlobal(MOCK_CHANNEL, JSON.stringify(MOCK_EVENT_PAYLOAD_OBJECT), MOCK_PUBLISH_OPTIONS);
|
|
172
|
-
|
|
173
|
-
await expect(response).rejects.toThrow(
|
|
174
|
-
'Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE'
|
|
175
|
-
);
|
|
176
|
-
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
177
|
-
});
|
|
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
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { __getRuntime } from '../runtime';
|
|
2
|
-
import { FORGE_RUNTIME } from './utils';
|
|
3
|
-
|
|
4
|
-
describe('__getRuntime', () => {
|
|
5
|
-
it('should return the runtime object', async () => {
|
|
6
|
-
(global as any).__forge_runtime__ = FORGE_RUNTIME;
|
|
7
|
-
|
|
8
|
-
const runtime = __getRuntime();
|
|
9
|
-
|
|
10
|
-
expect(runtime.appContext.appId).toBe('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa');
|
|
11
|
-
expect(runtime.contextAri).toBe('ari:cloud:jira::site/ffffffff-ffff-ffff-ffff-ffffffffffff');
|
|
12
|
-
});
|
|
13
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { ForgeRuntime } from '@forge/api';
|
|
2
|
-
import { NoMetrics } from '@atlassian/metrics-interface';
|
|
3
|
-
|
|
4
|
-
export const FORGE_RUNTIME: ForgeRuntime = {
|
|
5
|
-
container: { runtime: 'node', region: 'us-west-2' },
|
|
6
|
-
proxy: { url: 'https://foo', token: 'token' },
|
|
7
|
-
contextAri: 'ari:cloud:jira::site/ffffffff-ffff-ffff-ffff-ffffffffffff',
|
|
8
|
-
appContext: {
|
|
9
|
-
appId: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
10
|
-
environmentId: 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
|
11
|
-
environmentType: 'DEVELOPMENT',
|
|
12
|
-
invocationId: '33333',
|
|
13
|
-
installationId: 'iiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii',
|
|
14
|
-
appVersion: '1.2.3',
|
|
15
|
-
functionKey: 'functionKey',
|
|
16
|
-
moduleType: 'moduleType',
|
|
17
|
-
moduleKey: 'moduleKey',
|
|
18
|
-
license: { isActive: true }
|
|
19
|
-
},
|
|
20
|
-
tracing: {
|
|
21
|
-
traceId: 'traceId',
|
|
22
|
-
spanId: 'spanId'
|
|
23
|
-
},
|
|
24
|
-
lambdaContext: {
|
|
25
|
-
awsRequestId: '123',
|
|
26
|
-
getRemainingTimeInMillis: jest.fn()
|
|
27
|
-
},
|
|
28
|
-
metrics: new NoMetrics(),
|
|
29
|
-
realtime: {
|
|
30
|
-
contextToken: 'my.context.token'
|
|
31
|
-
},
|
|
32
|
-
featureFlags: jest.fn()
|
|
33
|
-
};
|
package/src/index.ts
DELETED
package/src/publish.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { __getRuntime } from './runtime';
|
|
2
|
-
import { handleProxyResponseErrors, prodEnvErrorMessage } from './utils';
|
|
3
|
-
|
|
4
|
-
const graphqlBody = `mutation publishRealtimeChannel(
|
|
5
|
-
$installationId: ID!
|
|
6
|
-
$name: String!
|
|
7
|
-
$payload: String!
|
|
8
|
-
$isGlobal: Boolean
|
|
9
|
-
$token: String
|
|
10
|
-
){
|
|
11
|
-
ecosystem {
|
|
12
|
-
publishRealtimeChannel(
|
|
13
|
-
installationId: $installationId
|
|
14
|
-
name: $name
|
|
15
|
-
payload: $payload
|
|
16
|
-
isGlobal: $isGlobal
|
|
17
|
-
token: $token
|
|
18
|
-
) {
|
|
19
|
-
eventId,
|
|
20
|
-
eventTimestamp
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}`;
|
|
24
|
-
|
|
25
|
-
interface PublishOptions {
|
|
26
|
-
token?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const publish = async (channelName: string, eventPayload: string, options?: PublishOptions) => {
|
|
30
|
-
const { appContext, realtime } = __getRuntime();
|
|
31
|
-
|
|
32
|
-
if (appContext?.environmentType === 'PRODUCTION') {
|
|
33
|
-
throw new Error(prodEnvErrorMessage);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
-
const response = await (global as any).__forge_fetch__(
|
|
38
|
-
{
|
|
39
|
-
type: 'realtime'
|
|
40
|
-
},
|
|
41
|
-
'/',
|
|
42
|
-
{
|
|
43
|
-
method: 'POST',
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
query: graphqlBody,
|
|
46
|
-
variables: {
|
|
47
|
-
installationId: appContext.installationId,
|
|
48
|
-
name: channelName,
|
|
49
|
-
payload: eventPayload,
|
|
50
|
-
isGlobal: false,
|
|
51
|
-
token: options?.token
|
|
52
|
-
}
|
|
53
|
-
}),
|
|
54
|
-
errors: [],
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
'x-forge-context-token': realtime?.contextToken
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
handleProxyResponseErrors(response);
|
|
63
|
-
|
|
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
|
-
};
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export const publishGlobal = async (channelName: string, eventPayload: string, options?: PublishOptions) => {
|
|
97
|
-
const { appContext } = __getRuntime();
|
|
98
|
-
|
|
99
|
-
if (appContext?.environmentType === 'PRODUCTION') {
|
|
100
|
-
throw new Error(prodEnvErrorMessage);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
-
const response = await (global as any).__forge_fetch__(
|
|
105
|
-
{
|
|
106
|
-
type: 'realtime'
|
|
107
|
-
},
|
|
108
|
-
'/',
|
|
109
|
-
{
|
|
110
|
-
method: 'POST',
|
|
111
|
-
body: JSON.stringify({
|
|
112
|
-
query: graphqlBody,
|
|
113
|
-
variables: {
|
|
114
|
-
installationId: appContext.installationId,
|
|
115
|
-
name: channelName,
|
|
116
|
-
payload: eventPayload,
|
|
117
|
-
isGlobal: true,
|
|
118
|
-
token: options?.token
|
|
119
|
-
}
|
|
120
|
-
}),
|
|
121
|
-
errors: [],
|
|
122
|
-
headers: {
|
|
123
|
-
'Content-Type': 'application/json'
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
handleProxyResponseErrors(response);
|
|
129
|
-
|
|
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
|
-
};
|
|
160
|
-
};
|
package/src/runtime.ts
DELETED
package/src/signRealtimeToken.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { __getRuntime } from './runtime';
|
|
2
|
-
import { handleProxyResponseErrors, prodEnvErrorMessage } from './utils';
|
|
3
|
-
|
|
4
|
-
const graphqlBody = `mutation signRealtimeToken(
|
|
5
|
-
$channelName: String!
|
|
6
|
-
$claims: JSON!
|
|
7
|
-
){
|
|
8
|
-
ecosystem {
|
|
9
|
-
signRealtimeToken(
|
|
10
|
-
channelName: $channelName
|
|
11
|
-
claims: $claims
|
|
12
|
-
) {
|
|
13
|
-
errors {
|
|
14
|
-
message
|
|
15
|
-
extensions {
|
|
16
|
-
errorType
|
|
17
|
-
statusCode
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
forgeRealtimeToken {
|
|
21
|
-
jwt
|
|
22
|
-
expiresAt
|
|
23
|
-
}
|
|
24
|
-
success
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}`;
|
|
28
|
-
|
|
29
|
-
export const signRealtimeToken = async (channelName: string, claims: any) => {
|
|
30
|
-
const { appContext } = __getRuntime();
|
|
31
|
-
|
|
32
|
-
if (appContext?.environmentType === 'PRODUCTION') {
|
|
33
|
-
throw new Error(prodEnvErrorMessage);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
-
const response = await (global as any).__forge_fetch__(
|
|
38
|
-
{
|
|
39
|
-
type: 'realtime'
|
|
40
|
-
},
|
|
41
|
-
'/',
|
|
42
|
-
{
|
|
43
|
-
method: 'POST',
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
query: graphqlBody,
|
|
46
|
-
variables: {
|
|
47
|
-
channelName,
|
|
48
|
-
claims
|
|
49
|
-
}
|
|
50
|
-
}),
|
|
51
|
-
errors: [],
|
|
52
|
-
headers: {
|
|
53
|
-
'Content-Type': 'application/json'
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
handleProxyResponseErrors(response);
|
|
59
|
-
|
|
60
|
-
const awaitedResponse = await response.json();
|
|
61
|
-
|
|
62
|
-
const { data, errors } = awaitedResponse;
|
|
63
|
-
|
|
64
|
-
if (errors && errors.length > 0) {
|
|
65
|
-
return {
|
|
66
|
-
token: null,
|
|
67
|
-
expiresAt: null,
|
|
68
|
-
errors
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!data) {
|
|
73
|
-
return {
|
|
74
|
-
token: null,
|
|
75
|
-
expiresAt: null,
|
|
76
|
-
errors: [
|
|
77
|
-
{
|
|
78
|
-
message: 'Error signing realtime token.'
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const { jwt, expiresAt } = data.ecosystem.signRealtimeToken.forgeRealtimeToken;
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
token: jwt,
|
|
88
|
-
expiresAt
|
|
89
|
-
};
|
|
90
|
-
};
|
package/src/utils.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { ProxyRequestError } from '@forge/api';
|
|
2
|
-
|
|
3
|
-
const getForgeProxyError = (response: Response) => response.headers.get('forge-proxy-error');
|
|
4
|
-
export const handleProxyResponseErrors = (response: Response): void => {
|
|
5
|
-
const errorReason = getForgeProxyError(response);
|
|
6
|
-
if (errorReason) {
|
|
7
|
-
throw new ProxyRequestError(response.status, errorReason);
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const prodEnvErrorMessage =
|
|
12
|
-
'Forge realtime usage is restricted to Forge apps in a non-production environment. Please see https://developer.atlassian.com/platform/forge/cli-reference/environments/ for reference on Forge app environments.';
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig-base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"lib": [
|
|
5
|
-
"es2017",
|
|
6
|
-
"DOM"
|
|
7
|
-
],
|
|
8
|
-
"outDir": "./out",
|
|
9
|
-
"rootDir": "src",
|
|
10
|
-
"composite": true,
|
|
11
|
-
"moduleResolution": "node"
|
|
12
|
-
},
|
|
13
|
-
"references": [
|
|
14
|
-
{
|
|
15
|
-
"path": "../forge-api"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|