@queue-it/fastly 1.0.3 → 1.0.4
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/LICENSE +21 -0
- package/README.md +91 -129
- package/assembly/contextProvider.ts +122 -122
- package/assembly/helper.ts +1 -1
- package/assembly/index.ts +3 -3
- package/assembly/requestResponseHandler.ts +137 -136
- package/assembly/sdk/HttpContextProvider.ts +24 -24
- package/assembly/sdk/UserInQueueService.ts +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Queue-it
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,129 +1,91 @@
|
|
|
1
|
-
# KnownUser.V3.Fastly
|
|
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
|
-
- Go to the Fastly
|
|
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
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const myOrigin = 'Ticketania';
|
|
93
|
-
const cacheOverride = new Fastly.CacheOverride();
|
|
94
|
-
const res = Fastly.fetch(req, {
|
|
95
|
-
backend: myOrigin,
|
|
96
|
-
cacheOverride,
|
|
97
|
-
}).wait();
|
|
98
|
-
onQueueITResponse(res);
|
|
99
|
-
Fastly.respondWith(res);
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- Build and deploy the package running `fastly compute build` and `fastly compute deploy` in the same directory.
|
|
104
|
-
- Create desired waiting room(s), triggers, and actions in the Go Queue-It self-service platform.
|
|
105
|
-
Then, save/publish the configuration.
|
|
106
|
-
|
|
107
|
-
## Providing the queue configuration
|
|
108
|
-
|
|
109
|
-
The recommended way is to use the Go Queue-it self-service portal to setup the configuration. The configuration
|
|
110
|
-
specifies a set of Triggers and Actions. A Trigger is an expression matching one, more or all URLs on your website. When
|
|
111
|
-
a user enter your website and the URL matches a Trigger-expression the corresponding Action will be triggered. The
|
|
112
|
-
Action specifies which waiting room the users should be send to. In this way you can specify which waiting room(s)
|
|
113
|
-
should protect which page(s) on the fly without changing the server-side integration.
|
|
114
|
-
|
|
115
|
-
## Protecting AJAX calls
|
|
116
|
-
|
|
117
|
-
If you need to protect AJAX calls beside page loads you need to add the below JavaScript tags to your pages:
|
|
118
|
-
|
|
119
|
-
```html
|
|
120
|
-
|
|
121
|
-
<script type="text/javascript" src="//static.queue-it.net/script/queueclient.min.js"></script>
|
|
122
|
-
<script
|
|
123
|
-
data-queueit-intercept-domain="{YOUR_CURRENT_DOMAIN}"
|
|
124
|
-
data-queueit-intercept="true"
|
|
125
|
-
data-queueit-c="{YOUR_CUSTOMER_ID}"
|
|
126
|
-
type="text/javascript"
|
|
127
|
-
src="//static.queue-it.net/script/queueconfigloader.min.js">
|
|
128
|
-
</script>
|
|
129
|
-
```
|
|
1
|
+
# KnownUser.V3.Fastly
|
|
2
|
+
|
|
3
|
+
Before getting started please read the [documentation](https://github.com/queueit/Documentation/tree/main/edge-connectors) to get acquainted with edge connectors.
|
|
4
|
+
|
|
5
|
+
This Fastly Queue-it Connector (aka, Queue-it's server-side KnownUser connector) uses a Compute@Edge service to
|
|
6
|
+
integrate Queue-it's functionality into Fastly's network.
|
|
7
|
+
|
|
8
|
+
A Wasm service is required to utilize this connector.
|
|
9
|
+
|
|
10
|
+
> You can find the latest released version [here](https://github.com/queueit/KnownUser.V3.Fastly/releases/latest).
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
There are two methods of installation:
|
|
15
|
+
|
|
16
|
+
### As a standalone service
|
|
17
|
+
|
|
18
|
+
- Go to the Fastly services page and create a new **Wasm** service.
|
|
19
|
+
- Go to Domains and fill in the domain that you want your service to be reachable at. You may need to register a CNAME
|
|
20
|
+
record if you have your own domain.
|
|
21
|
+
- Then click on *Origins* and add a new host that has the hostname of your origin server.
|
|
22
|
+
You need to edit the Host and name it **origin**.
|
|
23
|
+
- Create a second host that has the hostname of `{yourCustomerId}.queue-it.net` and name it **queue-it**.
|
|
24
|
+
Edit the host, go to advanced options and fill in the same hostname in **Override host**
|
|
25
|
+
- Go to **Dictionaries** and create a new dictionary named `IntegrationConfiguration`.
|
|
26
|
+
Add the following items in the dictionary:
|
|
27
|
+
- customerId: Your customer ID
|
|
28
|
+
- apiKey: The API key for your account
|
|
29
|
+
- secret: Your KnownUserV3 secret
|
|
30
|
+
- queueItOrigin: The name of the queue-it host, in this case it's `queue-it`
|
|
31
|
+
You can find these values in the Go Queue-It self-service platform.
|
|
32
|
+
- Download the latest package file (release-package.tar.gz) from the releases page and unarchive it.
|
|
33
|
+
- Edit the `fastly.toml` file and copy the ID of your service (you can see this by opening up the service in Fastly) and
|
|
34
|
+
replace **{YourServiceId}** with it.
|
|
35
|
+
- Archive the directory in the same format (tar.gz).
|
|
36
|
+
- Go to `Package` in the Fastly service page and upload the package.
|
|
37
|
+
- To finish up and deploy your service click on the **Activate** button.
|
|
38
|
+
|
|
39
|
+
### Customizable service with the connector
|
|
40
|
+
|
|
41
|
+
- Go to the Fastly services page and create a new **Wasm** service and copy it's ID.
|
|
42
|
+
- Clone this repository and edit the fastly.toml file, make sure to set the `service_id` field to the ID you copied.
|
|
43
|
+
- Then click on *Origins* and add a new host that has the hostname of your origin server.
|
|
44
|
+
You can name the host **origin** or whatever you choose.
|
|
45
|
+
- Create a host that has the hostname of `{yourCustomerId}.queue-it.net` and name it **queue-it**.
|
|
46
|
+
Edit the host, go to advanced options and fill in the same hostname in **Override host**
|
|
47
|
+
- Open up the service in Fastly and go to **Dictionaries** and create a new dictionary named `IntegrationConfiguration`
|
|
48
|
+
.
|
|
49
|
+
Add the following items in the dictionary:
|
|
50
|
+
- customerId: Your customer ID
|
|
51
|
+
- apiKey: The API key for your account
|
|
52
|
+
- secret: Your KnownUserV3 secret
|
|
53
|
+
- queueItOrigin: The name of the queue-it origin, in this case it's `queue-it`
|
|
54
|
+
You can find these values in the Go Queue-It self-service platform.
|
|
55
|
+
- You need to add some code that uses this connector. Here is an example:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import {Fastly} from "@fastly/as-compute";
|
|
59
|
+
import {onQueueITRequest, IntegrationDetails, onQueueITResponse} from "@queue-it/fastly";
|
|
60
|
+
|
|
61
|
+
const req = Fastly.getClientRequest();
|
|
62
|
+
|
|
63
|
+
// This is optional and can be null if it's specified in your Dictionary
|
|
64
|
+
/* const integrationDetails = new IntegrationDetails(
|
|
65
|
+
{QueueItOriginName},
|
|
66
|
+
{CustomerId},
|
|
67
|
+
{SecretKey},
|
|
68
|
+
{ApiKey},
|
|
69
|
+
{workerHost});
|
|
70
|
+
let res = onQueueITRequest(req, integrationDetails);
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
let res = onQueueITRequest(req, null);
|
|
74
|
+
|
|
75
|
+
if (res != null) {
|
|
76
|
+
Fastly.respondWith(res!);
|
|
77
|
+
} else {
|
|
78
|
+
const myOrigin = 'origin';
|
|
79
|
+
const cacheOverride = new Fastly.CacheOverride();
|
|
80
|
+
const res = Fastly.fetch(req, {
|
|
81
|
+
backend: myOrigin,
|
|
82
|
+
cacheOverride,
|
|
83
|
+
}).wait();
|
|
84
|
+
onQueueITResponse(res);
|
|
85
|
+
Fastly.respondWith(res);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Build and deploy the package running `fastly compute build` and `fastly compute deploy` in the same directory.
|
|
90
|
+
- Create desired waiting room(s), triggers, and actions in the Go Queue-It self-service platform.
|
|
91
|
+
Then, save/publish the configuration.
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import {IHttpContextProvider, IHttpRequest, IHttpResponse} from "./sdk/HttpContextProvider";
|
|
2
|
-
import {Request, Fastly, Headers} from "@fastly/as-compute";
|
|
3
|
-
import {decodeURIComponent, encodeURIComponent} from "./sdk/helpers/Uri";
|
|
4
|
-
|
|
5
|
-
export function getHttpHandler(req: Request): FastlyHttpContextProvider {
|
|
6
|
-
return new FastlyHttpContextProvider(req);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class FastlyHttpContextProvider implements IHttpContextProvider {
|
|
10
|
-
isError: bool = false;
|
|
11
|
-
private readonly req: FastlyHttpRequest;
|
|
12
|
-
// @ts-ignore
|
|
13
|
-
private readonly res: FastlyHttpResponse;
|
|
14
|
-
|
|
15
|
-
constructor(fReq: Request) {
|
|
16
|
-
this.req = new FastlyHttpRequest(fReq);
|
|
17
|
-
this.res = new FastlyHttpResponse();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getHttpRequest(): IHttpRequest {
|
|
21
|
-
return this.req;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getHttpResponse(): IHttpResponse {
|
|
25
|
-
return this.res;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class FastlyHttpRequest implements IHttpRequest {
|
|
30
|
-
private parsedCookieDic: Map<string, string>
|
|
31
|
-
private bodyFetched: bool = false;
|
|
32
|
-
private body: string = '';
|
|
33
|
-
|
|
34
|
-
constructor(private baseReq: Request) {
|
|
35
|
-
this.parsedCookieDic = new Map();
|
|
36
|
-
this.bodyFetched = false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private parseCookies(cookieValue: string): void {
|
|
40
|
-
const cookies = cookieValue.split(';');
|
|
41
|
-
for (let i = 0; i < cookies.length; i++) {
|
|
42
|
-
let cookieKV = cookies[i].split('=', 2);
|
|
43
|
-
if (cookieKV.length >= 2) {
|
|
44
|
-
this.parsedCookieDic.set(cookieKV[0].trim(), cookieKV[1].trim())
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private handleBody(): void {
|
|
50
|
-
if (this.baseReq.bodyUsed || this.bodyFetched) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
this.bodyFetched = true;
|
|
54
|
-
const rawBody: ArrayBuffer | null = null; // this.context.req.arrayBuffer();
|
|
55
|
-
if (rawBody == null) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (rawBody!.byteLength == 0) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.body = ''; // this.context.req.text()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
getAbsoluteUri(): string {
|
|
66
|
-
return this.baseReq.url;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
getCookieValue(cookieKey: string): string {
|
|
70
|
-
if (this.parsedCookieDic.keys().length == 0) {
|
|
71
|
-
this.parseCookies(this.getHeader('cookie'))
|
|
72
|
-
}
|
|
73
|
-
return this.parsedCookieDic.has(cookieKey) ? decodeURIComponent(this.parsedCookieDic.get(cookieKey)) : '';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getHeader(name: string): string {
|
|
77
|
-
if (name.length == 0) return "";
|
|
78
|
-
if (!this.baseReq.headers.has(name)) {
|
|
79
|
-
return '';
|
|
80
|
-
}
|
|
81
|
-
const value = this.baseReq.headers.get(name);
|
|
82
|
-
if (value == null) return '';
|
|
83
|
-
return value!;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
getRequestBodyAsString(): string {
|
|
87
|
-
if (!this.bodyFetched) {
|
|
88
|
-
this.handleBody();
|
|
89
|
-
}
|
|
90
|
-
return this.body;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
getUserAgent(): string {
|
|
94
|
-
return this.baseReq.headers.has('user-agent') ? this.baseReq.headers.get('user-agent')! : '';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
getUserHostAddress(): string {
|
|
98
|
-
return Fastly.getClientIpAddressString();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export class FastlyHttpResponse implements IHttpResponse {
|
|
103
|
-
private readonly headers: Headers;
|
|
104
|
-
|
|
105
|
-
constructor() {
|
|
106
|
-
this.headers = new Headers();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
setCookie(cookieName: string, cookieValue: string, domain: string, expiration: i64): void {
|
|
110
|
-
const expirationDate = new Date(expiration * 1000);
|
|
111
|
-
let setCookieString = cookieName + "=" + encodeURIComponent(cookieValue) + "; expires=" + expirationDate.toUTCString() + ";";
|
|
112
|
-
if (domain != "") {
|
|
113
|
-
setCookieString += " domain=" + domain + ";";
|
|
114
|
-
}
|
|
115
|
-
setCookieString += " path=/;";
|
|
116
|
-
this.headers.set('set-cookie', setCookieString);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
getHeaders(): Headers {
|
|
120
|
-
return this.headers;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
1
|
+
import {IHttpContextProvider, IHttpRequest, IHttpResponse} from "./sdk/HttpContextProvider";
|
|
2
|
+
import {Request, Fastly, Headers} from "@fastly/as-compute";
|
|
3
|
+
import {decodeURIComponent, encodeURIComponent} from "./sdk/helpers/Uri";
|
|
4
|
+
|
|
5
|
+
export function getHttpHandler(req: Request): FastlyHttpContextProvider {
|
|
6
|
+
return new FastlyHttpContextProvider(req);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class FastlyHttpContextProvider implements IHttpContextProvider {
|
|
10
|
+
isError: bool = false;
|
|
11
|
+
private readonly req: FastlyHttpRequest;
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
private readonly res: FastlyHttpResponse;
|
|
14
|
+
|
|
15
|
+
constructor(fReq: Request) {
|
|
16
|
+
this.req = new FastlyHttpRequest(fReq);
|
|
17
|
+
this.res = new FastlyHttpResponse();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getHttpRequest(): IHttpRequest {
|
|
21
|
+
return this.req;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getHttpResponse(): IHttpResponse {
|
|
25
|
+
return this.res;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class FastlyHttpRequest implements IHttpRequest {
|
|
30
|
+
private parsedCookieDic: Map<string, string>
|
|
31
|
+
private bodyFetched: bool = false;
|
|
32
|
+
private body: string = '';
|
|
33
|
+
|
|
34
|
+
constructor(private baseReq: Request) {
|
|
35
|
+
this.parsedCookieDic = new Map();
|
|
36
|
+
this.bodyFetched = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private parseCookies(cookieValue: string): void {
|
|
40
|
+
const cookies = cookieValue.split(';');
|
|
41
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
42
|
+
let cookieKV = cookies[i].split('=', 2);
|
|
43
|
+
if (cookieKV.length >= 2) {
|
|
44
|
+
this.parsedCookieDic.set(cookieKV[0].trim(), cookieKV[1].trim())
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private handleBody(): void {
|
|
50
|
+
if (this.baseReq.bodyUsed || this.bodyFetched) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.bodyFetched = true;
|
|
54
|
+
const rawBody: ArrayBuffer | null = null; // this.context.req.arrayBuffer();
|
|
55
|
+
if (rawBody == null) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (rawBody!.byteLength == 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.body = ''; // this.context.req.text()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getAbsoluteUri(): string {
|
|
66
|
+
return this.baseReq.url;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getCookieValue(cookieKey: string): string {
|
|
70
|
+
if (this.parsedCookieDic.keys().length == 0) {
|
|
71
|
+
this.parseCookies(this.getHeader('cookie'))
|
|
72
|
+
}
|
|
73
|
+
return this.parsedCookieDic.has(cookieKey) ? decodeURIComponent(this.parsedCookieDic.get(cookieKey)) : '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getHeader(name: string): string {
|
|
77
|
+
if (name.length == 0) return "";
|
|
78
|
+
if (!this.baseReq.headers.has(name)) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
const value = this.baseReq.headers.get(name);
|
|
82
|
+
if (value == null) return '';
|
|
83
|
+
return value!;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRequestBodyAsString(): string {
|
|
87
|
+
if (!this.bodyFetched) {
|
|
88
|
+
this.handleBody();
|
|
89
|
+
}
|
|
90
|
+
return this.body;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getUserAgent(): string {
|
|
94
|
+
return this.baseReq.headers.has('user-agent') ? this.baseReq.headers.get('user-agent')! : '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getUserHostAddress(): string {
|
|
98
|
+
return Fastly.getClientIpAddressString();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class FastlyHttpResponse implements IHttpResponse {
|
|
103
|
+
private readonly headers: Headers;
|
|
104
|
+
|
|
105
|
+
constructor() {
|
|
106
|
+
this.headers = new Headers();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setCookie(cookieName: string, cookieValue: string, domain: string, expiration: i64): void {
|
|
110
|
+
const expirationDate = new Date(expiration * 1000);
|
|
111
|
+
let setCookieString = cookieName + "=" + encodeURIComponent(cookieValue) + "; expires=" + expirationDate.toUTCString() + ";";
|
|
112
|
+
if (domain != "") {
|
|
113
|
+
setCookieString += " domain=" + domain + ";";
|
|
114
|
+
}
|
|
115
|
+
setCookieString += " path=/;";
|
|
116
|
+
this.headers.set('set-cookie', setCookieString);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getHeaders(): Headers {
|
|
120
|
+
return this.headers;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/assembly/helper.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Utils } from "./sdk/QueueITHelpers";
|
|
|
2
2
|
import { hmacString } from "./sdk/helpers/crypto";
|
|
3
3
|
|
|
4
4
|
export class QueueITHelper {
|
|
5
|
-
static readonly KUP_VERSION: string = "fastly-1.0.
|
|
5
|
+
static readonly KUP_VERSION: string = "fastly-1.0.4";
|
|
6
6
|
|
|
7
7
|
static configureKnownUserHashing(): void {
|
|
8
8
|
Utils.generateSHA256Hash = hmacString;
|
package/assembly/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {IntegrationDetails, IntegrationEndpointProvider, IntegrationEndpointCacheConfig} from "./integrationConfigProvider"
|
|
2
|
-
export {onQueueITRequest, onQueueITResponse} from "./requestResponseHandler";
|
|
3
|
-
export {RequestLogger} from "./helper";
|
|
1
|
+
export {IntegrationDetails, IntegrationEndpointProvider, IntegrationEndpointCacheConfig} from "./integrationConfigProvider"
|
|
2
|
+
export {onQueueITRequest, onQueueITResponse} from "./requestResponseHandler";
|
|
3
|
+
export {RequestLogger} from "./helper";
|
|
@@ -1,136 +1,137 @@
|
|
|
1
|
-
import { Request, Response, Headers } from "@fastly/as-compute";
|
|
2
|
-
import { KnownUser } from "./sdk/KnownUser";
|
|
3
|
-
import { QueueITHelper } from "./helper";
|
|
4
|
-
import { FastlyHttpContextProvider, getHttpHandler } from "./contextProvider";
|
|
5
|
-
import {
|
|
6
|
-
getIntegrationConfig,
|
|
7
|
-
resolveIntegrationDetails,
|
|
8
|
-
IntegrationDetails,
|
|
9
|
-
QueueItIntegrationEndpointProvider,
|
|
10
|
-
} from "./integrationConfigProvider";
|
|
11
|
-
import { Utils } from "./sdk/QueueITHelpers";
|
|
12
|
-
|
|
13
|
-
const QUEUEIT_FAILED_HEADERNAME = "x-queueit-failed";
|
|
14
|
-
let httpProvider: FastlyHttpContextProvider | null = null;
|
|
15
|
-
|
|
16
|
-
export function onQueueITRequest(
|
|
17
|
-
req: Request,
|
|
18
|
-
conf: IntegrationDetails | null = null
|
|
19
|
-
): Response | null {
|
|
20
|
-
if (conf == null) {
|
|
21
|
-
conf = resolveIntegrationDetails();
|
|
22
|
-
}
|
|
23
|
-
if (conf == null) {
|
|
24
|
-
return new Response(String.UTF8.encode("No integration details found."), {
|
|
25
|
-
headers: new Headers(),
|
|
26
|
-
status: 404,
|
|
27
|
-
url: "",
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const integrationProvider =
|
|
32
|
-
conf.provider == null
|
|
33
|
-
? new QueueItIntegrationEndpointProvider()
|
|
34
|
-
: conf.provider;
|
|
35
|
-
QueueITHelper.configureKnownUserHashing();
|
|
36
|
-
httpProvider = getHttpHandler(req);
|
|
37
|
-
|
|
38
|
-
let integrationConfigJson = getIntegrationConfig(conf, integrationProvider);
|
|
39
|
-
const requestUrl: string = conf.resolveWorkerRequestUrl(req.url);
|
|
40
|
-
|
|
41
|
-
const queueItToken = Utils.getParameterByName(
|
|
42
|
-
requestUrl,
|
|
43
|
-
KnownUser.QueueITTokenKey
|
|
44
|
-
);
|
|
45
|
-
const requestUrlWithoutToken: string = Utils.removeQueueItToken(requestUrl);
|
|
46
|
-
|
|
47
|
-
// The requestUrlWithoutToken is used to match Triggers and as the Target url (where to return the users to).
|
|
48
|
-
// It is therefor important that this is exactly the url of the users browsers. So, if your webserver is
|
|
49
|
-
// behind e.g. a load balancer that modifies the host name or port, reformat requestUrlWithoutToken before proceeding.
|
|
50
|
-
const validationResultPair = KnownUser.validateRequestByIntegrationConfig(
|
|
51
|
-
requestUrlWithoutToken,
|
|
52
|
-
queueItToken,
|
|
53
|
-
integrationConfigJson,
|
|
54
|
-
conf.customerId,
|
|
55
|
-
conf.secretKey,
|
|
56
|
-
httpProvider!
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
validationResultPair.first != null &&
|
|
61
|
-
validationResultPair.first!.doRedirect()
|
|
62
|
-
) {
|
|
63
|
-
const validationResult = validationResultPair.first!;
|
|
64
|
-
|
|
65
|
-
if (validationResult.isAjaxResult) {
|
|
66
|
-
let response = new Response(null, {
|
|
67
|
-
status: 200,
|
|
68
|
-
headers: httpProvider!.getHttpResponse().getHeaders(),
|
|
69
|
-
url: "",
|
|
70
|
-
});
|
|
71
|
-
// 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
|
|
72
|
-
response.headers.set(
|
|
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
|
-
validationResult.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
1
|
+
import { Request, Response, Headers } from "@fastly/as-compute";
|
|
2
|
+
import { KnownUser } from "./sdk/KnownUser";
|
|
3
|
+
import { QueueITHelper } from "./helper";
|
|
4
|
+
import { FastlyHttpContextProvider, getHttpHandler } from "./contextProvider";
|
|
5
|
+
import {
|
|
6
|
+
getIntegrationConfig,
|
|
7
|
+
resolveIntegrationDetails,
|
|
8
|
+
IntegrationDetails,
|
|
9
|
+
QueueItIntegrationEndpointProvider,
|
|
10
|
+
} from "./integrationConfigProvider";
|
|
11
|
+
import { Utils } from "./sdk/QueueITHelpers";
|
|
12
|
+
|
|
13
|
+
const QUEUEIT_FAILED_HEADERNAME = "x-queueit-failed";
|
|
14
|
+
let httpProvider: FastlyHttpContextProvider | null = null;
|
|
15
|
+
|
|
16
|
+
export function onQueueITRequest(
|
|
17
|
+
req: Request,
|
|
18
|
+
conf: IntegrationDetails | null = null
|
|
19
|
+
): Response | null {
|
|
20
|
+
if (conf == null) {
|
|
21
|
+
conf = resolveIntegrationDetails();
|
|
22
|
+
}
|
|
23
|
+
if (conf == null) {
|
|
24
|
+
return new Response(String.UTF8.encode("No integration details found."), {
|
|
25
|
+
headers: new Headers(),
|
|
26
|
+
status: 404,
|
|
27
|
+
url: "",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const integrationProvider =
|
|
32
|
+
conf.provider == null
|
|
33
|
+
? new QueueItIntegrationEndpointProvider()
|
|
34
|
+
: conf.provider;
|
|
35
|
+
QueueITHelper.configureKnownUserHashing();
|
|
36
|
+
httpProvider = getHttpHandler(req);
|
|
37
|
+
|
|
38
|
+
let integrationConfigJson = getIntegrationConfig(conf, integrationProvider);
|
|
39
|
+
const requestUrl: string = conf.resolveWorkerRequestUrl(req.url);
|
|
40
|
+
|
|
41
|
+
const queueItToken = Utils.getParameterByName(
|
|
42
|
+
requestUrl,
|
|
43
|
+
KnownUser.QueueITTokenKey
|
|
44
|
+
);
|
|
45
|
+
const requestUrlWithoutToken: string = Utils.removeQueueItToken(requestUrl);
|
|
46
|
+
|
|
47
|
+
// The requestUrlWithoutToken is used to match Triggers and as the Target url (where to return the users to).
|
|
48
|
+
// It is therefor important that this is exactly the url of the users browsers. So, if your webserver is
|
|
49
|
+
// behind e.g. a load balancer that modifies the host name or port, reformat requestUrlWithoutToken before proceeding.
|
|
50
|
+
const validationResultPair = KnownUser.validateRequestByIntegrationConfig(
|
|
51
|
+
requestUrlWithoutToken,
|
|
52
|
+
queueItToken,
|
|
53
|
+
integrationConfigJson,
|
|
54
|
+
conf.customerId,
|
|
55
|
+
conf.secretKey,
|
|
56
|
+
httpProvider!
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
validationResultPair.first != null &&
|
|
61
|
+
validationResultPair.first!.doRedirect()
|
|
62
|
+
) {
|
|
63
|
+
const validationResult = validationResultPair.first!;
|
|
64
|
+
|
|
65
|
+
if (validationResult.isAjaxResult) {
|
|
66
|
+
let response = new Response(null, {
|
|
67
|
+
status: 200,
|
|
68
|
+
headers: httpProvider!.getHttpResponse().getHeaders(),
|
|
69
|
+
url: "",
|
|
70
|
+
});
|
|
71
|
+
// 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
|
|
72
|
+
response.headers.set("Access-Control-Expose-Headers", validationResult.getAjaxQueueRedirectHeaderKey());
|
|
73
|
+
response.headers.set(
|
|
74
|
+
validationResult.getAjaxQueueRedirectHeaderKey(),
|
|
75
|
+
QueueITHelper.addKUPlatformVersion(
|
|
76
|
+
validationResult.getAjaxRedirectUrl()
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
Utils.addNoCacheHeaders(response);
|
|
80
|
+
return response;
|
|
81
|
+
} else {
|
|
82
|
+
let response = new Response(null, {
|
|
83
|
+
status: 302,
|
|
84
|
+
headers: httpProvider!.getHttpResponse().getHeaders(),
|
|
85
|
+
url: "",
|
|
86
|
+
});
|
|
87
|
+
// Send the user to the queue - either because hash was missing or because is was invalid
|
|
88
|
+
response.headers.set(
|
|
89
|
+
"Location",
|
|
90
|
+
QueueITHelper.addKUPlatformVersion(validationResult.redirectUrl)
|
|
91
|
+
);
|
|
92
|
+
Utils.addNoCacheHeaders(response);
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
} else if (validationResultPair.first != null) {
|
|
96
|
+
const validationResult = validationResultPair.first!;
|
|
97
|
+
// Request can continue - we remove queueittoken form querystring parameter to avoid sharing of user specific token
|
|
98
|
+
// Support mobile scenario adding the condition !validationResult.isAjaxResult
|
|
99
|
+
if (
|
|
100
|
+
queueItToken != "" &&
|
|
101
|
+
!validationResult.isAjaxResult &&
|
|
102
|
+
validationResult.actionType == "Queue"
|
|
103
|
+
) {
|
|
104
|
+
let response = new Response(null, {
|
|
105
|
+
status: 302,
|
|
106
|
+
headers: httpProvider!.getHttpResponse().getHeaders(),
|
|
107
|
+
url: requestUrlWithoutToken,
|
|
108
|
+
});
|
|
109
|
+
response.headers.set("Location", requestUrlWithoutToken);
|
|
110
|
+
Utils.addNoCacheHeaders(response);
|
|
111
|
+
return response;
|
|
112
|
+
} else {
|
|
113
|
+
// lets caller decide the next step, or just serve the request normally
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
} else if (validationResultPair.second != null) {
|
|
117
|
+
httpProvider!.isError = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//Fill in the Queue-it headers
|
|
124
|
+
export function onQueueITResponse(res: Response): void {
|
|
125
|
+
const contextHeaders = httpProvider!.getHttpResponse().getHeaders();
|
|
126
|
+
const contextHeaderKeys = contextHeaders.keys();
|
|
127
|
+
|
|
128
|
+
if (httpProvider!.isError) {
|
|
129
|
+
res.headers.append(QUEUEIT_FAILED_HEADERNAME, "true");
|
|
130
|
+
}
|
|
131
|
+
for (let i = 0; i < contextHeaderKeys.length; i++) {
|
|
132
|
+
if (contextHeaderKeys[i].length == 0) continue;
|
|
133
|
+
let value = contextHeaders.get(contextHeaderKeys[i]);
|
|
134
|
+
if (value != null && value!.length > 0)
|
|
135
|
+
res.headers.append(contextHeaderKeys[i], value!);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { Headers } from "@fastly/as-compute";
|
|
2
|
-
|
|
3
|
-
export interface IHttpRequest {
|
|
4
|
-
getUserAgent(): string;
|
|
5
|
-
getHeader(name: string): string;
|
|
6
|
-
getAbsoluteUri(): string;
|
|
7
|
-
getUserHostAddress(): string;
|
|
8
|
-
getCookieValue(cookieKey: string): string;
|
|
9
|
-
getRequestBodyAsString(): string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface IHttpResponse {
|
|
13
|
-
setCookie(cookieName: string, cookieValue: string, domain: string, expiration: i64): void;
|
|
14
|
-
getHeaders(): Headers;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface IHttpContextProvider {
|
|
18
|
-
getHttpRequest(): IHttpRequest;
|
|
19
|
-
getHttpResponse(): IHttpResponse;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface IDateTimeProvider {
|
|
23
|
-
getCurrentTime(): Date
|
|
24
|
-
}
|
|
1
|
+
import { Headers } from "@fastly/as-compute";
|
|
2
|
+
|
|
3
|
+
export interface IHttpRequest {
|
|
4
|
+
getUserAgent(): string;
|
|
5
|
+
getHeader(name: string): string;
|
|
6
|
+
getAbsoluteUri(): string;
|
|
7
|
+
getUserHostAddress(): string;
|
|
8
|
+
getCookieValue(cookieKey: string): string;
|
|
9
|
+
getRequestBodyAsString(): string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IHttpResponse {
|
|
13
|
+
setCookie(cookieName: string, cookieValue: string, domain: string, expiration: i64): void;
|
|
14
|
+
getHeaders(): Headers;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IHttpContextProvider {
|
|
18
|
+
getHttpRequest(): IHttpRequest;
|
|
19
|
+
getHttpResponse(): IHttpResponse;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IDateTimeProvider {
|
|
23
|
+
getCurrentTime(): Date
|
|
24
|
+
}
|
|
@@ -3,7 +3,7 @@ import {ActionTypes, RequestValidationResult, QueueEventConfig, CancelEventConfi
|
|
|
3
3
|
import {StateInfo, UserInQueueStateCookieRepository} from './UserInQueueStateCookieRepository'
|
|
4
4
|
|
|
5
5
|
export class UserInQueueService {
|
|
6
|
-
static readonly SDK_VERSION: string = "v3-asmscrpt-
|
|
6
|
+
static readonly SDK_VERSION: string = "v3-asmscrpt-3.6.1";
|
|
7
7
|
|
|
8
8
|
constructor(private userInQueueStateRepository: UserInQueueStateCookieRepository) {
|
|
9
9
|
}
|