@queue-it/fastly 2.0.3-beta.4 → 4.4.3-beta.0

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/README.md CHANGED
@@ -109,6 +109,28 @@ if (res != null) {
109
109
  - Create desired waiting room(s), triggers, and actions in the Go Queue-It self-service platform.
110
110
  Then, save/publish the configuration.
111
111
 
112
+ ## Enqueue Token Settings
113
+
114
+ The connector supports enqueue tokens, which are used to secure the queue redirect flow. These settings are read at runtime from the Fastly `IntegrationConfiguration` Config Store. They are optional — if not set, the defaults below apply.
115
+
116
+ | Key | Default | Description |
117
+ |-----|---------|-------------|
118
+ | `enqueueTokenEnabled` | `"true"` | Enables enqueue token generation. Set to `"false"` to disable. |
119
+ | `enqueueTokenValidityTime` | `"240000"` | Token validity time in milliseconds. |
120
+ | `enqueueTokenKeyEnabled` | `"false"` | Enables key-based enqueue tokens. Set to `"true"` to enable. |
121
+
122
+ To configure these, add them as items in the `IntegrationConfiguration` Config Store alongside the other settings (`customerId`, `apiKey`, `secret`, etc.) — either via the Fastly UI or the Fastly API.
123
+
124
+ ## Request Body Trigger Matching
125
+
126
+ The connector supports matching triggers based on the request body content. This is disabled by default. When enabled, up to 2048 characters of the request body are read and passed to the SDK for trigger evaluation.
127
+
128
+ To enable it, add the following key to the `IntegrationConfiguration` Config Store:
129
+
130
+ | Key | Default | Description |
131
+ |-----|---------|-------------|
132
+ | `requestBodyEnabled` | `"false"` | Set to `"true"` to enable request body trigger matching. |
133
+
112
134
  ## Providing the queue configuration
113
135
 
114
136
  The recommended way is to use the Go Queue-it self-service portal to setup the configuration. The configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queue-it/fastly",
3
- "version": "2.0.3-beta.4",
3
+ "version": "4.4.3-beta.0",
4
4
  "description": "Queue-it connector for Fastly",
5
5
  "main": "src/index.ts",
6
6
  "author": "devs@queue-it.com",
@@ -18,13 +18,15 @@
18
18
  "src/index.ts"
19
19
  ],
20
20
  "scripts": {
21
+ "typecheck": "tsc --noEmit",
21
22
  "bundle": "esbuild src/index.ts --bundle --outfile=bin/index.js --platform=node --target=es2022 && js-compute-runtime bin/index.js bin/main.wasm",
22
23
  "build": "fastly compute build",
23
24
  "deploy": "fastly compute deploy"
24
25
  },
25
26
  "dependencies": {
26
27
  "@fastly/js-compute": "^3.0.0",
27
- "@queue-it/connector-javascript": "^5.0.1"
28
+ "@queue-it/connector-javascript": "^4.4.4",
29
+ "@queue-it/queue-token": "~1.0.4"
28
30
  },
29
31
  "devDependencies": {
30
32
  "esbuild": "^0.27.3",
@@ -1,127 +1,172 @@
1
- import { IConnectorContextProvider, IHttpRequest, IHttpResponse } from '@queue-it/connector-javascript';
1
+ import {
2
+ DefaultEnqueueTokenProvider,
3
+ IConnectorContextProvider,
4
+ ICryptoProvider,
5
+ IEnqueueTokenProvider,
6
+ INormalizationProvider,
7
+ DefaultNormalizationProvider,
8
+ IHttpRequest,
9
+ IHttpResponse,
10
+ } from '@queue-it/connector-javascript';
11
+ import { Token, Payload } from '@queue-it/queue-token';
2
12
  import { FastlyCryptoProvider } from './fastlyCryptoProvider';
3
13
 
4
- export function getHttpHandler(req: Request): FastlyHttpContextProvider {
5
- return new FastlyHttpContextProvider(req);
14
+ export function getHttpHandler(req: Request, clientIp: string = '', bodyString: string = ''): FastlyHttpContextProvider {
15
+ return new FastlyHttpContextProvider(req, clientIp, bodyString);
6
16
  }
7
17
 
8
18
  export class FastlyHttpContextProvider implements IConnectorContextProvider {
9
19
  isError: boolean = false;
10
- private readonly req: FastlyHttpRequest;
11
- private readonly res: FastlyHttpResponse;
20
+ private readonly _httpRequest: FastlyHttpRequest;
21
+ private readonly _httpResponse: FastlyHttpResponse;
22
+ private _enqueueTokenProvider: IEnqueueTokenProvider | null = null;
23
+ private readonly _normalizationProvider: INormalizationProvider;
24
+ private readonly _cryptoProvider: ICryptoProvider;
12
25
 
13
- constructor(fReq: Request) {
14
- this.req = new FastlyHttpRequest(fReq);
15
- this.res = new FastlyHttpResponse();
26
+ constructor(fReq: Request, clientIp: string = '', bodyString: string = '') {
27
+ this._httpRequest = new FastlyHttpRequest(fReq, clientIp, bodyString);
28
+ this._httpResponse = new FastlyHttpResponse();
29
+ this._normalizationProvider = new DefaultNormalizationProvider();
30
+ this._cryptoProvider = new FastlyCryptoProvider();
16
31
  }
17
32
 
18
33
  getHttpRequest(): IHttpRequest {
19
- return this.req;
34
+ return this._httpRequest;
20
35
  }
21
36
 
22
37
  getHttpResponse(): IHttpResponse {
23
- return this.res;
38
+ return this._httpResponse;
24
39
  }
25
40
 
26
- getCryptoProvider(): FastlyCryptoProvider {
27
- return new FastlyCryptoProvider();
41
+ getCryptoProvider(): ICryptoProvider {
42
+ return this._cryptoProvider;
28
43
  }
29
44
 
30
- getEnqueueTokenProvider(): null {
31
- return null;
45
+ setEnqueueTokenProvider(
46
+ customerId: string,
47
+ secretKey: string,
48
+ validityTime: number,
49
+ clientIp: string,
50
+ withKey: boolean
51
+ ): void {
52
+ this._enqueueTokenProvider = new DefaultEnqueueTokenProvider(
53
+ customerId,
54
+ secretKey,
55
+ validityTime,
56
+ clientIp,
57
+ withKey,
58
+ Token,
59
+ Payload
60
+ );
61
+ }
62
+
63
+ setCustomEnqueueTokenProvider(provider: IEnqueueTokenProvider): void {
64
+ this._enqueueTokenProvider = provider;
65
+ }
66
+
67
+ getEnqueueTokenProvider(): IEnqueueTokenProvider | null {
68
+ return this._enqueueTokenProvider;
69
+ }
70
+
71
+ getNormalizationProvider(): INormalizationProvider {
72
+ return this._normalizationProvider;
32
73
  }
33
74
 
34
75
  getResponseHeaders(): Headers {
35
- return (this.res as FastlyHttpResponse).getHeaders();
76
+ return this._httpResponse.getHeaders();
36
77
  }
37
78
  }
38
79
 
39
- export class FastlyHttpRequest implements IHttpRequest {
40
- private parsedCookieDic: Map<string, string>
41
- private bodyFetched: boolean = false;
42
- private body: string = '';
80
+ class FastlyHttpRequest implements IHttpRequest {
81
+ private _parsedCookieDic: Record<string, string> | null = null;
43
82
 
44
- constructor(private baseReq: Request) {
45
- this.parsedCookieDic = new Map();
46
- this.bodyFetched = false;
47
- }
83
+ constructor(private baseReq: Request, private _clientIp: string = '', private _bodyString: string = '') {}
48
84
 
49
- private parseCookies(cookieValue: string): void {
50
- const cookies = cookieValue.split(';');
51
- for (let i = 0; i < cookies.length; i++) {
52
- let cookieKV = cookies[i].split('=', 2);
53
- if (cookieKV.length >= 2) {
54
- this.parsedCookieDic.set(cookieKV[0].trim(), cookieKV[1].trim())
55
- }
56
- }
85
+ getUserAgent(): string {
86
+ return this.getHeader('user-agent');
57
87
  }
58
88
 
59
- private handleBody(): void {
60
- if (this.baseReq.bodyUsed || this.bodyFetched) {
61
- return;
62
- }
63
- this.bodyFetched = true;
64
- this.body = ''; // this.context.req.text()
89
+ getHeader(name: string): string {
90
+ if (name.toLowerCase() === 'x-queueit-clientip')
91
+ return this.getUserHostAddress();
92
+
93
+ return this.baseReq.headers.get(name) || '';
65
94
  }
66
95
 
67
96
  getAbsoluteUri(): string {
68
97
  return this.baseReq.url;
69
98
  }
70
99
 
71
- getCookieValue(cookieKey: string): string | undefined {
72
- if (this.parsedCookieDic.size == 0) {
73
- this.parseCookies(this.getHeader('cookie'))
74
- }
75
- if (!this.parsedCookieDic.has(cookieKey)) return undefined;
76
- const cookieVal = this.parsedCookieDic.get(cookieKey)!;
77
- try {
78
- return decodeURIComponent(cookieVal);
79
- } catch {
80
- return cookieVal;
81
- }
100
+ getUserHostAddress(): string {
101
+ return this._clientIp || this.baseReq.headers.get('Fastly-Client-IP') || '';
82
102
  }
83
103
 
84
- getHeader(name: string): string {
85
- if (name.length == 0) return "";
86
- if (!this.baseReq.headers.has(name)) {
87
- return '';
104
+ getCookieValue(name: string): string | undefined {
105
+ if (!this._parsedCookieDic) {
106
+ this._parsedCookieDic = this._parseCookies(this.getHeader('cookie'));
88
107
  }
89
- const value = this.baseReq.headers.get(name);
90
- if (value == null) return '';
91
- return value!;
92
- }
93
108
 
94
- getRequestBodyAsString(): string {
95
- if (!this.bodyFetched) {
96
- this.handleBody();
109
+ const cookieValue = this._parsedCookieDic[name];
110
+ if (cookieValue) {
111
+ try {
112
+ return decodeURIComponent(cookieValue);
113
+ } catch {
114
+ return;
115
+ }
97
116
  }
98
- return this.body;
117
+
118
+ return;
99
119
  }
100
120
 
101
- getUserAgent(): string {
102
- return this.baseReq.headers.has('user-agent') ? this.baseReq.headers.get('user-agent')! : '';
121
+ getRequestBodyAsString(): string {
122
+ return this._bodyString;
103
123
  }
104
124
 
105
- getUserHostAddress(): string {
106
- return this.baseReq.headers.get('Fastly-Client-IP') ?? '';
125
+ private _parseCookies(cookieValue: string): Record<string, string> {
126
+ const parsedCookie: Record<string, string> = {};
127
+ cookieValue.split(';').forEach(function (cookie) {
128
+ if (cookie) {
129
+ const parts = cookie.split('=');
130
+ if (parts.length >= 2) parsedCookie[parts[0].trim()] = parts[1].trim();
131
+ }
132
+ });
133
+ return parsedCookie;
107
134
  }
108
135
  }
109
136
 
110
- export class FastlyHttpResponse implements IHttpResponse {
137
+ class FastlyHttpResponse implements IHttpResponse {
111
138
  private readonly headers: Headers;
112
139
 
113
140
  constructor() {
114
141
  this.headers = new Headers();
115
142
  }
116
143
 
117
- setCookie(cookieName: string, cookieValue: string, domain: string, expiration: number, _isHttpOnly: boolean, _isSecure: boolean): void {
144
+ setCookie(
145
+ cookieName: string,
146
+ cookieValue: string,
147
+ domain: string,
148
+ expiration: number,
149
+ httpOnly: boolean,
150
+ isSecure: boolean
151
+ ): void {
118
152
  const expirationDate = new Date(expiration * 1000);
119
- let setCookieString = cookieName + "=" + encodeURIComponent(cookieValue) + "; expires=" + expirationDate.toUTCString() + ";";
120
- if (domain != "") {
121
- setCookieString += " domain=" + domain + ";";
153
+
154
+ let setCookieString = `${cookieName}=${encodeURIComponent(cookieValue)}; expires=${expirationDate.toUTCString()};`;
155
+
156
+ if (domain) {
157
+ setCookieString += ` domain=${domain};`;
158
+ }
159
+
160
+ if (httpOnly) {
161
+ setCookieString += ' HttpOnly;';
162
+ }
163
+
164
+ if (isSecure) {
165
+ setCookieString += ' Secure;';
122
166
  }
123
- setCookieString += " path=/;";
124
- this.headers.set('set-cookie', setCookieString);
167
+
168
+ setCookieString += ' path=/';
169
+ this.headers.append('set-cookie', setCookieString);
125
170
  }
126
171
 
127
172
  getHeaders(): Headers {
package/src/helper.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export class QueueITHelper {
2
- static readonly KUP_VERSION: string = "fastly-2.0.3-beta.4";
2
+ static readonly KUP_VERSION: string = "fastly-4.4.3";
3
3
 
4
4
  static addKUPlatformVersion(redirectQueueUrl: string): string {
5
5
  return redirectQueueUrl + "&kupver=" + QueueITHelper.KUP_VERSION;
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export {IntegrationDetails, IntegrationEndpointProvider, IntegrationEndpointCacheConfig, resolveIntegrationDetails} from "./integrationConfigProvider"
1
+ export {IntegrationDetails, EnqueueTokenProviderFactory, IntegrationEndpointProvider, IntegrationEndpointCacheConfig, resolveIntegrationDetails} from "./integrationConfigProvider"
2
2
  export {onQueueITRequest, onQueueITResponse} from "./requestResponseHandler";
3
3
  export {RequestLogger} from "./helper";
@@ -1,6 +1,15 @@
1
1
  import { ConfigStore } from "fastly:config-store";
2
+ import { IEnqueueTokenProvider } from "@queue-it/connector-javascript";
2
3
  import { RequestLogger } from "./helper";
3
4
 
5
+ export type EnqueueTokenProviderFactory = (
6
+ customerId: string,
7
+ secretKey: string,
8
+ validityTime: number,
9
+ clientIp: string,
10
+ withKey: boolean
11
+ ) => IEnqueueTokenProvider;
12
+
4
13
  export async function getIntegrationConfig(
5
14
  details: IntegrationDetails,
6
15
  endpointProvider: IntegrationEndpointProvider
@@ -46,7 +55,11 @@ const integrationCustomerId = "customerId",
46
55
  integrationSecret = "secret",
47
56
  integrationQueueItOrigin = "queueItOrigin",
48
57
  integrationDictionary = "IntegrationConfiguration",
49
- workerHost = "workerHost";
58
+ workerHost = "workerHost",
59
+ enqueueTokenEnabledKey = "enqueueTokenEnabled",
60
+ enqueueTokenValidityTimeKey = "enqueueTokenValidityTime",
61
+ enqueueTokenKeyEnabledKey = "enqueueTokenKeyEnabled",
62
+ requestBodyEnabledKey = "requestBodyEnabled";
50
63
 
51
64
  export function resolveIntegrationDetails(): IntegrationDetails | null {
52
65
  const dict = new ConfigStore(integrationDictionary);
@@ -65,12 +78,40 @@ export function resolveIntegrationDetails(): IntegrationDetails | null {
65
78
  workerHostValue = workerHostVal;
66
79
  }
67
80
 
81
+ let enqueueTokenEnabled = true;
82
+ const enqueueTokenEnabledVal = dict.get(enqueueTokenEnabledKey);
83
+ if (enqueueTokenEnabledVal !== null) {
84
+ enqueueTokenEnabled = enqueueTokenEnabledVal.toLowerCase() === "true";
85
+ }
86
+
87
+ let enqueueTokenValidityTime = 240000;
88
+ const enqueueTokenValidityTimeVal = dict.get(enqueueTokenValidityTimeKey);
89
+ if (enqueueTokenValidityTimeVal !== null) {
90
+ enqueueTokenValidityTime = parseInt(enqueueTokenValidityTimeVal, 10);
91
+ }
92
+
93
+ let enqueueTokenKeyEnabled = false;
94
+ const enqueueTokenKeyEnabledVal = dict.get(enqueueTokenKeyEnabledKey);
95
+ if (enqueueTokenKeyEnabledVal !== null) {
96
+ enqueueTokenKeyEnabled = enqueueTokenKeyEnabledVal.toLowerCase() === "true";
97
+ }
98
+
99
+ let requestBodyEnabled = false;
100
+ const requestBodyEnabledVal = dict.get(requestBodyEnabledKey);
101
+ if (requestBodyEnabledVal !== null) {
102
+ requestBodyEnabled = requestBodyEnabledVal.toLowerCase() === "true";
103
+ }
104
+
68
105
  return new IntegrationDetails(
69
106
  dict.get(integrationQueueItOrigin)!,
70
107
  dict.get(integrationCustomerId)!,
71
108
  dict.get(integrationSecret)!,
72
109
  dict.get(integrationApiKey)!,
73
- workerHostValue
110
+ workerHostValue,
111
+ enqueueTokenEnabled,
112
+ enqueueTokenValidityTime,
113
+ enqueueTokenKeyEnabled,
114
+ requestBodyEnabled
74
115
  );
75
116
  }
76
117
 
@@ -117,12 +158,26 @@ class MockLogger implements RequestLogger {
117
158
  }
118
159
 
119
160
  export class IntegrationDetails {
161
+ public enqueueTokenProviderFactory: EnqueueTokenProviderFactory | null = null;
162
+
163
+ /**
164
+ * The client IP address of the end user. In Fastly Compute, the client IP is not available
165
+ * via request headers (unlike Cloudflare's cf-connecting-ip or Akamai's True-Client-IP).
166
+ * Instead, it must be obtained from event.client.address in the fetch event handler and
167
+ * passed here. Used by the SDK for IP-based trigger matching and enqueue token generation.
168
+ */
169
+ public clientIp: string = '';
170
+
120
171
  constructor(
121
172
  public queueItOrigin: string,
122
173
  public customerId: string,
123
174
  public secretKey: string,
124
175
  public apiKey: string,
125
176
  public workerHost: string,
177
+ public enqueueTokenEnabled: boolean = true,
178
+ public enqueueTokenValidityTime: number = 240000,
179
+ public enqueueTokenKeyEnabled: boolean = false,
180
+ public requestBodyEnabled: boolean = false,
126
181
  public provider: IntegrationEndpointProvider = new QueueItIntegrationEndpointProvider(),
127
182
  public logger: RequestLogger = new MockLogger()
128
183
  ) {}
@@ -8,31 +8,11 @@ import {
8
8
  QueueItIntegrationEndpointProvider,
9
9
  } from "./integrationConfigProvider";
10
10
 
11
- function getParameterByName(url: string, name: string): string {
12
- name = name.replace(/[\[\]]/g, '\\$&');
13
- const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
14
- const results = regex.exec(url);
15
- if (!results) return '';
16
- if (!results[2]) return '';
17
- return results[2].replace(/\+/g, ' ');
18
- }
19
-
20
- function removeQueueItToken(url: string): string {
21
- const pattern = new RegExp("([?&])(" + KnownUser.QueueITTokenKey + "=[^&]*)", 'gi');
22
- const match = pattern.exec(url);
23
- if (match === null) return url;
24
- url = url.replaceAll(match[0], '');
25
- return url;
26
- }
27
-
28
- function addNoCacheHeaders(res: Response): void {
29
- res.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0');
30
- res.headers.set('Pragma', 'no-cache');
31
- res.headers.set('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT');
32
- }
33
-
34
11
  const QUEUEIT_FAILED_HEADERNAME = "x-queueit-failed";
12
+ const QUEUEIT_CONNECTOR_EXECUTED_HEADER_NAME = "x-queueit-connector";
13
+
35
14
  let httpProvider: FastlyHttpContextProvider | null = null;
15
+ let sendNoCacheHeaders: boolean = false;
36
16
 
37
17
  export async function onQueueITRequest(
38
18
  req: Request,
@@ -52,18 +32,52 @@ export async function onQueueITRequest(
52
32
  conf.provider == null
53
33
  ? new QueueItIntegrationEndpointProvider()
54
34
  : conf.provider;
55
- httpProvider = getHttpHandler(req);
56
35
 
57
- let integrationConfigJson = await getIntegrationConfig(conf, integrationProvider);
58
- const requestUrl: string = conf.resolveWorkerRequestUrl(req.url);
36
+ let bodyString = '';
37
+ if (conf.requestBodyEnabled) {
38
+ bodyString = ((await req.clone().text()) || '').substring(0, 2048);
39
+ }
59
40
 
60
- const queueItToken = getParameterByName(requestUrl, KnownUser.QueueITTokenKey);
61
- const requestUrlWithoutToken: string = removeQueueItToken(requestUrl);
41
+ httpProvider = getHttpHandler(req, conf.clientIp, bodyString);
42
+ sendNoCacheHeaders = false;
43
+
44
+ if (conf.enqueueTokenEnabled) {
45
+ const clientIp = httpProvider.getHttpRequest().getUserHostAddress();
46
+ if (conf.enqueueTokenProviderFactory) {
47
+ httpProvider.setCustomEnqueueTokenProvider(
48
+ conf.enqueueTokenProviderFactory(
49
+ conf.customerId,
50
+ conf.secretKey,
51
+ conf.enqueueTokenValidityTime,
52
+ clientIp,
53
+ conf.enqueueTokenKeyEnabled
54
+ )
55
+ );
56
+ } else {
57
+ httpProvider.setEnqueueTokenProvider(
58
+ conf.customerId,
59
+ conf.secretKey,
60
+ conf.enqueueTokenValidityTime,
61
+ clientIp,
62
+ conf.enqueueTokenKeyEnabled
63
+ );
64
+ }
65
+ }
62
66
 
63
- // The requestUrlWithoutToken is used to match Triggers and as the Target url (where to return the users to).
64
- // It is therefor important that this is exactly the url of the users browsers. So, if your webserver is
65
- // behind e.g. a load balancer that modifies the host name or port, reformat requestUrlWithoutToken before proceeding.
66
67
  try {
68
+ const integrationConfigJson = await getIntegrationConfig(conf, integrationProvider);
69
+ const requestUrl: string = conf.resolveWorkerRequestUrl(req.url);
70
+
71
+ const queueItToken = getQueueItToken(requestUrl, httpProvider);
72
+ const requestUrlWithoutToken = requestUrl.replace(
73
+ new RegExp("([?&])(" + KnownUser.QueueITTokenKey + "=[^&]*)", "i"),
74
+ ""
75
+ );
76
+
77
+ // The requestUrlWithoutToken is used to match Triggers and as the Target url (where to return the users to).
78
+ // It is therefor important that this is exactly the url of the users browsers. So, if your webserver is
79
+ // behind e.g. a load balancer that modifies the host name or port, reformat requestUrlWithoutToken before proceeding.
80
+
67
81
  const validationResult = await KnownUser.validateRequestByIntegrationConfig(
68
82
  requestUrlWithoutToken,
69
83
  queueItToken,
@@ -75,22 +89,23 @@ export async function onQueueITRequest(
75
89
 
76
90
  if (validationResult.doRedirect()) {
77
91
  if (validationResult.isAjaxResult) {
78
- let response = new Response(null, {
92
+ const response = new Response(null, {
79
93
  status: 200,
80
94
  headers: httpProvider!.getResponseHeaders(),
81
95
  });
96
+ const headerKey = validationResult.getAjaxQueueRedirectHeaderKey();
97
+ const queueRedirectUrl = validationResult.getAjaxRedirectUrl();
98
+
82
99
  // In case of ajax call send the user to the queue by sending a custom queue-it header and redirecting user to queue from javascript
83
- response.headers.set("Access-Control-Expose-Headers", validationResult.getAjaxQueueRedirectHeaderKey());
84
100
  response.headers.set(
85
- validationResult.getAjaxQueueRedirectHeaderKey(),
86
- QueueITHelper.addKUPlatformVersion(
87
- validationResult.getAjaxRedirectUrl()
88
- )
101
+ headerKey,
102
+ QueueITHelper.addKUPlatformVersion(queueRedirectUrl)
89
103
  );
90
- addNoCacheHeaders(response);
104
+ response.headers.set("Access-Control-Expose-Headers", headerKey);
105
+ sendNoCacheHeaders = true;
91
106
  return response;
92
107
  } else {
93
- let response = new Response(null, {
108
+ const response = new Response(null, {
94
109
  status: 302,
95
110
  headers: httpProvider!.getResponseHeaders(),
96
111
  });
@@ -99,47 +114,87 @@ export async function onQueueITRequest(
99
114
  "Location",
100
115
  QueueITHelper.addKUPlatformVersion(validationResult.redirectUrl)
101
116
  );
102
- addNoCacheHeaders(response);
117
+ sendNoCacheHeaders = true;
103
118
  return response;
104
119
  }
105
120
  } else {
106
121
  // Request can continue - we remove queueittoken from querystring parameter to avoid sharing of user specific token
107
- // Support mobile scenario adding the condition !validationResult.isAjaxResult
108
122
  if (
109
- queueItToken != "" &&
110
- !validationResult.isAjaxResult &&
111
- validationResult.actionType == "Queue"
123
+ requestUrl !== requestUrlWithoutToken &&
124
+ validationResult.actionType === "Queue"
112
125
  ) {
113
- let response = new Response(null, {
126
+ const response = new Response(null, {
114
127
  status: 302,
115
128
  headers: httpProvider!.getResponseHeaders(),
116
129
  });
117
130
  response.headers.set("Location", requestUrlWithoutToken);
118
- addNoCacheHeaders(response);
131
+ sendNoCacheHeaders = true;
119
132
  return response;
120
133
  } else {
121
134
  // lets caller decide the next step, or just serve the request normally
122
135
  return null;
123
136
  }
124
137
  }
125
- } catch {
138
+ } catch (e) {
139
+ if (console && console.log) {
140
+ console.log("ERROR:" + e);
141
+ }
126
142
  httpProvider!.isError = true;
143
+ return null;
127
144
  }
128
-
129
- return null;
130
145
  }
131
146
 
132
- //Fill in the Queue-it headers
147
+ // Fill in the Queue-it headers
133
148
  export function onQueueITResponse(res: Response): void {
134
- const contextHeaders = httpProvider!.getResponseHeaders();
149
+ res.headers.set(QUEUEIT_CONNECTOR_EXECUTED_HEADER_NAME, "fastly");
150
+
151
+ if (httpProvider) {
152
+ const contextHeaders = httpProvider.getResponseHeaders();
153
+ for (const key of contextHeaders.keys()) {
154
+ if (key.length == 0) continue;
155
+ const value = contextHeaders.get(key);
156
+ if (value != null && value.length > 0)
157
+ res.headers.append(key, value);
158
+ }
159
+
160
+ if (httpProvider.isError) {
161
+ res.headers.append(QUEUEIT_FAILED_HEADERNAME, "true");
162
+ }
163
+ }
164
+
165
+ if (sendNoCacheHeaders) {
166
+ addNoCacheHeaders(res);
167
+ }
168
+ }
169
+
170
+ function addNoCacheHeaders(res: Response): void {
171
+ res.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0');
172
+ res.headers.set('Pragma', 'no-cache');
173
+ res.headers.set('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT');
174
+ }
135
175
 
136
- if (httpProvider!.isError) {
137
- res.headers.append(QUEUEIT_FAILED_HEADERNAME, "true");
176
+ function getQueueItToken(
177
+ requestUrl: string,
178
+ httpContext: FastlyHttpContextProvider
179
+ ): string {
180
+ const queueItToken = getParameterByName(requestUrl, KnownUser.QueueITTokenKey);
181
+ if (queueItToken) {
182
+ return queueItToken;
138
183
  }
139
- for (const key of contextHeaders.keys()) {
140
- if (key.length == 0) continue;
141
- const value = contextHeaders.get(key);
142
- if (value != null && value.length > 0)
143
- res.headers.append(key, value);
184
+
185
+ const tokenHeaderName = `x-${KnownUser.QueueITTokenKey}`;
186
+ return httpContext.getHttpRequest().getHeader(tokenHeaderName);
187
+ }
188
+
189
+ function getParameterByName(url: string, name: string): string {
190
+ name = name.replace(/[\[\]]/g, '\\$&');
191
+ const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
192
+ const results = regex.exec(url);
193
+ if (!results) return '';
194
+ if (!results[2]) return '';
195
+ try {
196
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
197
+ } catch {
198
+ return results[2];
144
199
  }
145
200
  }