Skip to content

Commit a87059a

Browse files
Merge pull request #106 from IABTechLab/llp-UID2-3760-ssl-dev-experience
Create a dev testing site to make it easier to test various cross-domain and CSTG scenarios.
2 parents 14d8253 + 2493a19 commit a87059a

38 files changed

+3872
-957
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
dist
3-
lib
3+
lib
4+
ca

createCA.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { createCA, createCert } from 'mkcert';
2+
import { devDomains } from './localtest/siteDetails';
3+
import fs from 'node:fs/promises';
4+
5+
const domains = devDomains;
6+
7+
const caFolder = './ca/';
8+
const caFile = `${caFolder}ca.crt`;
9+
const caKey = `${caFolder}ca.key`;
10+
const certFile = `${caFolder}cert.crt`;
11+
const certKey = `${caFolder}cert.key`;
12+
13+
const overwriteFileOptions = {
14+
flag: 'w',
15+
};
16+
const failIfExistsFileOptions = {
17+
flag: 'wx',
18+
};
19+
20+
const fileExists = async (path) => !!(await fs.stat(path).catch((e) => false));
21+
const getOrCreateCA = async () => {
22+
if (await fileExists(caFile)) {
23+
console.log('Found existing CA, loading...');
24+
return {
25+
cert: await fs.readFile(caFile, { encoding: 'utf8' }),
26+
key: await fs.readFile(caKey, { encoding: 'utf8' }),
27+
};
28+
} else {
29+
console.log('Creating new CA...');
30+
const ca = await createCA({
31+
organization: 'UID2 local dev CA',
32+
countryCode: 'AU',
33+
state: 'NSW',
34+
locality: 'Sydney',
35+
validity: 3650,
36+
});
37+
await fs.mkdir(caFolder, { recursive: true });
38+
await fs.writeFile(caFile, ca.cert, failIfExistsFileOptions);
39+
await fs.writeFile(caKey, ca.key, failIfExistsFileOptions);
40+
return ca;
41+
}
42+
};
43+
44+
async function createCerts() {
45+
const ca = await getOrCreateCA();
46+
console.log(`Creating a certificate for ${domains.join(', ')}`);
47+
const cert = await createCert({
48+
ca: { key: ca.key, cert: ca.cert },
49+
domains,
50+
validity: 3650,
51+
});
52+
console.log('Certificate created.');
53+
54+
await fs.writeFile(certFile, `${cert.cert}${ca.cert}`, overwriteFileOptions);
55+
await fs.writeFile(certKey, cert.key, overwriteFileOptions);
56+
57+
console.log('Certificate saved.');
58+
}
59+
60+
createCerts();

localtest/auth/auth.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>UID2 local dev setup: Auth/login</title>
5+
<%~ include("shared/googleFontTags.html") %> <%~ include("shared/uid2SdkTag.html") %>
6+
<link href="../style.scss" rel="stylesheet" />
7+
<link href="./auth.scss" rel="stylesheet" />
8+
<script src="./auth.tsx" defer="defer"></script>
9+
</head>
10+
<body>
11+
<div id="app"></div>
12+
</body>
13+
</html>

localtest/auth/auth.scss

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.content {
2+
background-color: aliceblue;
3+
margin: 0 50px;
4+
form {
5+
width: auto;
6+
display: flex;
7+
flex-direction: column;
8+
span {
9+
margin: 0 20px 0 0;
10+
}
11+
div {
12+
display: flex;
13+
input {
14+
flex-grow: 1;
15+
}
16+
}
17+
button {
18+
margin: 20px auto 0 auto;
19+
}
20+
}
21+
22+
.logged-in {
23+
div {
24+
display: flex;
25+
gap: 20px;
26+
}
27+
.token {
28+
word-break: break-all;
29+
}
30+
}
31+
}

localtest/auth/auth.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createApp } from '../shared/createApp';
2+
import { Layout } from '../shared/layout';
3+
import { setUid2Identity, useUid2Identity } from '../shared/uid2Identity';
4+
import type { Identity } from '../../src/exports';
5+
import { FormEventHandler, useState } from 'react';
6+
import { getEmailCookie, setEmailCookie } from '../shared/user';
7+
import { initUid2Sdk } from '../shared/uid2Helper';
8+
import { devSiteMap } from '../siteDetails';
9+
10+
initUid2Sdk();
11+
const mainSiteUrl = devSiteMap.www.url;
12+
13+
type LoggedInProps = Readonly<{ identity: Identity; email?: string }>;
14+
function LoggedIn({ identity, email }: LoggedInProps) {
15+
return (
16+
<div className='logged-in'>
17+
<div>
18+
<span>Email</span>
19+
<span>
20+
{!!email && email}
21+
{!email && '<Not logged in>'}
22+
</span>
23+
</div>
24+
<div>
25+
<span>Token</span>
26+
<span className='token'>{identity.advertising_token}</span>
27+
</div>
28+
<div>
29+
<span>
30+
<a href={mainSiteUrl}>Back to the main site</a>
31+
</span>
32+
</div>
33+
</div>
34+
);
35+
}
36+
37+
type LoginFormProps = Readonly<{
38+
setEmail: (email: string) => void;
39+
}>;
40+
function LoginForm({ setEmail }: LoginFormProps) {
41+
const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
42+
e.preventDefault();
43+
const email = (e.currentTarget.elements as any).email.value;
44+
if (email) {
45+
console.log(`Sending CSTG request for ${email}...`);
46+
await setUid2Identity(email);
47+
console.log(`CSTG request for ${email} complete`);
48+
setEmailCookie(email);
49+
setEmail(email);
50+
}
51+
};
52+
return (
53+
<form onSubmit={handleSubmit}>
54+
<div>
55+
<span>Email</span>
56+
<input type='text' id='email' />
57+
</div>
58+
<button type='submit'>Log in</button>
59+
</form>
60+
);
61+
}
62+
63+
function Auth() {
64+
const [email, setEmail] = useState(getEmailCookie());
65+
const identity = useUid2Identity();
66+
return (
67+
<>
68+
{!!identity && <LoggedIn identity={identity} email={email} />}
69+
{!identity && <LoginForm setEmail={setEmail} />}
70+
</>
71+
);
72+
}
73+
74+
createApp(
75+
<Layout siteName='auth'>
76+
<Auth />
77+
</Layout>
78+
);

localtest/auth/logout.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>UID2 local dev setup: Auth/logout</title>
5+
<%~ include("shared/googleFontTags.html") %> <%~ include("shared/uid2SdkTag.html") %>
6+
<link href="../style.scss" rel="stylesheet" />
7+
<script src="./logout.ts"></script>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
</body>
12+
</html>

localtest/auth/logout.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { UID2 } from '../../src/uid2Sdk';
2+
import { initUid2Sdk } from '../shared/uid2Helper';
3+
import { setEmailCookie } from '../shared/user';
4+
import { devSiteMap } from '../siteDetails';
5+
6+
const mainSiteUrl = devSiteMap.www.url;
7+
8+
initUid2Sdk((event) => {
9+
if (event === 'InitCompleted') {
10+
setEmailCookie('');
11+
(window.__uid2 as UID2).disconnect();
12+
window.location.replace(mainSiteUrl);
13+
}
14+
});

localtest/configuration.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Configuration
2+
3+
## Changing the port
4+
5+
This site runs on port 443 by default to give the most realistic setup possible. You can change this by updating the port in `./siteDetails.js`.
6+
7+
## Stop the `localtest` npm task from opening a browser tab
8+
9+
If you restart the server regularly but keep the tab open, you might not want the npm task to open the tab for you. To do this, comment out the `open` entry in `server.ts` - i.e. the following line:
10+
11+
```
12+
open: devSiteMap['www'].url,
13+
```

localtest/overview.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Overview
2+
3+
Running the npm task `localtest` (`npm run localtest`) will start the server running on port 443. This is the default SSL port and you mustn't have anything else running on it. You can change this - see [configuration.md](./configuration.md).
4+
5+
You can access the site from any of the domains listed in `./siteDetails.js`. For example, you can visit `https://www.uid2-local-dev-setup.com/`. All files built for any domain are available on all domains, however each domain will get a different default index file when you visit the URL with no path.
6+
7+
## Folder layout
8+
9+
### `./static/`
10+
11+
All files in here are served as-is with no build pipeline. E.g. `thirdparty-script.js` is available on all domains - both of these URLs work fine:
12+
https://www.unrelated-third-party-test.com/thirdparty-script.js
13+
https://www.uid2-local-dev-setup.com/thirdparty-script.js
14+
15+
### `./shared/`
16+
17+
By convention, files imported to multiple sites go here. They are only used by the build process and aren't directly available via a URL.
18+
19+
### Other folders (e.g. `./www/`, `./auth/`)
20+
21+
Each of the folders defined in `./siteDetails.js` is a site (the site `name` property matches the folder). Given this entry in `siteDetails`:
22+
23+
```
24+
{
25+
name: 'www',
26+
domain: 'www.uid2-local-dev-setup.com',
27+
},
28+
```
29+
30+
Based on the name, it will look in `./www/` for files. A HTML file with the same name as the folder (in this case, `./www/www.html`) is the default index page for that domain.
31+
32+
While you can access any file using any domain, please keep domain-specific files in the correct folder. Put files that you want to be served with no build step (e.g. images, shared JS libraries that you don't want built into a bundle) in `./static/`, and shared files that are imported or included into multiple sites in `./shared/`.
33+
34+
## Build process & files
35+
36+
Webpack is configured to use [html-bundler-webpack-plugin](https://github.com/webdiscus/html-bundler-webpack-plugin). It will treat every html file in one of the site folders as an entry-point.
37+
38+
You can reference other files using relative paths, and webpack will build your bundles and update the URLs as needed.
39+
40+
For example, this tag in `./www/www.html`:
41+
42+
```html
43+
<script src="./www.tsx" defer="defer"></script>
44+
```
45+
46+
Will result in `./www/www.tsx` being built (with all imported dependencies) to something like `www.bundle.js` and the script tag being updated to:
47+
48+
```html
49+
<script src="www.bundle.js" defer="defer"></script>
50+
```
51+
52+
The same applies for `.scss` files.
53+
54+
## HTML templating
55+
56+
`html-bundler-webpack-plugin` comes bundled with [eta.js](https://eta.js.org/) out of the box. It will be used to pre-process HTML files. You can provide data to the template in `./webpack.config.js` (make sure you get this one and not the webpack config in the root folder) - see the `data` value passed in to the `HtmlBundlerPlugin` constructor.
57+
58+
For example, this is used to provide the correct port in absolute URLs if you choose to run on a port other than 443. Search for `urlPortSuffix` to see how it's provided to the template engine (in `./webpack.config.js`) and how it's injected into a page (in `./www/www.html`).
59+
60+
## React
61+
62+
There is a very basic React setup. You can use TypeScript and .tsx files as usual. `./shared/createApp.tsx` exports a `createApp` function that you can use to attach a component to an element that (by convention) must have the id `app`. See e.g. `./www/www.html` and `./www/www.tsx`.
63+
64+
There is no router setup, although something like the React Browser Router should work if you need to install it. 404s will just serve up the default index file for the domain, which is important for React Router to work properly.

localtest/readme.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# UID2 Local Dev Setup
2+
3+
This setup is intended to allow developers to easily run local TLS services with apparently "real" DNS names and locally valid certificates. This allows testing of a variety of scenarios, such as 1st-party related-domain cookies, CSTG, and iframe behaviour.
4+
5+
## Getting started
6+
7+
There are a few steps you need to take for this to work. Please see [setup.md](./setup.md) for details.
8+
9+
## Overview
10+
11+
For details about how to start the server, how the files & folders are laid out, how to use React, and HTML templating, see [overview.md](./overview.md).
12+
13+
## Scenarios
14+
15+
For information about the kinds of scenarios you can test, see [scenarios.md](./scenarios.md).
16+
17+
## Configuration
18+
19+
For information about other things you can configure, see [configuration.md](./configuration.md).

0 commit comments

Comments
 (0)