@forge/realtime 0.4.2-next.0 → 0.5.0-experimental-04cc2b9

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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @forge/realtime
2
2
 
3
+ ## 0.5.0-experimental-04cc2b9
4
+
5
+ ### Major Changes
6
+
7
+ - d9ef926: Adds support for TypeScript 5
8
+
9
+ ### Patch Changes
10
+
11
+ - f4a575a: Adds rate limit headers to signRealtimeToken
12
+
13
+ ## 0.5.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 68e1229: Adds pre validation for the forge realtime token in publish methods
18
+
19
+ ### Patch Changes
20
+
21
+ - 4c69f6e: Add headers for rate limits
22
+ - 64e72aa: Updates error return to be consistent
23
+
24
+ ## 0.5.0-next.2
25
+
26
+ ### Patch Changes
27
+
28
+ - 64e72aa: Updates error return to be consistent
29
+
30
+ ## 0.5.0-next.1
31
+
32
+ ### Minor Changes
33
+
34
+ - 68e1229: Adds pre validation for the forge realtime token in publish methods
35
+
3
36
  ## 0.4.2-next.0
4
37
 
5
38
  ### Patch Changes
@@ -11,5 +11,5 @@ export declare enum Bitbucket {
11
11
  Repository = "repository",
12
12
  PullRequest = "pullRequest"
13
13
  }
14
- export declare type ProductContext = Jira | Confluence | Bitbucket;
14
+ export type ProductContext = Jira | Confluence | Bitbucket;
15
15
  //# sourceMappingURL=productContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"productContext.d.ts","sourceRoot":"","sources":["../src/productContext.ts"],"names":[],"mappings":"AAGA,oBAAY,IAAI;IACd,KAAK,UAAU;IACf,KAAK,UAAU;IACf,OAAO,YAAY;CACpB;AAED,oBAAY,UAAU;IACpB,OAAO,YAAY;IACnB,KAAK,UAAU;CAChB;AAED,oBAAY,SAAS;IACnB,UAAU,eAAe;IACzB,WAAW,gBAAgB;CAC5B;AAED,oBAAY,cAAc,GAAG,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"productContext.d.ts","sourceRoot":"","sources":["../src/productContext.ts"],"names":[],"mappings":"AAGA,oBAAY,IAAI;IACd,KAAK,UAAU;IACf,KAAK,UAAU;IACf,OAAO,YAAY;CACpB;AAED,oBAAY,UAAU;IACpB,OAAO,YAAY;IACnB,KAAK,UAAU;CAChB;AAED,oBAAY,SAAS;IACnB,UAAU,eAAe;IACzB,WAAW,gBAAgB;CAC5B;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC"}
@@ -6,14 +6,14 @@ var Jira;
6
6
  Jira["Board"] = "board";
7
7
  Jira["Issue"] = "issue";
8
8
  Jira["Project"] = "project";
9
- })(Jira = exports.Jira || (exports.Jira = {}));
9
+ })(Jira || (exports.Jira = Jira = {}));
10
10
  var Confluence;
11
11
  (function (Confluence) {
12
12
  Confluence["Content"] = "content";
13
13
  Confluence["Space"] = "space";
14
- })(Confluence = exports.Confluence || (exports.Confluence = {}));
14
+ })(Confluence || (exports.Confluence = Confluence = {}));
15
15
  var Bitbucket;
16
16
  (function (Bitbucket) {
17
17
  Bitbucket["Repository"] = "repository";
18
18
  Bitbucket["PullRequest"] = "pullRequest";
19
- })(Bitbucket = exports.Bitbucket || (exports.Bitbucket = {}));
19
+ })(Bitbucket || (exports.Bitbucket = Bitbucket = {}));
@@ -1 +1 @@
1
- {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA2BlD,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,OAAO,mDACL,MAAM,sCAET,cAAc;;;;;;;;EA4EzB,CAAC;AAEF,eAAO,MAAM,aAAa,mDACX,MAAM,sCAET,cAAc;;;;;;;;EAgEzB,CAAC"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA2BlD,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,OAAO,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,aAAa,MAAM,EACnB,cAAc,MAAM,GAAG,CAAC,EACxB,UAAU,cAAc;;;;;;;;EA2FzB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,aAAa,MAAM,EACnB,cAAc,MAAM,GAAG,CAAC,EACxB,UAAU,cAAc;;;;;;;;EAiFzB,CAAC"}
package/out/publish.js CHANGED
@@ -31,6 +31,20 @@ const publish = async (channelName, eventPayload, options) => {
31
31
  if (contextOverrides && !Array.isArray(contextOverrides)) {
32
32
  throw new Error('Invalid value for contextOverrides. Please provide an array of valid context properties.');
33
33
  }
34
+ if (token) {
35
+ const { valid, error } = (0, utils_1.validateToken)(token, channelName);
36
+ if (!valid) {
37
+ return {
38
+ eventId: null,
39
+ eventTimestamp: null,
40
+ errors: [
41
+ {
42
+ message: `Realtime token validation failed: ${error}`
43
+ }
44
+ ]
45
+ };
46
+ }
47
+ }
34
48
  const channelContext = contextOverrides
35
49
  ? JSON.stringify({
36
50
  contextOverrides
@@ -48,7 +62,7 @@ const publish = async (channelName, eventPayload, options) => {
48
62
  context: channelContext,
49
63
  payload: JSON.stringify(eventPayload),
50
64
  isGlobal: false,
51
- token: token
65
+ token
52
66
  }
53
67
  }),
54
68
  errors: [],
@@ -89,6 +103,21 @@ const publish = async (channelName, eventPayload, options) => {
89
103
  exports.publish = publish;
90
104
  const publishGlobal = async (channelName, eventPayload, options) => {
91
105
  const { appContext, realtime } = (0, runtime_1.__getRuntime)();
106
+ const { token } = options || {};
107
+ if (token) {
108
+ const { valid, error } = (0, utils_1.validateToken)(token, channelName);
109
+ if (!valid) {
110
+ return {
111
+ eventId: null,
112
+ eventTimestamp: null,
113
+ errors: [
114
+ {
115
+ message: `Realtime token validation failed: ${error}`
116
+ }
117
+ ]
118
+ };
119
+ }
120
+ }
92
121
  const response = await global.__forge_fetch__({
93
122
  type: 'realtime'
94
123
  }, '/', {
@@ -100,7 +129,7 @@ const publishGlobal = async (channelName, eventPayload, options) => {
100
129
  name: channelName,
101
130
  payload: JSON.stringify(eventPayload),
102
131
  isGlobal: true,
103
- token: options?.token
132
+ token
104
133
  }
105
134
  }),
106
135
  errors: [],
@@ -127,7 +156,7 @@ const publishGlobal = async (channelName, eventPayload, options) => {
127
156
  eventTimestamp: null,
128
157
  errors: [
129
158
  {
130
- message: 'Error publishing global event to channel.'
159
+ message: 'Error publishing event to channel.'
131
160
  }
132
161
  ]
133
162
  };
package/out/runtime.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.__getRuntime = void 0;
3
+ exports.__getRuntime = __getRuntime;
4
4
  function __getRuntime() {
5
5
  const runtime = global.__forge_runtime__;
6
6
  if (!runtime) {
@@ -8,4 +8,3 @@ function __getRuntime() {
8
8
  }
9
9
  return runtime;
10
10
  }
11
- exports.__getRuntime = __getRuntime;
@@ -1,4 +1,5 @@
1
- declare type RealtimeTokenPermissions = ['subscribe'] | ['publish'] | ['subscribe', 'publish'] | ['publish', 'subscribe'];
1
+ type TokenPermissions = 'subscribe' | 'publish';
2
+ export type RealtimeTokenPermissions = [TokenPermissions, ...TokenPermissions[]];
2
3
  export declare const signRealtimeToken: (channelName: string, claims: any, permissions?: RealtimeTokenPermissions) => Promise<{
3
4
  token: null;
4
5
  expiresAt: null;
@@ -1 +1 @@
1
- {"version":3,"file":"signRealtimeToken.d.ts","sourceRoot":"","sources":["../src/signRealtimeToken.ts"],"names":[],"mappings":"AA6BA,aAAK,wBAAwB,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAElH,eAAO,MAAM,iBAAiB,gBACf,MAAM,UAEX,GAAG,gBACG,wBAAwB;;;;;;;;EAyDvC,CAAC"}
1
+ {"version":3,"file":"signRealtimeToken.d.ts","sourceRoot":"","sources":["../src/signRealtimeToken.ts"],"names":[],"mappings":"AA8BA,KAAK,gBAAgB,GAAG,WAAW,GAAG,SAAS,CAAC;AAChD,MAAM,MAAM,wBAAwB,GAAG,CAAC,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAAC,CAAC;AAEjF,eAAO,MAAM,iBAAiB,GAC5B,aAAa,MAAM,EAEnB,QAAQ,GAAG,EACX,cAAc,wBAAwB;;;;;;;;EA6DvC,CAAC"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.signRealtimeToken = void 0;
4
4
  const utils_1 = require("./utils");
5
+ const runtime_1 = require("./runtime");
5
6
  const graphqlBody = `mutation signRealtimeToken(
6
7
  $channelName: String!
7
8
  $claims: JSON!
@@ -29,6 +30,7 @@ const graphqlBody = `mutation signRealtimeToken(
29
30
  }
30
31
  }`;
31
32
  const signRealtimeToken = async (channelName, claims, permissions) => {
33
+ const { appContext } = (0, runtime_1.__getRuntime)();
32
34
  const response = await global.__forge_fetch__({
33
35
  type: 'realtime'
34
36
  }, '/', {
@@ -43,7 +45,9 @@ const signRealtimeToken = async (channelName, claims, permissions) => {
43
45
  }),
44
46
  errors: [],
45
47
  headers: {
46
- 'Content-Type': 'application/json'
48
+ 'Content-Type': 'application/json',
49
+ 'x-rate-limit-app-id': appContext.appId,
50
+ 'x-rate-limit-installation-id': appContext.installationId
47
51
  }
48
52
  });
49
53
  (0, utils_1.handleProxyResponseErrors)(response);
@@ -62,7 +66,7 @@ const signRealtimeToken = async (channelName, claims, permissions) => {
62
66
  expiresAt: null,
63
67
  errors: [
64
68
  {
65
- message: 'Error signing realtime token.'
69
+ message: 'Error signing realtime token'
66
70
  }
67
71
  ]
68
72
  };
package/out/utils.d.ts CHANGED
@@ -1,2 +1,9 @@
1
1
  export declare const handleProxyResponseErrors: (response: Response) => void;
2
+ type RealtimeTokenValidationError = 'INVALID_TOKEN' | 'TOKEN_EXPIRED' | 'MISSING_PERMISSION' | 'CHANNEL_NAME_MISMATCH';
3
+ interface RealtimeTokenValidationResult {
4
+ error?: RealtimeTokenValidationError;
5
+ valid: boolean;
6
+ }
7
+ export declare const validateToken: (token: string, channelName: string) => RealtimeTokenValidationResult;
8
+ export {};
2
9
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,aAAc,QAAQ,KAAG,IAK9D,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,yBAAyB,GAAI,UAAU,QAAQ,KAAG,IAK9D,CAAC;AAeF,KAAK,4BAA4B,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;AAEvH,UAAU,6BAA6B;IACrC,KAAK,CAAC,EAAE,4BAA4B,CAAC;IACrC,KAAK,EAAE,OAAO,CAAC;CAChB;AAuBD,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,aAAa,MAAM,KAAG,6BAqBlE,CAAC"}
package/out/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleProxyResponseErrors = void 0;
3
+ exports.validateToken = exports.handleProxyResponseErrors = void 0;
4
4
  const api_1 = require("@forge/api");
5
5
  const getForgeProxyError = (response) => response.headers.get('forge-proxy-error');
6
6
  const handleProxyResponseErrors = (response) => {
@@ -10,3 +10,31 @@ const handleProxyResponseErrors = (response) => {
10
10
  }
11
11
  };
12
12
  exports.handleProxyResponseErrors = handleProxyResponseErrors;
13
+ const decodeTokenPayload = (token) => {
14
+ const parts = token.split('.');
15
+ if (parts.length !== 3) {
16
+ throw new Error('Invalid token format.');
17
+ }
18
+ const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
19
+ return JSON.parse(Buffer.from(base64, 'base64').toString('utf-8'));
20
+ };
21
+ const validateToken = (token, channelName) => {
22
+ let decodedToken;
23
+ try {
24
+ decodedToken = decodeTokenPayload(token);
25
+ }
26
+ catch {
27
+ return { valid: false, error: 'INVALID_TOKEN' };
28
+ }
29
+ if (typeof decodedToken.exp === 'number' && Date.now() / 1000 >= decodedToken.exp) {
30
+ return { valid: false, error: 'TOKEN_EXPIRED' };
31
+ }
32
+ if (decodedToken.channel.name !== channelName) {
33
+ return { valid: false, error: 'CHANNEL_NAME_MISMATCH' };
34
+ }
35
+ if (decodedToken.permissions && !decodedToken.permissions?.includes('publish')) {
36
+ return { valid: false, error: 'MISSING_PERMISSION' };
37
+ }
38
+ return { valid: true };
39
+ };
40
+ exports.validateToken = validateToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forge/realtime",
3
- "version": "0.4.2-next.0",
3
+ "version": "0.5.0-experimental-04cc2b9",
4
4
  "description": "Forge realtime",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",
@@ -14,10 +14,19 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@atlassian/metrics-interface": "4.0.0",
17
- "@forge/api": "^7.2.1",
18
- "@types/node": "20.19.1"
17
+ "@forge/api": "^7.2.2-experimental-04cc2b9",
18
+ "@types/node": "20.19.1",
19
+ "typescript": "5.9.2"
19
20
  },
20
21
  "publishConfig": {
21
22
  "registry": "https://packages.atlassian.com/api/npm/npm-public/"
23
+ },
24
+ "peerDependencies": {
25
+ "typescript": ">=5.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "typescript": {
29
+ "optional": true
30
+ }
22
31
  }
23
32
  }