-
Notifications
You must be signed in to change notification settings - Fork 10
/
requestResponseHandler.ts
139 lines (115 loc) · 6.2 KB
/
requestResponseHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
const QUEUEIT_FAILED_HEADERNAME = "x-queueit-failed";
const QUEUEIT_CONNECTOR_EXECUTED_HEADER_NAME = 'x-queueit-connector';
const SHOULD_IGNORE_OPTIONS_REQUESTS = false;
declare var IntegrationConfigKV: string;
declare var Response: any;
import {KnownUser, Utils} from 'queueit-knownuser'
import CloudflareHttpContextProvider from './contextProvider'
import {addKUPlatformVersion, configureKnownUserHashing, getParameterByName} from "./queueitHelpers";
import {getIntegrationConfig, tryStoreIntegrationConfig} from "./integrationConfigProvider";
export default class QueueITRequestResponseHandler {
private httpContextProvider: CloudflareHttpContextProvider | null;
private sendNoCacheHeaders: boolean = false;
constructor(private customerId: string, private secretKey: string, private readRequestBody: boolean = false) {
}
async onClientRequest(request: any) {
if (isIgnored(request)) {
return null;
}
if (request.url.indexOf('__push_queueit_config') > 0) {
const result = await tryStoreIntegrationConfig(request, IntegrationConfigKV, this.secretKey);
return new Response(result ? "Success!" : "Fail!", result ? undefined : {status: 400});
}
try {
const integrationConfigJson = await getIntegrationConfig(IntegrationConfigKV) || "";
configureKnownUserHashing(Utils);
let bodyText = "";
if (this.readRequestBody) {
//reading maximum 2k characters of body to do the mathcing
bodyText = (await request.clone().text() || "").substring(0, 2048);
}
this.httpContextProvider = new CloudflareHttpContextProvider(request, bodyText);
const queueitToken = getQueueItToken(request, this.httpContextProvider);
const requestUrl = request.url;
const requestUrlWithoutToken = requestUrl.replace(new RegExp("([\?&])(" + KnownUser.QueueITTokenKey + "=[^&]*)", 'i'), "");
// The requestUrlWithoutToken is used to match Triggers and as the Target url (where to return the users to).
// It is therefor important that this is exactly the url of the users browsers. So, if your webserver is
// behind e.g. a load balancer that modifies the host name or port, reformat requestUrlWithoutToken before proceeding.
const validationResult = KnownUser.validateRequestByIntegrationConfig(
requestUrlWithoutToken, queueitToken, integrationConfigJson,
this.customerId, this.secretKey, this.httpContextProvider);
if (validationResult.doRedirect()) {
if (validationResult.isAjaxResult) {
const response = new Response();
const headerKey = validationResult.getAjaxQueueRedirectHeaderKey();
const queueRedirectUrl = validationResult.getAjaxRedirectUrl();
// 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
response.headers.set(headerKey, addKUPlatformVersion(queueRedirectUrl));
response.headers.set('Access-Control-Expose-Headers', headerKey)
this.sendNoCacheHeaders = true;
return response;
} else {
let responseResult = new Response(null, {status: 302});
// Send the user to the queue - either because hash was missing or because is was invalid
responseResult.headers.set('Location', addKUPlatformVersion(validationResult.redirectUrl));
this.sendNoCacheHeaders = true;
return responseResult
}
} else {
// Request can continue - we remove queueittoken form querystring parameter to avoid sharing of user specific token
if (requestUrl !== requestUrlWithoutToken && validationResult.actionType === 'Queue') {
let response = new Response(null, {status: 302});
response.headers.set('Location', requestUrlWithoutToken);
this.sendNoCacheHeaders = true;
return response;
} else {
// lets caller decides the next step
return null;
}
}
} catch (e) {
// There was an error validationg the request
// Use your own logging framework to log the Exception
if (console && console.log) {
console.log("ERROR:" + e);
}
this.httpContextProvider!.isError = true;
// lets caller decides the next step
return null;
}
}
async onClientResponse(response: any) {
let newResponse = new Response(response.body, response);
newResponse.headers.set(QUEUEIT_CONNECTOR_EXECUTED_HEADER_NAME, 'cloudflare');
if (this.httpContextProvider) {
const outputCookie = this.httpContextProvider.getOutputCookie();
if (outputCookie) {
newResponse.headers.append("Set-Cookie", outputCookie);
}
if (this.httpContextProvider.isError) {
newResponse.headers.append(QUEUEIT_FAILED_HEADERNAME, "true");
}
}
if (this.sendNoCacheHeaders) {
addNoCacheHeaders(newResponse);
}
return newResponse;
}
}
function isIgnored(request: any) {
return SHOULD_IGNORE_OPTIONS_REQUESTS && request.method === 'OPTIONS';
}
function addNoCacheHeaders(response: any) {
// Adding no cache headers to prevent browsers to cache requests
response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0');
response.headers.set('Pragma', 'no-cache');
response.headers.set('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT');
}
function getQueueItToken(request: any, httpContext: CloudflareHttpContextProvider) {
let queueItToken = getParameterByName(request.url, KnownUser.QueueITTokenKey);
if (queueItToken) {
return queueItToken;
}
const tokenHeaderName = `x-${KnownUser.QueueITTokenKey}`;
return httpContext.getHttpRequest().getHeader(tokenHeaderName);
}