aws-appsync-subscription-link 4.0.0 → 4.0.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 CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ <a name="4.1.0"></a>
7
+ # [4.1.0] (Unreleased)
8
+
9
+ ### Features
10
+
11
+ * **aws-appsync-subscription-link:** add `proxy` config option to support routing subscriptions through CloudFront or other CDN proxies ([#710](https://github.com/aws-amplify/amplify-category-api/issues/710))
12
+
6
13
  <a name="4.0.0"></a>
7
14
  # [4.0.0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@4.0.0-next.1...aws-appsync-subscription-link@4.0.0) (2025-11-19)
8
15
 
@@ -428,6 +428,120 @@ describe("RealTime subscription link", () => {
428
428
  });
429
429
  });
430
430
 
431
+ test("Can instantiate link with proxy config", () => {
432
+ expect.assertions(1);
433
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
434
+ auth: {
435
+ type: AUTH_TYPE.API_KEY,
436
+ apiKey: "xxxxx",
437
+ },
438
+ region: "us-west-2",
439
+ url: "https://firsttesturl12345678901234.appsync-api.us-west-2.amazonaws.com/graphql",
440
+ proxy: {
441
+ url: "https://d111111abcdef8.cloudfront.net/graphql",
442
+ },
443
+ });
444
+
445
+ expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
446
+ });
447
+
448
+ test("Initialize WebSocket through proxy for API KEY - routes to proxy URL with AppSync host in auth", (done) => {
449
+ expect.assertions(2);
450
+ jest.spyOn(Date.prototype, "toISOString").mockImplementation(
451
+ jest.fn(() => {
452
+ return "2019-11-13T18:47:04.733Z";
453
+ })
454
+ );
455
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn(
456
+ (url, protocol) => {
457
+ // WebSocket should connect to the proxy, not directly to AppSync
458
+ // The header should contain the original AppSync host for auth
459
+ const urlObj = new URL(url);
460
+ expect(urlObj.origin + urlObj.pathname).toBe(
461
+ "wss://d111111abcdef8.cloudfront.net/realtime"
462
+ );
463
+ // Decode the header to verify the host is the AppSync endpoint, not CloudFront
464
+ const headerB64 = urlObj.searchParams.get("header");
465
+ const header = JSON.parse(Buffer.from(headerB64, "base64").toString());
466
+ expect(header.host).toBe(
467
+ "proxytesturl12345678901234567.appsync-api.us-west-2.amazonaws.com"
468
+ );
469
+ done();
470
+ return new myWebSocket();
471
+ }
472
+ );
473
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
474
+ auth: {
475
+ type: AUTH_TYPE.API_KEY,
476
+ apiKey: "xxxxx",
477
+ },
478
+ region: "us-west-2",
479
+ url: "https://proxytesturl12345678901234567.appsync-api.us-west-2.amazonaws.com/graphql",
480
+ proxy: {
481
+ url: "https://d111111abcdef8.cloudfront.net/graphql",
482
+ },
483
+ });
484
+
485
+ execute(link, { query }, { client: mockClient }).subscribe({
486
+ error: (err) => {
487
+ fail;
488
+ },
489
+ next: (data) => {
490
+ done();
491
+ },
492
+ complete: () => {
493
+ done();
494
+ },
495
+ });
496
+ });
497
+
498
+ test("Initialize WebSocket through proxy for COGNITO USER POOLS - host in auth is AppSync not proxy", (done) => {
499
+ expect.assertions(2);
500
+ jest.spyOn(Date.prototype, "toISOString").mockImplementation(
501
+ jest.fn(() => {
502
+ return "2019-11-13T18:47:04.733Z";
503
+ })
504
+ );
505
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn(
506
+ (url, protocol) => {
507
+ const urlObj = new URL(url);
508
+ expect(urlObj.origin + urlObj.pathname).toBe(
509
+ "wss://mycdn.example.com/realtime"
510
+ );
511
+ const headerB64 = urlObj.searchParams.get("header");
512
+ const header = JSON.parse(Buffer.from(headerB64, "base64").toString());
513
+ expect(header.host).toBe(
514
+ "cognitoproxytesturl123456789.appsync-api.us-west-2.amazonaws.com"
515
+ );
516
+ done();
517
+ return new myWebSocket();
518
+ }
519
+ );
520
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
521
+ auth: {
522
+ type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
523
+ jwtToken: "token",
524
+ },
525
+ region: "us-west-2",
526
+ url: "https://cognitoproxytesturl123456789.appsync-api.us-west-2.amazonaws.com/graphql",
527
+ proxy: {
528
+ url: "https://mycdn.example.com/graphql",
529
+ },
530
+ });
531
+
532
+ execute(link, { query }, { client: mockClient }).subscribe({
533
+ error: (err) => {
534
+ fail;
535
+ },
536
+ next: (data) => {
537
+ done();
538
+ },
539
+ complete: () => {
540
+ done();
541
+ },
542
+ });
543
+ });
544
+
431
545
  test("Can use a custom keepAliveTimeoutMs", (done) => {
432
546
  const id = "abcd-efgh-ijkl-mnop";
433
547
  jest.mocked(uuid).mockImplementationOnce(() => id);
package/lib/index.d.mts CHANGED
@@ -13,8 +13,29 @@ type UrlInfo = {
13
13
  auth: AuthOptions;
14
14
  region: string;
15
15
  };
16
+ /**
17
+ * Route connections through a proxy (e.g. CloudFront) instead of connecting
18
+ * directly to AppSync. The library will send traffic to the proxy URL while
19
+ * using the original AppSync host for authentication.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * {
24
+ * url: 'https://xxx.appsync-api.us-east-1.amazonaws.com/graphql',
25
+ * region: 'us-east-1',
26
+ * auth: { type: AUTH_TYPE.API_KEY, apiKey: 'da2-xxx' },
27
+ * proxy: { url: 'https://d111111abcdef8.cloudfront.net/graphql' }
28
+ * }
29
+ * ```
30
+ */
31
+ type ProxyConfig = {
32
+ /** The proxy endpoint that sits in front of AppSync (e.g. a CloudFront distribution URL). */
33
+ url: string;
34
+ };
16
35
  type AppSyncRealTimeSubscriptionConfig = UrlInfo & {
17
36
  keepAliveTimeoutMs?: number;
37
+ /** Optional proxy configuration for routing through CloudFront or another CDN. */
38
+ proxy?: ProxyConfig;
18
39
  };
19
40
 
20
41
  declare function createSubscriptionHandshakeLink(args: AppSyncRealTimeSubscriptionConfig, resultsFetcherLink?: ApolloLink): ApolloLink;
package/lib/index.d.ts CHANGED
@@ -13,8 +13,29 @@ type UrlInfo = {
13
13
  auth: AuthOptions;
14
14
  region: string;
15
15
  };
16
+ /**
17
+ * Route connections through a proxy (e.g. CloudFront) instead of connecting
18
+ * directly to AppSync. The library will send traffic to the proxy URL while
19
+ * using the original AppSync host for authentication.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * {
24
+ * url: 'https://xxx.appsync-api.us-east-1.amazonaws.com/graphql',
25
+ * region: 'us-east-1',
26
+ * auth: { type: AUTH_TYPE.API_KEY, apiKey: 'da2-xxx' },
27
+ * proxy: { url: 'https://d111111abcdef8.cloudfront.net/graphql' }
28
+ * }
29
+ * ```
30
+ */
31
+ type ProxyConfig = {
32
+ /** The proxy endpoint that sits in front of AppSync (e.g. a CloudFront distribution URL). */
33
+ url: string;
34
+ };
16
35
  type AppSyncRealTimeSubscriptionConfig = UrlInfo & {
17
36
  keepAliveTimeoutMs?: number;
37
+ /** Optional proxy configuration for routing through CloudFront or another CDN. */
38
+ proxy?: ProxyConfig;
18
39
  };
19
40
 
20
41
  declare function createSubscriptionHandshakeLink(args: AppSyncRealTimeSubscriptionConfig, resultsFetcherLink?: ApolloLink): ApolloLink;
package/lib/index.js CHANGED
@@ -2694,7 +2694,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(_import_co
2694
2694
  "use strict";
2695
2695
  _inherits(_AppSyncRealTimeSubscriptionHandshakeLink, _import_core3_ApolloLink);
2696
2696
  function _AppSyncRealTimeSubscriptionHandshakeLink(param) {
2697
- var theUrl = param.url, theRegion = param.region, theAuth = param.auth, keepAliveTimeoutMs = param.keepAliveTimeoutMs;
2697
+ var theUrl = param.url, theRegion = param.region, theAuth = param.auth, keepAliveTimeoutMs = param.keepAliveTimeoutMs, proxy = param.proxy;
2698
2698
  _class_call_check(this, _AppSyncRealTimeSubscriptionHandshakeLink);
2699
2699
  var _this;
2700
2700
  _this = _call_super(this, _AppSyncRealTimeSubscriptionHandshakeLink);
@@ -2705,6 +2705,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(_import_co
2705
2705
  _this.url = theUrl;
2706
2706
  _this.region = theRegion;
2707
2707
  _this.auth = theAuth;
2708
+ _this.proxyUrl = proxy === null || proxy === void 0 ? void 0 : proxy.url;
2708
2709
  _this.keepAliveTimeout = keepAliveTimeoutMs;
2709
2710
  if (_this.keepAliveTimeout < SERVER_KEEP_ALIVE_TIMEOUT) {
2710
2711
  var configName = "keepAliveTimeoutMs";
@@ -3046,13 +3047,15 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(_import_co
3046
3047
  ]);
3047
3048
  headerQs = Buffer.from(headerString).toString("base64");
3048
3049
  payloadQs = Buffer.from(payloadString).toString("base64");
3049
- discoverableEndpoint = appSyncGraphqlEndpoint;
3050
- if (this.isCustomDomain(discoverableEndpoint)) {
3051
- discoverableEndpoint = discoverableEndpoint.concat(customDomainPath);
3050
+ if (this.proxyUrl) {
3051
+ discoverableEndpoint = this.proxyUrl.replace(/\/graphql$/, "").concat(customDomainPath).replace("https://", "wss://").replace("http://", "ws://");
3052
+ } else if (this.isCustomDomain(appSyncGraphqlEndpoint)) {
3053
+ discoverableEndpoint = appSyncGraphqlEndpoint.concat(customDomainPath);
3054
+ discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3052
3055
  } else {
3053
- discoverableEndpoint = discoverableEndpoint.replace("appsync-api", "appsync-realtime-api").replace("gogi-beta", "grt-beta");
3056
+ discoverableEndpoint = appSyncGraphqlEndpoint.replace("appsync-api", "appsync-realtime-api").replace("gogi-beta", "grt-beta");
3057
+ discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3054
3058
  }
3055
- discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3056
3059
  awsRealTimeUrl = "".concat(discoverableEndpoint, "?header=").concat(headerQs, "&payload=").concat(payloadQs);
3057
3060
  return [
3058
3061
  4,
@@ -3123,7 +3126,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(_import_co
3123
3126
  {}
3124
3127
  ];
3125
3128
  }
3126
- host = url.parse(appSyncGraphqlEndpoint).host;
3129
+ host = url.parse(this.url).host;
3127
3130
  return [
3128
3131
  4,
3129
3132
  handler({
package/lib/index.mjs CHANGED
@@ -2671,7 +2671,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(ApolloLink
2671
2671
  "use strict";
2672
2672
  _inherits(_AppSyncRealTimeSubscriptionHandshakeLink, ApolloLink3);
2673
2673
  function _AppSyncRealTimeSubscriptionHandshakeLink(param) {
2674
- var theUrl = param.url, theRegion = param.region, theAuth = param.auth, keepAliveTimeoutMs = param.keepAliveTimeoutMs;
2674
+ var theUrl = param.url, theRegion = param.region, theAuth = param.auth, keepAliveTimeoutMs = param.keepAliveTimeoutMs, proxy = param.proxy;
2675
2675
  _class_call_check(this, _AppSyncRealTimeSubscriptionHandshakeLink);
2676
2676
  var _this;
2677
2677
  _this = _call_super(this, _AppSyncRealTimeSubscriptionHandshakeLink);
@@ -2682,6 +2682,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(ApolloLink
2682
2682
  _this.url = theUrl;
2683
2683
  _this.region = theRegion;
2684
2684
  _this.auth = theAuth;
2685
+ _this.proxyUrl = proxy === null || proxy === void 0 ? void 0 : proxy.url;
2685
2686
  _this.keepAliveTimeout = keepAliveTimeoutMs;
2686
2687
  if (_this.keepAliveTimeout < SERVER_KEEP_ALIVE_TIMEOUT) {
2687
2688
  var configName = "keepAliveTimeoutMs";
@@ -3023,13 +3024,15 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(ApolloLink
3023
3024
  ]);
3024
3025
  headerQs = Buffer.from(headerString).toString("base64");
3025
3026
  payloadQs = Buffer.from(payloadString).toString("base64");
3026
- discoverableEndpoint = appSyncGraphqlEndpoint;
3027
- if (this.isCustomDomain(discoverableEndpoint)) {
3028
- discoverableEndpoint = discoverableEndpoint.concat(customDomainPath);
3027
+ if (this.proxyUrl) {
3028
+ discoverableEndpoint = this.proxyUrl.replace(/\/graphql$/, "").concat(customDomainPath).replace("https://", "wss://").replace("http://", "ws://");
3029
+ } else if (this.isCustomDomain(appSyncGraphqlEndpoint)) {
3030
+ discoverableEndpoint = appSyncGraphqlEndpoint.concat(customDomainPath);
3031
+ discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3029
3032
  } else {
3030
- discoverableEndpoint = discoverableEndpoint.replace("appsync-api", "appsync-realtime-api").replace("gogi-beta", "grt-beta");
3033
+ discoverableEndpoint = appSyncGraphqlEndpoint.replace("appsync-api", "appsync-realtime-api").replace("gogi-beta", "grt-beta");
3034
+ discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3031
3035
  }
3032
- discoverableEndpoint = discoverableEndpoint.replace("https://", "wss://").replace("http://", "ws://");
3033
3036
  awsRealTimeUrl = "".concat(discoverableEndpoint, "?header=").concat(headerQs, "&payload=").concat(payloadQs);
3034
3037
  return [
3035
3038
  4,
@@ -3100,7 +3103,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /*#__PURE__*/ function(ApolloLink
3100
3103
  {}
3101
3104
  ];
3102
3105
  }
3103
- host = url.parse(appSyncGraphqlEndpoint).host;
3106
+ host = url.parse(this.url).host;
3104
3107
  return [
3105
3108
  4,
3106
3109
  handler({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-appsync-subscription-link",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "main": "lib/index.js",
5
5
  "module": "lib/index.mjs",
6
6
  "types": "lib/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "test-watch": "jest --watch"
31
31
  },
32
32
  "dependencies": {
33
- "aws-appsync-auth-link": "^4.0.0",
33
+ "aws-appsync-auth-link": "^4.0.1",
34
34
  "debug": "2.6.9",
35
35
  "url": "^0.11.0",
36
36
  "zen-observable-ts": "^1.2.5"