Skip to content

Commit 18e1609

Browse files
committed
updated docs
1 parent cfc94fd commit 18e1609

26 files changed

+309
-14
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![CircleCI](https://img.shields.io/circleci/project/github/mozilla/send.svg)](https://circleci.com/gh/mozilla/send)
44
[![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/send)
55

6-
**Docs:** [Docker](docs/docker.md), [Metrics](docs/metrics.md)
6+
**Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [Metrics](docs/metrics.md), [More](docs/)
77

88
---
99

@@ -71,6 +71,8 @@ The server is configured with environment variables. See [server/config.js](serv
7171

7272
Firefox Send localization is managed via [Pontoon](https://pontoon.mozilla.org/projects/test-pilot-firefox-send/), not direct pull requests to the repository. If you want to fix a typo, add a new language, or simply know more about localization, please get in touch with the [existing localization team](https://pontoon.mozilla.org/teams/) for your language or Mozilla’s [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.
7373

74+
see also [docs/localization.md](docs/localization.md)
75+
7476
---
7577

7678
## Contributing

app/fileManager.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ export default function(state, emitter) {
104104
metrics.completedUpload(ownedFile);
105105

106106
state.storage.addFile(ownedFile);
107-
108-
document.getElementById('cancel-upload').hidden = 'hidden';
107+
const cancelBtn = document.getElementById('cancel-upload');
108+
if (cancelBtn) {
109+
cancelBtn.hidden = 'hidden';
110+
}
109111
await delay(1000);
110112
await fadeOut('.page');
111113
openLinksInNewTab(links, false);

app/readme.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Application Code
2+
3+
`app/` contains the browser code that gets bundled into `app.[hash].js`. It's got all the logic, crypto, and UI. All of it gets used in the browser, and some of it by the server for server side rendering.
4+
5+
The main entrypoint for the browser is [main.js](./main.js) and on the server [routes/index.js](./routes/index.js) gets imported by [/server/routes/pages.js](../server/routes/pages.js)
6+
7+
- `pages` contains display logic an markup for pages
8+
- `routes` contains route definitions and logic
9+
- `templates` contains ui elements smaller than pages

build/generate_asset_map.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
/*
2+
This code is included by both the server and frontend via
3+
common/assets.js
4+
5+
When included from the server the export will be the function.
6+
7+
When included from the frontend (via webpack) the export will
8+
be an object mapping file names to hashed file names. Example:
9+
"send_logo.svg": "send_logo.5fcfdf0e.svg"
10+
*/
11+
112
const fs = require('fs');
213
const path = require('path');
314

build/generate_l10n_map.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
/*
2+
This code is included by both the server and frontend via
3+
common/locales.js
4+
5+
When included from the server the export will be the function.
6+
7+
When included from the frontend (via webpack) the export will
8+
be an object mapping ftl files to js files. Example:
9+
"public/locales/en-US/send.ftl":"public/locales/en-US/send.6b4f8354.js"
10+
*/
11+
112
const fs = require('fs');
213
const path = require('path');
314

build/readme.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Custom Loaders
2+
3+
## Fluent Loader
4+
5+
The fluent loader "compiles" `.ftl` files into `.js` files directly usable by both the frontend and server for localization.
6+
7+
## Generate Asset Map
8+
9+
This loader enumerates all the files in `assets/` so that `common/assets.js` can provide mappings from the source filename to the hashed filename used on the site.
10+
11+
## Generate L10N Map
12+
13+
This loader enumerates all the ftl files in `public/locales` so that the fluent loader can create it's js files.
14+
15+
## Package.json Loader
16+
17+
This loader creates a `version.json` file that gets exposed by the `/__version__` route from the `package.json` file and current git commit hash.
18+
19+
## Version Loader
20+
21+
This loader substitutes the string "VERSION" for the version string specified in `package.json`. This is a workaround because `package.json` already uses the `package_json_loader`. See [app/templates/header/index.js](../app/templates/header/index.js) for more info.
22+
23+
# See Also
24+
25+
- [docs/build.md](../docs/build.md)
26+
- [webpack.config.js](../webpack.config.js)

common/readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Common Code
2+
3+
This directory contains code loaded by both the frontend `app` and backend `server`. The code here can be challenging to understand at first because the contexts for the two (three counting the dev server) environments that include them are quite different, but the purpose of these modules are quite simple, to provide mappings from the source assets (`copy-16.png`) to the concrete production assets (`copy-16.db66e0bf.svg`), similarly for localizations.

docs/build.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Send has two build configurations, development and production. Both can be run via `npm` scripts, `npm start` for development and `npm run build` for production. Webpack is our only build tool and all configuration lives in [webpack.config.js](../webpack.config.js).
2+
3+
# Development
4+
5+
`npm start` launches a `webpack-dev-server` on port 8080 that compiles the assets and watches files for changes. It also serves the backend API and frontend unit tests via the `server/dev.js` entrypoint. The frontend tests can be run in the browser by navigating to http://localhost:8080/test and will rerun automatically as the watched files are saved with changes.
6+
7+
# Production
8+
9+
`npm run build` compiles the assets and writes the files to the `dist/` directory. `npm run prod` launches an Express server on port 1443 that serves the backend API and frontend static assets from `dist/` via the `server/prod.js` entrypoint.
10+
11+
# Notable differences
12+
13+
- Development compiles assets in memory, so no `dist/` directory is generated
14+
- Development does not enable CSP headers
15+
- Development frontend source is instrumented for code coverage
16+
- Only development includes sourcemaps
17+
- Only development exposes the `/test` route
18+
- Production sets Cache-Control immutable headers on the hashed static assets
19+
20+
# Custom Loaders
21+
22+
The `build/` directory contains custom webpack loaders specific to Send. See [build/readme.md](../build/readme.md) for details on each loader.

docs/encryption.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# File Encryption
2+
3+
Send use 128-bit AES-GCM encryption via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to encrypt files in the browser before uploading them to the server. The code is in [app/keychain.js](../app/keychain.js).
4+
5+
## Steps
6+
7+
### Uploading
8+
9+
1. A new secret key is generated with `crypto.getRandomValues`
10+
2. The secret key is used to derive 3 more keys via HKDF SHA-256
11+
- an encryption key for the file (AES-GCM)
12+
- an encryption key for the file metadata (AES-GCM)
13+
- a signing key for request authentication (HMAC SHA-256)
14+
3. The file and metadata are encrypted with their corresponding keys
15+
4. The encrypted data and signing key are uploaded to the server
16+
5. An owner token and the share url are returned by the server and stored in local storage
17+
6. The secret key is appended to the share url as a [#fragment](https://en.wikipedia.org/wiki/Fragment_identifier) and presented to the UI
18+
19+
### Downloading
20+
21+
1. The browser loads the share url page, which includes an authentication nonce
22+
2. The browser imports the secret key from the url fragment
23+
3. The same 3 keys as above are derived
24+
4. The browser signs the nonce with it's signing key and requests the metadata
25+
5. The encrypted metadata is decrypted and presented on the page
26+
6. The browser makes another authenticated request to download the encrypted file
27+
7. The browser downloads and decrypts the file
28+
8. The file prompts the save dialog or automatically saves depending on the browser settings
29+
30+
### Passwords
31+
32+
A password may optionally be set to authenticate the download request. When a password is set the following steps occur.
33+
34+
#### Sender
35+
36+
1. The original signing key derived from the secret key is discarded
37+
2. A new signing key is generated via PBKDF2 from the user entered password and the full share url (including secret key fragment)
38+
3. The new key is sent to the server, authenticated by the owner token
39+
4. The server stores the new key and marks the record as needing a password
40+
41+
#### Downloader
42+
43+
1. The browser loads the share url page, which includes an authentication nonce and indicator that the file requires a password
44+
2. The user is prompted for the password and the signing key is derived
45+
3. The browser requests the metadata using the key to sign the nonce
46+
4. If the password was correct the metadata is returned, otherwise a 401

docs/experiments.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# A/B experiment testing
2+
3+
We're using Google Analytics Experiments for A/B testing.
4+
5+
## Creating an experiment
6+
7+
Navigate to the Behavior > Experiments section of Google Analytics and click the "Create experiment" button.
8+
9+
The "Objective for this experiment" is the most complicated part. See the "Promo click (Goal ID 4 / Goal Set 1)" for an example.
10+
11+
In step 2 add as many variants as you plan to test. The urls are not important since we aren't using their js library to choose the variants. The name will show up in the report so choose good ones. "Original page" becomes variant 0 and each variant increments by one. We'll use the numbers in our `app/experiments.js` code.
12+
13+
Step 3 contains some script that we'll ignore. The important thing here is the **Experiment ID**. This is the value we need to name our experiment in `app/experiments.js`. Save the changes so far and wait until the code containing the experiment has been deployed to production **before** starting the experiment.
14+
15+
## Experiment code
16+
17+
Code for experiments live in [app/experiments.js](../app/experiments.js). There's an `experiments` object that contains the logic for deciding whether an experiment should run, which variant to use, and what to do. Each object needs to have these functions:
18+
19+
### `eligible` function
20+
21+
This function returns a boolean of whether this experiment should be active for this session. Any data available to the page can be used determine the result.
22+
23+
### `variant` function
24+
25+
This function returns which experimental group this session is placed in. The variant values need to match the values set up in Google Analytics, usually 0 thru N-1. This value is usually picked at random based on what percentage of each variant is desired.
26+
27+
### `run` function
28+
29+
This function gets the `variant` value chosen by the variant function and the `state` and `emitter` objects from the app. This function can do anything needed to change the app based on the experiment. A common pattern is to set or change a value on `state` that will be picked up by other parts of the app, like ui templates, to change how it looks or behaves.
30+
31+
### Example
32+
33+
Here's a full example of the experiment object:
34+
35+
```js
36+
const experiments = {
37+
S9wqVl2SQ4ab2yZtqDI3Dw: { // The Experiment ID from Google Analytics
38+
id: 'S9wqVl2SQ4ab2yZtqDI3Dw',
39+
run: function(variant, state, emitter) {
40+
switch (variant) {
41+
case 1:
42+
state.promo = 'blue';
43+
break;
44+
case 2:
45+
state.promo = 'pink';
46+
break;
47+
default:
48+
state.promo = 'grey';
49+
}
50+
emitter.emit('render');
51+
},
52+
eligible: function() {
53+
return (
54+
!/firefox|fxios/i.test(navigator.userAgent) &&
55+
document.querySelector('html').lang === 'en-US'
56+
);
57+
},
58+
variant: function(state) {
59+
const n = this.luckyNumber(state);
60+
if (n < 0.33) {
61+
return 0;
62+
}
63+
return n < 0.66 ? 1 : 2;
64+
},
65+
luckyNumber: function(state) {
66+
return luckyNumber(
67+
`${this.id}:${state.storage.get('testpilot_ga__cid')}`
68+
);
69+
}
70+
}
71+
};
72+
```
73+
74+
## Reporting results
75+
76+
All metrics pings will include the variant and experiment id, but it's usually important to trigger a specific event to be counted as the experiment goal (the "Objective for this experiment" part from setup). Use an 'experiment' event to do this. For example:
77+
78+
```js
79+
emit('experiment', { cd3: 'promo' });
80+
```
81+
82+
where `emit` is the app emitter function passed to the [route handler](https://github.com/choojs/choo#approuteroutename-handlerstate-emit)
83+
84+
The second argument can be an object with any additional parameters. It usually includes a custom dimension that we chose to filter on while creating the experiment in Google Analytics.

0 commit comments

Comments
 (0)