@forge/realtime 0.0.7-next.0 → 0.1.0-next.1
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 +6 -0
- package/out/__test__/publish.test.js +71 -8
- 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/index.d.ts +1 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +3 -1
- package/out/publish.d.ts +18 -4
- package/out/publish.d.ts.map +1 -1
- package/out/publish.js +58 -18
- 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 +45 -0
- package/src/__test__/__snapshots__/signRealtimeToken.test.ts.snap +58 -0
- package/src/__test__/publish.test.ts +84 -8
- package/src/__test__/signRealtimeToken.test.ts +96 -0
- package/src/index.ts +1 -0
- package/src/publish.ts +63 -15
- package/src/signRealtimeToken.ts +90 -0
- package/src/utils.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const runtime = tslib_1.__importStar(require("../runtime"));
|
|
5
5
|
const publish_1 = require("../publish");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
|
+
const TEST_EVENT_ID = 'event-id';
|
|
8
|
+
const TEST_EVENT_TIMESTAMP = '1234567890';
|
|
9
|
+
const MOCK_FETCH_RESPONSE = {
|
|
10
|
+
errors: [],
|
|
11
|
+
data: {
|
|
12
|
+
ecosystem: {
|
|
13
|
+
publishRealtimeChannel: {
|
|
14
|
+
eventId: TEST_EVENT_ID,
|
|
15
|
+
eventTimestamp: TEST_EVENT_TIMESTAMP
|
|
16
|
+
},
|
|
17
|
+
success: true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const MOCK_ERRORS = [
|
|
22
|
+
{
|
|
23
|
+
message: 'Error message',
|
|
24
|
+
extensions: {
|
|
25
|
+
errorType: 'Error type',
|
|
26
|
+
statusCode: 500
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
];
|
|
7
30
|
describe('publish', () => {
|
|
8
31
|
beforeEach(() => {
|
|
9
32
|
jest.restoreAllMocks();
|
|
@@ -12,26 +35,26 @@ describe('publish', () => {
|
|
|
12
35
|
it('should publish an event with FCT in header', async () => {
|
|
13
36
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
14
37
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
15
|
-
|
|
38
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
16
39
|
status: 200,
|
|
17
40
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
18
41
|
});
|
|
19
42
|
global.__forge_fetch__ = mockForgeFetch;
|
|
20
43
|
const response = await (0, publish_1.publish)('my-channel', 'this is an event payload');
|
|
21
|
-
expect(response
|
|
44
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
22
45
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
23
46
|
});
|
|
24
47
|
it('should publish an event when the payload is an object', async () => {
|
|
25
48
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
26
49
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
27
|
-
|
|
50
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
28
51
|
status: 200,
|
|
29
52
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
30
53
|
});
|
|
31
54
|
global.__forge_fetch__ = mockForgeFetch;
|
|
32
55
|
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
33
56
|
const response = await (0, publish_1.publish)('my-channel', JSON.stringify(payload));
|
|
34
|
-
expect(response
|
|
57
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
35
58
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
36
59
|
});
|
|
37
60
|
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
@@ -47,6 +70,26 @@ describe('publish', () => {
|
|
|
47
70
|
await expect(response).rejects.toThrow('Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE');
|
|
48
71
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
49
72
|
});
|
|
73
|
+
it('should return null eventId and eventTimestamp when the response has errors', async () => {
|
|
74
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
75
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
76
|
+
json: jest.fn().mockResolvedValue({
|
|
77
|
+
errors: MOCK_ERRORS,
|
|
78
|
+
data: {
|
|
79
|
+
ecosystem: {
|
|
80
|
+
publishRealtimeChannel: null,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
status: 200,
|
|
85
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
86
|
+
});
|
|
87
|
+
global.__forge_fetch__ = mockForgeFetch;
|
|
88
|
+
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
89
|
+
const response = await (0, publish_1.publish)('my-channel', JSON.stringify(payload));
|
|
90
|
+
expect(response).toEqual({ eventId: null, eventTimestamp: null, errors: MOCK_ERRORS });
|
|
91
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
92
|
+
});
|
|
50
93
|
});
|
|
51
94
|
describe('publishGlobal', () => {
|
|
52
95
|
beforeEach(() => {
|
|
@@ -56,26 +99,26 @@ describe('publishGlobal', () => {
|
|
|
56
99
|
it('should publish an event', async () => {
|
|
57
100
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
58
101
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
59
|
-
|
|
102
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
60
103
|
status: 200,
|
|
61
104
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
62
105
|
});
|
|
63
106
|
global.__forge_fetch__ = mockForgeFetch;
|
|
64
107
|
const response = await (0, publish_1.publishGlobal)('my-channel', 'this is an event payload');
|
|
65
|
-
expect(response
|
|
108
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
66
109
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
67
110
|
});
|
|
68
111
|
it('should publish an event when the payload is an object', async () => {
|
|
69
112
|
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
70
113
|
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
71
|
-
|
|
114
|
+
json: jest.fn().mockResolvedValue(MOCK_FETCH_RESPONSE),
|
|
72
115
|
status: 200,
|
|
73
116
|
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
74
117
|
});
|
|
75
118
|
global.__forge_fetch__ = mockForgeFetch;
|
|
76
119
|
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
77
120
|
const response = await (0, publish_1.publishGlobal)('my-channel', JSON.stringify(payload));
|
|
78
|
-
expect(response
|
|
121
|
+
expect(response).toEqual({ eventId: TEST_EVENT_ID, eventTimestamp: TEST_EVENT_TIMESTAMP });
|
|
79
122
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
80
123
|
});
|
|
81
124
|
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
@@ -91,4 +134,24 @@ describe('publishGlobal', () => {
|
|
|
91
134
|
await expect(response).rejects.toThrow('Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE');
|
|
92
135
|
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
93
136
|
});
|
|
137
|
+
it('should return null eventId and eventTimestamp when the response has errors', async () => {
|
|
138
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
139
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
140
|
+
json: jest.fn().mockResolvedValue({
|
|
141
|
+
errors: MOCK_ERRORS,
|
|
142
|
+
data: {
|
|
143
|
+
ecosystem: {
|
|
144
|
+
publishRealtimeChannel: null,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
status: 200,
|
|
149
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
150
|
+
});
|
|
151
|
+
global.__forge_fetch__ = mockForgeFetch;
|
|
152
|
+
const payload = { value: 'this is a test payload', funLevel: 100 };
|
|
153
|
+
const response = await (0, publish_1.publishGlobal)('my-channel', JSON.stringify(payload));
|
|
154
|
+
expect(response).toEqual({ eventId: null, eventTimestamp: null, errors: MOCK_ERRORS });
|
|
155
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
156
|
+
});
|
|
94
157
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signRealtimeToken.test.d.ts","sourceRoot":"","sources":["../../src/__test__/signRealtimeToken.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const runtime = tslib_1.__importStar(require("../runtime"));
|
|
5
|
+
const signRealtimeToken_1 = require("../signRealtimeToken");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
const TEST_TOKEN = 'jwt-token';
|
|
8
|
+
const TEST_EXPIRES_AT = '1234567890';
|
|
9
|
+
const MOCK_ERRORS = [
|
|
10
|
+
{
|
|
11
|
+
message: 'Error message',
|
|
12
|
+
extensions: {
|
|
13
|
+
errorType: 'Error type',
|
|
14
|
+
statusCode: 500
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
describe('signRealtimeToken', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.restoreAllMocks();
|
|
21
|
+
jest.resetAllMocks();
|
|
22
|
+
});
|
|
23
|
+
it('should send a signRealtimeToken request and recieve a jwt and expiresAt', async () => {
|
|
24
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
25
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
26
|
+
json: jest.fn().mockResolvedValue({
|
|
27
|
+
errors: [],
|
|
28
|
+
data: {
|
|
29
|
+
ecosystem: {
|
|
30
|
+
signRealtimeToken: {
|
|
31
|
+
errors: [],
|
|
32
|
+
forgeRealtimeToken: {
|
|
33
|
+
jwt: TEST_TOKEN,
|
|
34
|
+
expiresAt: TEST_EXPIRES_AT
|
|
35
|
+
},
|
|
36
|
+
success: true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
43
|
+
});
|
|
44
|
+
global.__forge_fetch__ = mockForgeFetch;
|
|
45
|
+
const response = await (0, signRealtimeToken_1.signRealtimeToken)('my-channel', { 'test-key': 'test-value' });
|
|
46
|
+
expect(response).toEqual({ jwt: TEST_TOKEN, expiresAt: TEST_EXPIRES_AT });
|
|
47
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
48
|
+
});
|
|
49
|
+
it('should throw an error when Forge Outbound Proxy returns an error', async () => {
|
|
50
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
51
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
52
|
+
text: jest.fn().mockResolvedValue('Error occurred'),
|
|
53
|
+
status: 502,
|
|
54
|
+
headers: { get: jest.fn().mockReturnValue('UPSTREAM_FAILURE') }
|
|
55
|
+
});
|
|
56
|
+
global.__forge_fetch__ = mockForgeFetch;
|
|
57
|
+
const response = (0, signRealtimeToken_1.signRealtimeToken)('my-channel', { 'test-key': 'test-value' });
|
|
58
|
+
await expect(response).rejects.toThrow('Forge platform failed to process runtime HTTP request - 502 - UPSTREAM_FAILURE');
|
|
59
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
it('should send generic error when errors are returned', async () => {
|
|
62
|
+
jest.spyOn(runtime, '__getRuntime').mockReturnValue(utils_1.FORGE_RUNTIME);
|
|
63
|
+
const mockForgeFetch = jest.fn().mockResolvedValue({
|
|
64
|
+
json: jest.fn().mockResolvedValue({
|
|
65
|
+
errors: MOCK_ERRORS,
|
|
66
|
+
data: {
|
|
67
|
+
ecosystem: {
|
|
68
|
+
signRealtimeToken: null,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
status: 200,
|
|
73
|
+
headers: { get: jest.fn().mockReturnValue(undefined) }
|
|
74
|
+
});
|
|
75
|
+
global.__forge_fetch__ = mockForgeFetch;
|
|
76
|
+
const response = await (0, signRealtimeToken_1.signRealtimeToken)('my-channel', { 'test-key': 'test-value' });
|
|
77
|
+
expect(response).toEqual({ jwt: null, expiresAt: null, errors: MOCK_ERRORS });
|
|
78
|
+
expect(mockForgeFetch.mock.calls).toMatchSnapshot();
|
|
79
|
+
});
|
|
80
|
+
});
|
package/out/index.d.ts
CHANGED
package/out/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/out/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.publishGlobal = exports.publish = void 0;
|
|
3
|
+
exports.signRealtimeToken = exports.publishGlobal = exports.publish = void 0;
|
|
4
4
|
var publish_1 = require("./publish");
|
|
5
5
|
Object.defineProperty(exports, "publish", { enumerable: true, get: function () { return publish_1.publish; } });
|
|
6
6
|
Object.defineProperty(exports, "publishGlobal", { enumerable: true, get: function () { return publish_1.publishGlobal; } });
|
|
7
|
+
var signRealtimeToken_1 = require("./signRealtimeToken");
|
|
8
|
+
Object.defineProperty(exports, "signRealtimeToken", { enumerable: true, get: function () { return signRealtimeToken_1.signRealtimeToken; } });
|
package/out/publish.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export declare const publish: (channelName: string, eventPayload: string) => Promise<{
|
|
2
|
+
eventId: null;
|
|
3
|
+
eventTimestamp: null;
|
|
4
|
+
errors: any;
|
|
5
|
+
} | {
|
|
6
|
+
eventId: any;
|
|
7
|
+
eventTimestamp: any;
|
|
8
|
+
errors?: undefined;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const publishGlobal: (channelName: string, eventPayload: string) => Promise<{
|
|
11
|
+
eventId: null;
|
|
12
|
+
eventTimestamp: null;
|
|
13
|
+
errors: any;
|
|
14
|
+
} | {
|
|
15
|
+
eventId: any;
|
|
16
|
+
eventTimestamp: any;
|
|
17
|
+
errors?: undefined;
|
|
18
|
+
}>;
|
|
5
19
|
//# sourceMappingURL=publish.d.ts.map
|
package/out/publish.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,OAAO,gBAAuB,MAAM,gBAAgB,MAAM;;;;;;;;EAgEtE,CAAC;AAEF,eAAO,MAAM,aAAa,gBAAuB,MAAM,gBAAgB,MAAM;;;;;;;;EA+D5E,CAAC"}
|
package/out/publish.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.publishGlobal = exports.publish =
|
|
4
|
-
const api_1 = require("@forge/api");
|
|
3
|
+
exports.publishGlobal = exports.publish = void 0;
|
|
5
4
|
const runtime_1 = require("./runtime");
|
|
6
|
-
const
|
|
7
|
-
exports.getForgeProxyError = getForgeProxyError;
|
|
8
|
-
const handleProxyResponseErrors = (response) => {
|
|
9
|
-
const errorReason = (0, exports.getForgeProxyError)(response);
|
|
10
|
-
if (errorReason) {
|
|
11
|
-
throw new api_1.ProxyRequestError(response.status, errorReason);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
exports.handleProxyResponseErrors = handleProxyResponseErrors;
|
|
5
|
+
const utils_1 = require("./utils");
|
|
15
6
|
const graphqlBody = `mutation publishRealtimeChannel(
|
|
16
7
|
$installationId: ID!
|
|
17
8
|
$name: String!
|
|
@@ -30,11 +21,10 @@ const graphqlBody = `mutation publishRealtimeChannel(
|
|
|
30
21
|
}
|
|
31
22
|
}
|
|
32
23
|
}`;
|
|
33
|
-
const prodEnvErrorMessage = '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.';
|
|
34
24
|
const publish = async (channelName, eventPayload) => {
|
|
35
25
|
const { appContext, realtime } = (0, runtime_1.__getRuntime)();
|
|
36
26
|
if (appContext?.environmentType === 'PRODUCTION') {
|
|
37
|
-
throw new Error(prodEnvErrorMessage);
|
|
27
|
+
throw new Error(utils_1.prodEnvErrorMessage);
|
|
38
28
|
}
|
|
39
29
|
const response = await global.__forge_fetch__({
|
|
40
30
|
type: 'realtime'
|
|
@@ -49,19 +39,44 @@ const publish = async (channelName, eventPayload) => {
|
|
|
49
39
|
isGlobal: false
|
|
50
40
|
}
|
|
51
41
|
}),
|
|
42
|
+
errors: [],
|
|
52
43
|
headers: {
|
|
53
44
|
'Content-Type': 'application/json',
|
|
54
45
|
'x-forge-context-token': realtime?.contextToken
|
|
55
46
|
}
|
|
56
47
|
});
|
|
57
|
-
(0,
|
|
58
|
-
|
|
48
|
+
(0, utils_1.handleProxyResponseErrors)(response);
|
|
49
|
+
const awaitedResponse = await response.json();
|
|
50
|
+
const { data, errors } = awaitedResponse;
|
|
51
|
+
if (errors && errors.length > 0) {
|
|
52
|
+
return {
|
|
53
|
+
eventId: null,
|
|
54
|
+
eventTimestamp: null,
|
|
55
|
+
errors
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!data) {
|
|
59
|
+
return {
|
|
60
|
+
eventId: null,
|
|
61
|
+
eventTimestamp: null,
|
|
62
|
+
errors: [
|
|
63
|
+
{
|
|
64
|
+
message: 'Error publishing event to channel.'
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const { eventId, eventTimestamp } = awaitedResponse.data.ecosystem.publishRealtimeChannel;
|
|
70
|
+
return {
|
|
71
|
+
eventId,
|
|
72
|
+
eventTimestamp
|
|
73
|
+
};
|
|
59
74
|
};
|
|
60
75
|
exports.publish = publish;
|
|
61
76
|
const publishGlobal = async (channelName, eventPayload) => {
|
|
62
77
|
const { appContext } = (0, runtime_1.__getRuntime)();
|
|
63
78
|
if (appContext?.environmentType === 'PRODUCTION') {
|
|
64
|
-
throw new Error(prodEnvErrorMessage);
|
|
79
|
+
throw new Error(utils_1.prodEnvErrorMessage);
|
|
65
80
|
}
|
|
66
81
|
const response = await global.__forge_fetch__({
|
|
67
82
|
type: 'realtime'
|
|
@@ -76,11 +91,36 @@ const publishGlobal = async (channelName, eventPayload) => {
|
|
|
76
91
|
isGlobal: true
|
|
77
92
|
}
|
|
78
93
|
}),
|
|
94
|
+
errors: [],
|
|
79
95
|
headers: {
|
|
80
96
|
'Content-Type': 'application/json'
|
|
81
97
|
}
|
|
82
98
|
});
|
|
83
|
-
(0,
|
|
84
|
-
|
|
99
|
+
(0, utils_1.handleProxyResponseErrors)(response);
|
|
100
|
+
const awaitedResponse = await response.json();
|
|
101
|
+
const { data, errors } = awaitedResponse;
|
|
102
|
+
if (errors && errors.length > 0) {
|
|
103
|
+
return {
|
|
104
|
+
eventId: null,
|
|
105
|
+
eventTimestamp: null,
|
|
106
|
+
errors
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (!data) {
|
|
110
|
+
return {
|
|
111
|
+
eventId: null,
|
|
112
|
+
eventTimestamp: null,
|
|
113
|
+
errors: [
|
|
114
|
+
{
|
|
115
|
+
message: 'Error publishing global event to channel.'
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const { eventId, eventTimestamp } = data.ecosystem.publishRealtimeChannel;
|
|
121
|
+
return {
|
|
122
|
+
eventId,
|
|
123
|
+
eventTimestamp
|
|
124
|
+
};
|
|
85
125
|
};
|
|
86
126
|
exports.publishGlobal = publishGlobal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signRealtimeToken.d.ts","sourceRoot":"","sources":["../src/signRealtimeToken.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,iBAAiB,gBAAuB,MAAM,UAAU,GAAG;;;;;;;;EA6DvE,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signRealtimeToken = void 0;
|
|
4
|
+
const runtime_1 = require("./runtime");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
const graphqlBody = `mutation signRealtimeToken(
|
|
7
|
+
$channelName: String!
|
|
8
|
+
$claims: JSON!
|
|
9
|
+
){
|
|
10
|
+
ecosystem {
|
|
11
|
+
signRealtimeToken(
|
|
12
|
+
channelName: $channelName
|
|
13
|
+
claims: $claims
|
|
14
|
+
) {
|
|
15
|
+
errors {
|
|
16
|
+
message
|
|
17
|
+
extensions {
|
|
18
|
+
errorType
|
|
19
|
+
statusCode
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
forgeRealtimeToken {
|
|
23
|
+
jwt
|
|
24
|
+
expiresAt
|
|
25
|
+
}
|
|
26
|
+
success
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}`;
|
|
30
|
+
const signRealtimeToken = async (channelName, claims) => {
|
|
31
|
+
const { appContext } = (0, runtime_1.__getRuntime)();
|
|
32
|
+
if (appContext?.environmentType === 'PRODUCTION') {
|
|
33
|
+
throw new Error(utils_1.prodEnvErrorMessage);
|
|
34
|
+
}
|
|
35
|
+
const response = await global.__forge_fetch__({
|
|
36
|
+
type: 'realtime'
|
|
37
|
+
}, '/', {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
query: graphqlBody,
|
|
41
|
+
variables: {
|
|
42
|
+
channelName,
|
|
43
|
+
claims
|
|
44
|
+
}
|
|
45
|
+
}),
|
|
46
|
+
errors: [],
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
(0, utils_1.handleProxyResponseErrors)(response);
|
|
52
|
+
const awaitedResponse = await response.json();
|
|
53
|
+
const { data, errors } = awaitedResponse;
|
|
54
|
+
if (errors && errors.length > 0) {
|
|
55
|
+
return {
|
|
56
|
+
jwt: null,
|
|
57
|
+
expiresAt: null,
|
|
58
|
+
errors
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (!data) {
|
|
62
|
+
return {
|
|
63
|
+
jwt: null,
|
|
64
|
+
expiresAt: null,
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
message: 'Error signing realtime token.'
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const { jwt, expiresAt } = data.ecosystem.signRealtimeToken.forgeRealtimeToken;
|
|
73
|
+
return {
|
|
74
|
+
jwt,
|
|
75
|
+
expiresAt
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
exports.signRealtimeToken = signRealtimeToken;
|
package/out/utils.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const handleProxyResponseErrors: (response: Response) => void;
|
|
2
|
+
export declare const prodEnvErrorMessage = "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.";
|
|
3
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,aAAc,QAAQ,KAAG,IAK9D,CAAC;AAEF,eAAO,MAAM,mBAAmB,qNACoL,CAAC"}
|
package/out/utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prodEnvErrorMessage = exports.handleProxyResponseErrors = void 0;
|
|
4
|
+
const api_1 = require("@forge/api");
|
|
5
|
+
const getForgeProxyError = (response) => response.headers.get('forge-proxy-error');
|
|
6
|
+
const handleProxyResponseErrors = (response) => {
|
|
7
|
+
const errorReason = getForgeProxyError(response);
|
|
8
|
+
if (errorReason) {
|
|
9
|
+
throw new api_1.ProxyRequestError(response.status, errorReason);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
exports.handleProxyResponseErrors = handleProxyResponseErrors;
|
|
13
|
+
exports.prodEnvErrorMessage = '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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/realtime",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-next.1",
|
|
4
4
|
"description": "Forge realtime",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@atlassian/metrics-interface": "4.0.0",
|
|
17
|
-
"@forge/api": "6.1.0",
|
|
17
|
+
"@forge/api": "6.1.1-next.0",
|
|
18
18
|
"@types/node": "20.19.1"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
@@ -9,6 +9,7 @@ exports[`publish should publish an event when the payload is an object 1`] = `
|
|
|
9
9
|
"/",
|
|
10
10
|
{
|
|
11
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}}",
|
|
12
|
+
"errors": [],
|
|
12
13
|
"headers": {
|
|
13
14
|
"Content-Type": "application/json",
|
|
14
15
|
"x-forge-context-token": "my.context.token",
|
|
@@ -28,6 +29,27 @@ exports[`publish should publish an event with FCT in header 1`] = `
|
|
|
28
29
|
"/",
|
|
29
30
|
{
|
|
30
31
|
"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}}",
|
|
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){\\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}}",
|
|
52
|
+
"errors": [],
|
|
31
53
|
"headers": {
|
|
32
54
|
"Content-Type": "application/json",
|
|
33
55
|
"x-forge-context-token": "my.context.token",
|
|
@@ -47,6 +69,7 @@ exports[`publish should throw an error when Forge Outbound Proxy returns an erro
|
|
|
47
69
|
"/",
|
|
48
70
|
{
|
|
49
71
|
"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}}",
|
|
72
|
+
"errors": [],
|
|
50
73
|
"headers": {
|
|
51
74
|
"Content-Type": "application/json",
|
|
52
75
|
"x-forge-context-token": "my.context.token",
|
|
@@ -66,6 +89,7 @@ exports[`publishGlobal should publish an event 1`] = `
|
|
|
66
89
|
"/",
|
|
67
90
|
{
|
|
68
91
|
"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}}",
|
|
92
|
+
"errors": [],
|
|
69
93
|
"headers": {
|
|
70
94
|
"Content-Type": "application/json",
|
|
71
95
|
},
|
|
@@ -84,6 +108,26 @@ exports[`publishGlobal should publish an event when the payload is an object 1`]
|
|
|
84
108
|
"/",
|
|
85
109
|
{
|
|
86
110
|
"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}}",
|
|
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){\\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}}",
|
|
130
|
+
"errors": [],
|
|
87
131
|
"headers": {
|
|
88
132
|
"Content-Type": "application/json",
|
|
89
133
|
},
|
|
@@ -102,6 +146,7 @@ exports[`publishGlobal should throw an error when Forge Outbound Proxy returns a
|
|
|
102
146
|
"/",
|
|
103
147
|
{
|
|
104
148
|
"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}}",
|
|
149
|
+
"errors": [],
|
|
105
150
|
"headers": {
|
|
106
151
|
"Content-Type": "application/json",
|
|
107
152
|
},
|