├── .eslintrc.js ├── .github └── workflows │ ├── deploy.yml │ ├── main.yml │ └── semgrep.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── checkIDs.js ├── createTemplatePages.sh ├── package-lock.json ├── package.json └── tomlToJS.js ├── style ├── javascript.md └── rust.md ├── templates ├── javascript │ ├── ab_testing.js │ ├── aggregate_requests.js │ ├── alter_headers.js │ ├── auth_basic_http.js │ ├── auth_with_headers.js │ ├── block_on_tls_version.js │ ├── bulk_origin_proxies.js │ ├── bulk_redirects.js │ ├── cache_api.js │ ├── cache_ttl.js │ ├── conditional_response.js │ ├── cookie_extract.js │ ├── cors_header_proxy.js │ ├── country_code.js │ ├── debugging_tips.js │ ├── fetch_html.js │ ├── fetch_json.js │ ├── hot_link_protection.js │ ├── http2_server_push.js │ ├── modify_req_props.js │ ├── modify_res_props.js │ ├── post_data.js │ ├── post_json.js │ ├── private_data_loss.js │ ├── redirect.js │ ├── rewrite_links_html.js │ ├── send_raw_html.js │ ├── send_raw_json.js │ ├── signed_request.js │ └── webcrypto.js ├── meta_data │ ├── ab_testing.toml │ ├── aggregate_requests.toml │ ├── alter_headers.toml │ ├── auth_basic_http.toml │ ├── auth_with_headers.toml │ ├── binast_cf_worker.toml │ ├── block_on_tls_version.toml │ ├── bulk_origin_proxies.toml │ ├── bulk_redirects.toml │ ├── cache_api.toml │ ├── cache_ttl.toml │ ├── cloud_storage.toml │ ├── cobol.toml │ ├── conditional_response.toml │ ├── cookie_extract.toml │ ├── cors_header_proxy.toml │ ├── country_code.toml │ ├── dart_hello_world.toml │ ├── debugging_tips.toml │ ├── emscripten.toml │ ├── fetch_html.toml │ ├── fetch_json.toml │ ├── graphql_server.toml │ ├── hello_world.toml │ ├── hello_world_rust.toml │ ├── hot_link_protection.toml │ ├── http2_server_push.toml │ ├── img_color_worker.toml │ ├── kotlin_hello_world.toml │ ├── modify_req_props.toml │ ├── modify_res_props.toml │ ├── post_data.toml │ ├── post_json.toml │ ├── private_data_loss.toml │ ├── python_hello_world.toml │ ├── reason_hello_world.toml │ ├── redirect.toml │ ├── rewrite_links_html.toml │ ├── router.toml │ ├── scala_hello_world.toml │ ├── send_raw_html.toml │ ├── send_raw_json.toml │ ├── sentry.toml │ ├── signed_request.toml │ ├── sites.toml │ ├── speedtest_worker.toml │ ├── typescript.toml │ └── webcrypto.toml └── typescript │ ├── ab_testing.ts │ ├── aggregate_requests.ts │ ├── alter_headers.ts │ ├── auth_basic_http.ts │ ├── auth_with_headers.ts │ ├── block_on_tls_version.ts │ ├── bulk_origin_proxies.ts │ ├── bulk_redirects.ts │ ├── cache_api.ts │ ├── cache_ttl.ts │ ├── conditional_response.ts │ ├── cookie_extract.ts │ ├── cors_header_proxy.ts │ ├── country_code.ts │ ├── debugging_tips.ts │ ├── fetch_html.ts │ ├── fetch_json.ts │ ├── hot_link_protection.ts │ ├── http2_server_push.ts │ ├── modify_req_props.ts │ ├── modify_res_props.ts │ ├── post_data.ts │ ├── post_json.ts │ ├── private_data_loss.ts │ ├── redirect.ts │ ├── rewrite_links_html.ts │ ├── send_raw_html.ts │ ├── send_raw_json.ts │ ├── tsconfig.json │ └── webcrypto.ts ├── workers-site ├── .cargo-ok ├── .gitignore ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json └── wrangler.toml /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | module.exports = { 3 | "env": { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaVersion": 11, 18 | "sourceType": "module" 19 | }, 20 | "plugins": ["@typescript-eslint"] 21 | }; 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy production site 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Navigate to repo 14 | run: cd $GITHUB_WORKSPACE 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '10.x' 18 | - name: Install deps 19 | run: npm install 20 | - name: Build 21 | run: npm run build 22 | - name: Publish 23 | uses: cloudflare/wrangler-action@1.0.0 24 | with: 25 | apiKey: ${{ secrets.CF_API_KEY }} 26 | email: ${{ secrets.CF_EMAIL }} 27 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build site 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - staging 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Navigate to repo 15 | run: cd $GITHUB_WORKSPACE 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: '10.x' 19 | - name: Install deps 20 | run: npm install 21 | - name: Install Wrangler 22 | run: npm i @cloudflare/wrangler@1.5.0 -g 23 | - name: Build TS files 24 | run: npm run build 25 | - name: Build Workers script 26 | run: wrangler build 27 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .eslintcache 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "printWidth": 80, 4 | "singleQuote": true, 5 | "semi": false, 6 | "trailingComma": "all", 7 | "tabWidth": 2 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Cloudflare Workers Templates 2 | 3 | - [Template Metadata](#template-metadata) 4 | - [Required Properties](#required-properties) 5 | - [Optional Properties](#optional-properties) 6 | - [Template Content](#template-content) 7 | - [Snippet Content](#snippet-content) 8 | - [Boilerplate Template Content](#boilerplate-template-content) 9 | - [Updating a Template](#updating-a-template) 10 | - [Creating a Snippet](#creating-a-snippet) 11 | - [Creating a Boilerplate](#creating-a-boilerplate) 12 | - [Testing Your Boilerplate](#testing-your-boilerplate) 13 | - [Submitting a Boilerplate to the Template Registry](#submitting-a-boilerplate-to-the-template-registry) 14 | 15 | This repository supplies the list of templates that appear in the [Template Gallery on the Cloudflare Workers documentation website](https://developers.cloudflare.com/workers/templates/). 16 | 17 | There are two types of templates: 18 | 19 | - *Snippet*: A simple template whose content lives in a single TypeScript file in this repository in the `templates/typescript` directory. A snippet’s code will be displayed inline in the Template Gallery. Snippets are short and intended for users to copy-and-paste into their own projects, or use as a reference. 20 | - *Boilerplate*: A more-complex template in any programming language, whose content lives in a separate repository, may consist of multiple files, and can be used to generate a new project by invoking `wrangler generate path-to-template-repository`. Their content is not displayed directly in the Workers documentation; rather, each boilerplate includes a link to the source repository and a `wrangler generate` command to create a project from the boilerplate. They often have more detailed documentation, either in the template repository’s `README`, or in an article in the Workers docs [like this one](https://github.com/cloudflare/workers-docs/blob/master/workers-docs/src/content/templates/pages/graphql_server.md). 21 | 22 | ## Template Metadata 23 | 24 | Each template is defined by a `toml` file in the [`templates/meta_data`](./templates/meta_data) directory. For example, this is the contents of [`hello_world.toml`](https://github.com/cloudflare/template-registry/blob/f2a21ff87a4f9c60ce1d426e9e8d2e6807b786fd/templates/meta_data/hello_world.toml#L1-L11): 25 | 26 | ```toml 27 | id = "hello_world" 28 | weight = 99 29 | type = "boilerplate" 30 | title = "Hello World" 31 | description = "Simple Hello World in JS" 32 | repository_url = "https://github.com/cloudflare/worker-template" 33 | 34 | [demos.main] 35 | text = "Demo" 36 | url = "https://cloudflareworkers.com/#6626eb50f7b53c2d42b79d1082b9bd37:https://tutorial.cloudflareworkers.com" 37 | tags = [ "Originless" ] 38 | ``` 39 | 40 | ### Required Properties 41 | 42 | ```yaml 43 | id = "some_unique_alphanumeric_slug" 44 | title = "Title of your template" 45 | description = "Concise 1-2 sentences that explains what your template does" 46 | ``` 47 | 48 | Boilerplate templates must also have a `repository_url` property that links to the repository where the template code lives: 49 | 50 | ```yaml 51 | repository_url = "https://github.com//" 52 | ``` 53 | 54 | ### Optional Properties 55 | 56 | ```yaml 57 | share_url="/templates/pages/id " #path to another resource, like a tutorial, that will be displayed alongside the template 58 | tags = ["TypeScript", "Enterprise"] # arbitrary tags that can be used to filter templates in the Template Gallery 59 | 60 | [demos.main] 61 | title = "Demo" 62 | url = "https://cloudflareworkers.com/#6cbbd3ae7d4e928da3502cb9ce11227a:https://tutorial.cloudflareworkers.com/foo" # a live demo of your code 63 | ``` 64 | 65 | ## Template Content 66 | 67 | Templates should follow our [JavaScript/TypeScript style guide](./style/javascript.md). 68 | 69 | ### Snippet Content 70 | 71 | Snippet template content lives in [`templates/typescript`](./templates/typescript). TypeScript snippet definitions are also transpiled to JavaScript, and the JavaScript versions are committed to source control in [`templates/javascript`](./templates/javascript). 72 | 73 | A snippet with an `id` of `example` is expected to have a TypeScript content definition named `example.ts` and a JavaScript content definition named `example.js`. 74 | 75 | Snippets should consist of the following components, in this specific order: 76 | 77 | 1. A `handleRequest` function definition 78 | 2. A call to `addEventListener` 79 | 3. Helper functions 80 | 4. Hardcoded constants which a user will likely change 81 | 82 | Most of the logic should be in a function called `handleRequest`, which should have one of the following type signatures: 83 | 84 | ```typescript 85 | function handleRequest(request: Request): Promise 86 | function handleRequest(request: Request): Response 87 | ``` 88 | 89 | The event listener should be one of the following: 90 | 91 | ```typescript 92 | addEventListener('fetch', event => { 93 | event.respondWith(handleRequest(event.request)) 94 | }) 95 | addEventListener('fetch', event => { 96 | event.respondWith(handleRequest()) 97 | }) 98 | ``` 99 | 100 | Snippets *should not* contain any blank lines. This makes them easier to present in a small amount of space in the Template Gallery. 101 | 102 | ### Boilerplate Template Content 103 | 104 | Boilerplate template content lives in another repository; only metadata about these templates lives in this repository. The metadata allows the Workers Docs to render information about these templates. 105 | 106 | Boilerplate templates can be used to start a new project by running: 107 | 108 | ```bash 109 | wrangler generate https://github.com/path/to/template 110 | ``` 111 | 112 | `wrangler` clones the repo containing the boilerplate template and performs some simple templating logic using [`cargo-generate`](https://github.com/ashleygwilliams/cargo-generate). 113 | 114 | Refer to the [documentation of `cargo-generate`](https://github.com/ashleygwilliams/cargo-generate/blob/master/README.md) for information about template placeholders like `{{ project-name }}` and `{{ authors }}`, as well as the use of a `.genignore` file to determine which files from the template repository should end up in the user’s generated project directory. If there are binary or other files that you need to exclude from placeholder substitution, see the [documentation for cargo-generate.toml](https://github.com/ashleygwilliams/cargo-generate#include--exclude). 115 | 116 | Boilerplate templates should not contain any generated artifacts, like the `worker` directory of a default `wrangler` project. 117 | 118 | Example boilerplate templates include: 119 | 120 | * [cloudflare/worker-template](https://github.com/cloudflare/worker-template) 121 | * [EverlastingBugstopper/worker-typescript-template](https://github.com/EverlastingBugstopper/worker-typescript-template) 122 | * [cloudflare/rustwasm-worker-template](https://github.com/cloudflare/rustwasm-worker-template) 123 | * [cloudflare/worker-emscripten-template](https://github.com/cloudflare/worker-emscripten-template) 124 | * [cloudflare/cobol-worker-template](https://github.com/cloudflare/cobol-worker-template) 125 | * [signalnerve/workers-graphql-server](https://github.com/signalnerve/workers-graphql-server) 126 | * [cloudflare/worker-sites-template](https://github.com/cloudflare/worker-sites-template) 127 | * [cloudflare/worker-template-router](https://github.com/cloudflare/worker-template-router) 128 | 129 | You can write additional documentation about your template that will be displayed in the Workers documentation, like this: 130 | 131 | [Template Gallery | Hello World Rust](https://developers.cloudflare.com/workers/templates/pages/hello_world_rust) 132 | 133 | Such additional documentation lives in the [cloudflare/workers-docs](https://github.com/cloudflare/workers-docs/) repository. 134 | 135 | ## Updating a Template 136 | 137 | If you spot an error in a template, feel free to make a PR against this repository! 138 | 139 | If you want to update the contents of a snippet, edit the TypeScript file (not the transpiled JavaScript version), and then run: 140 | 141 | ```bash 142 | npm run transpile && npm run lint 143 | ``` 144 | 145 | This will transpile your TypeScript changes to the JavaScript version as well. 146 | 147 | ## Creating a Snippet 148 | 149 | 1. Clone this repository. 150 | 2. Run `npm install` from the repo’s root directory to install development dependencies. 151 | 3. Choose an `id` for your template. This should be a unique and descriptive “slug” like the other filenames in [`templates/meta_data`](./templates/meta_data). 152 | 4. Create a new metadata file in [`templates/meta_data`](./templates/meta_data) with the name `your_template_id.toml`. 153 | 5. Add your TypeScript template content in [`templates/typescript`](./templates/typescript) with the name `your_template_id.ts`. 154 | 6. Run `npm run transpile && npm run lint` to generate the JavaScript version of your TypeScript file. 155 | 7. Test your snippet in the [Cloudflare Workers Playground](https://cloudflareworkers.com/) or in a `wrangler` project. 156 | 8. Make a PR to this repository containing three files: 157 | - `templates/meta_data/your_template_id.toml` 158 | - `templates/typescript/your_template_id.ts` 159 | - `templates/javascript/your_template_id.js` 160 | 161 | ## Creating a Boilerplate 162 | 163 | You can get started by cloning the [template creator](https://github.com/victoriabernard92/workers-template-creator) and following the instructions in the README. You can also start from scratch and add template placeholders to any `wrangler` project. 164 | 165 | ### Testing Your Boilerplate 166 | 167 | A boilerplate must be capable of being installed with `wrangler generate`. 168 | 169 | Test using [`wrangler`](https://github.com/cloudflare/wrangler) to generate a new project from your boilerplate template: 170 | 171 | ```bash 172 | wrangler generate your-template-id ./ 173 | cd your-template-slug 174 | wrangler preview --watch 175 | ``` 176 | 177 | Finally, publish your template in a public GitHub repo, and then test your boilerplate template by running: 178 | 179 | ```bash 180 | wrangler generate https://github.com// 181 | cd your-template-id 182 | wrangler preview --watch 183 | ``` 184 | 185 | ### Submitting a Boilerplate to the Template Registry 186 | 187 | Create a metadata file for your template in `templates/meta_data`. Make sure to include a `repo` link to your boilerplate template’s GitHub repository. 188 | 189 | Then make a PR to this repo with your new metadata `toml` file. 190 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Cloudflare 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cloudflare 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Template Registry 2 | 3 | This repo runs a simple API via a Worker that serves all the template content consumed by different services (e.g. our template gallery at developers.cloudflare.com/workers/templates). 4 | 5 | ## API 6 | 7 | The API is a Cloudflare Worker that lives in [workers-site](./workers-site). That uses KV to store the toml/JS data and parses the data for the appropriate endpoints 8 | 9 | ## Data 10 | 11 | All the content that is served from the API lives in [templates](./templates) 12 | 13 | To contribute see [CONTRIBUTING](./CONTRIBUTING.md) 14 | 15 | ## Get Started 16 | 17 | To run for development 18 | 19 | ``` 20 | npm install 21 | ``` 22 | 23 | ``` 24 | npm run start 25 | ``` 26 | 27 | Then in the Workers preview test a link like https://example.com/templates/ or https://example.com/templates/ab_testing 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "worker", 4 | "version": "1.0.0", 5 | "main": "workers-site/dist/index.js", 6 | "author": "Victoria Bernard ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@cloudflare/workers-types": "^2.0.0", 10 | "toml": "^3.0.0", 11 | "typescript": "^3.8.3" 12 | }, 13 | "scripts": { 14 | "build": "cd workers-site && tsc -d", 15 | "lint": "eslint './templates/**/*.ts' --cache --fix && prettier --write ./templates/**/*.{js,ts}", 16 | "start": "npm run build && wrangler preview --watch", 17 | "transpile": "tsc -p ./templates/typescript/tsconfig.json" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^3.3.0", 21 | "@typescript-eslint/parser": "^3.3.0", 22 | "eslint": "^7.3.0", 23 | "husky": "^4.2.5", 24 | "lint-staged": "^10.2.11", 25 | "prettier": "^2.0.5" 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "lint-staged" 30 | } 31 | }, 32 | "lint-staged": { 33 | "./templates/**/*.ts": [ 34 | "eslint --cache --fix", 35 | "prettier --write" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scripts/checkIDs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses the toml files located in templates/meta_data and takes the JS in 3 | * 'code' field and placing it in templates/javascript 4 | */ 5 | const fs = require('fs') 6 | //requiring path and fs modules 7 | const path = require('path') 8 | const toml = require('toml') 9 | var process = require('process') 10 | // process.chdir('../') 11 | 12 | //joining path of directory 13 | const DIRECTORYPATH = path.join(__dirname, '/../templates/meta_data') 14 | const JSDIRECTORYPATH = path.join(__dirname, '/../templates/javascript') 15 | 16 | //read in all the toml files 17 | fs.readdir(DIRECTORYPATH, function(err, files) { 18 | if (err) { 19 | return console.log('Unable to scan directory: ' + err) 20 | } 21 | files.forEach(function(file) { 22 | const filePath = DIRECTORYPATH + '/' + file 23 | const jsFilePath = JSDIRECTORYPATH + '/' + file.replace('.toml', '.js') 24 | const fileID = file.replace('.toml', '') 25 | // console.log('fileID', fileID) 26 | // Do whatever you want to do with the file 27 | fs.readFile(filePath, (err, data) => { 28 | if (err) throw err 29 | jsonData = toml.parse(data.toString()) 30 | const id = jsonData.id 31 | if (id != fileID) { 32 | console.log("ID doesn't match toml fileName", id, fileID) 33 | } 34 | if (jsonData.type === 'snippet') { 35 | //try to read the javascript file 36 | fs.readFile(jsFilePath, (err, javascriptData) => { 37 | if (err) throw err 38 | if (!javascriptData.toString()) { 39 | console.log('JS file doesnt exist for snippet ' + id) 40 | } 41 | // console.log('javascriptData', javascriptData.toString()) 42 | }) 43 | } 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /scripts/createTemplatePages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for f in *; do echo "File is '$f'">> "${f:0:${#f}-4}md"; done 3 | -------------------------------------------------------------------------------- /scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@iarna/toml": { 8 | "version": "2.2.3", 9 | "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.3.tgz", 10 | "integrity": "sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg==" 11 | }, 12 | "toml": { 13 | "version": "3.0.0", 14 | "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", 15 | "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "worker", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "author": "Victoria Bernard ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@iarna/toml": "^2.2.3", 10 | "toml": "^3.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/tomlToJS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses the toml files located in templates/meta_data and takes the JS in 3 | * 'code' field and placing it in templates/javascript 4 | */ 5 | const fs = require('fs') 6 | //requiring path and fs modules 7 | const path = require('path') 8 | const toml = require('toml') 9 | const TOML = require('@iarna/toml') 10 | 11 | //joining path of directory 12 | const DIRECTORYPATH = path.join(__dirname, 'templates/meta_data') 13 | const JSDIRECTORYPATH = path.join(__dirname, 'templates/javascript') 14 | 15 | const createFile = (filePath, content) => { 16 | // write to a new file named 2pac.txt 17 | fs.writeFile(filePath, content, err => { 18 | // throws an error, you could also catch it here 19 | if (err) throw err 20 | console.log('Content saved!') 21 | }) 22 | } 23 | 24 | //read in all the toml files 25 | fs.readdir(DIRECTORYPATH, function(err, files) { 26 | if (err) { 27 | return console.log('Unable to scan directory: ' + err) 28 | } 29 | files.forEach(function(file) { 30 | const filePath = DIRECTORYPATH + '/' + file 31 | const jsFilePath = JSDIRECTORYPATH + '/' + file.replace('.toml', '.js') 32 | // Do whatever you want to do with the file 33 | fs.readFile(filePath, (err, data) => { 34 | if (err) throw err 35 | jsonData = toml.parse(data.toString()) 36 | if (jsonData.code) { 37 | createFile(jsFilePath, jsonData.code) 38 | } 39 | delete jsonData.code 40 | const tomlData = TOML.stringify(jsonData) 41 | createFile(filePath, tomlData) 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /style/javascript.md: -------------------------------------------------------------------------------- 1 | # Javascript Style Guide 2 | 3 | All JavaScript must adhere to [JavaScript Standard Style](https://standardjs.com/) and the [Cloudflare JS StyleGuide](./style/javascript). 4 | 5 | ## Basic Guidelines 6 | 7 | - Always deconstruct when possible `const { url } = request` over `const url = request.url` 8 | 9 | - Prefer the object spread operator (`{...anotherObj}`) to `Object.assign()` 10 | 11 | - Inline `export`s with expressions whenever possible 12 | 13 | ``` 14 | // Use this: 15 | export default class ClassName { 16 | 17 | } 18 | 19 | // Instead of: 20 | class ClassName { 21 | 22 | } 23 | export default ClassName 24 | ``` 25 | 26 | - Place requires in the following order: 27 | 28 | - Built in Node Modules (such as `path`) 29 | - Local Modules (using relative paths) 30 | 31 | - Use arrow functions `=>` over anonymous function expressions 32 | 33 | - Place class properties in the following order: 34 | 35 | - Class methods and properties (methods starting with `static`) 36 | - Instance methods and properties 37 | 38 | - [Avoid platform-dependent code](https://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/) 39 | 40 | * Comment all functions using JSDoc style (e.g. `/***/` ) and if not in Typescript include the param types 41 | 42 | ## Templates 43 | 44 | For Javascript snippets and boilerplates, please make sure your code is ran through prettier with the following config: 45 | 46 | ```javascript .prettierrc 47 | { 48 | "singleQuote": true, 49 | "semi": false, 50 | "tabWidth": 2, 51 | "trailingComma": "all", 52 | "printWidth": 80 53 | } 54 | ``` 55 | 56 | For boilerplates this will be done automatically, but for snippets you can do this using by following a [tutorial on using prettier](https://medium.com/dailyjs/getting-started-with-prettier-writing-clean-concise-code-6838e0912175). 57 | 58 | ## Thanks 59 | 60 | Thank you [Atom](https://atom.io/) for opening up your style guides to help us adopt the best practices:blush:. 61 | -------------------------------------------------------------------------------- /style/rust.md: -------------------------------------------------------------------------------- 1 | # Rust Style Guide 2 | 3 | If you are using Wasm and write in Rust, your template should adhere to rustfmt and clippy. 4 | 5 | ## Configuring rustfmt 6 | 7 | Before submitting code in a PR, make sure that you have formatted the codebase 8 | using [rustfmt][rustfmt]. `rustfmt` is a tool for formatting Rust code, which 9 | helps keep style consistent across the project. If you have not used `rustfmt` 10 | before, here's how to get setup: 11 | 12 | **1. Use Stable Toolchain** 13 | 14 | Use the `rustup override` command to make sure that you are using the stable 15 | toolchain. Run this command in the `cargo-generate` directory you cloned. 16 | 17 | ```sh 18 | rustup override set stable 19 | ``` 20 | 21 | **2. Add the rustfmt component** 22 | 23 | Install the most recent version of `rustfmt` using this command: 24 | 25 | ```sh 26 | rustup component add rustfmt-preview --toolchain stable 27 | ``` 28 | 29 | **3. Running rustfmt** 30 | 31 | To run `rustfmt`, use this command: 32 | 33 | ```sh 34 | cargo +stable fmt 35 | ``` 36 | 37 | [rustfmt]: https://github.com/rust-lang-nursery/rustfmt 38 | -------------------------------------------------------------------------------- /templates/javascript/ab_testing.js: -------------------------------------------------------------------------------- 1 | function handleRequest(request) { 2 | const NAME = 'experiment-0' 3 | // The Responses below are placeholders. You can set up a custom path for each test (e.g. /control/somepath ). 4 | const TEST_RESPONSE = new Response('Test group') // e.g. await fetch('/test/sompath', request) 5 | const CONTROL_RESPONSE = new Response('Control group') // e.g. await fetch('/control/sompath', request) 6 | // Determine which group this requester is in. 7 | const cookie = request.headers.get('cookie') 8 | if (cookie && cookie.includes(`${NAME}=control`)) { 9 | return CONTROL_RESPONSE 10 | } else if (cookie && cookie.includes(`${NAME}=test`)) { 11 | return TEST_RESPONSE 12 | } else { 13 | // If there is no cookie, this is a new client. Choose a group and set the cookie. 14 | const group = Math.random() < 0.5 ? 'test' : 'control' // 50/50 split 15 | const response = group === 'control' ? CONTROL_RESPONSE : TEST_RESPONSE 16 | response.headers.append('Set-Cookie', `${NAME}=${group}; path=/`) 17 | return response 18 | } 19 | } 20 | addEventListener('fetch', event => { 21 | event.respondWith(handleRequest(event.request)) 22 | }) 23 | -------------------------------------------------------------------------------- /templates/javascript/aggregate_requests.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': type, 5 | }, 6 | } 7 | const responses = await Promise.all([fetch(url1, init), fetch(url2, init)]) 8 | const results = await Promise.all([ 9 | gatherResponse(responses[0]), 10 | gatherResponse(responses[1]), 11 | ]) 12 | return new Response(results.join(), init) 13 | } 14 | addEventListener('fetch', event => { 15 | return event.respondWith(handleRequest()) 16 | }) 17 | /** 18 | * gatherResponse awaits and returns a response body as a string. 19 | * Use await gatherResponse(..) in an async function to get the response body 20 | * @param {Response} response 21 | */ 22 | async function gatherResponse(response) { 23 | const { headers } = response 24 | const contentType = headers.get('content-type') || '' 25 | if (contentType.includes('application/json')) { 26 | return JSON.stringify(await response.json()) 27 | } else if (contentType.includes('application/text')) { 28 | return await response.text() 29 | } else if (contentType.includes('text/html')) { 30 | return await response.text() 31 | } else { 32 | return await response.text() 33 | } 34 | } 35 | /** 36 | * Example someHost is set up to return JSON responses 37 | * Replace url1 and url2 with the hosts you wish to 38 | * send requests to 39 | * @param {string} url the URL to send the request to 40 | */ 41 | const someHost = 'https://workers-tooling.cf/demos' 42 | const url1 = someHost + '/requests/json' 43 | const url2 = someHost + '/requests/json' 44 | const type = 'application/json;charset=UTF-8' 45 | -------------------------------------------------------------------------------- /templates/javascript/alter_headers.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | // Make the headers mutable by re-constructing the Request. 3 | request = new Request(request) 4 | request.headers.set('x-my-header', 'custom value') 5 | const URL = 'https://workers-tooling.cf/demos/static/html' 6 | // URL is set up to respond with dummy HTML, remove to send requests to your own origin 7 | let response = await fetch(URL, request) 8 | // Make the headers mutable by re-constructing the Response. 9 | response = new Response(response.body, response) 10 | response.headers.set('x-my-header', 'custom value') 11 | return response 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | -------------------------------------------------------------------------------- /templates/javascript/auth_basic_http.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | if (!request.headers.has('authorization')) { 3 | return getUnauthorizedResponse( 4 | 'Provide User Name and Password to access this page.', 5 | ) 6 | } 7 | const authorization = request.headers.get('authorization') || '' 8 | const credentials = parseCredentials(authorization) 9 | if (credentials[0] !== USERNAME || credentials[1] !== PASSWORD) { 10 | return getUnauthorizedResponse( 11 | 'The User Name and Password combination you have entered is invalid.', 12 | ) 13 | } 14 | return await fetch(request) 15 | } 16 | addEventListener('fetch', event => { 17 | event.respondWith(handleRequest(event.request)) 18 | }) 19 | /** 20 | * Break down base64 encoded authorization string into plain-text username and password 21 | * @param {string} authorization 22 | * @returns {string[]} 23 | */ 24 | function parseCredentials(authorization) { 25 | const parts = authorization.split(' ') 26 | const plainAuth = atob(parts[1]) 27 | const credentials = plainAuth.split(':') 28 | return credentials 29 | } 30 | /** 31 | * Helper function to generate Response object 32 | * @param {string} message 33 | * @returns {Response} 34 | */ 35 | function getUnauthorizedResponse(message) { 36 | const response = new Response(message, { 37 | status: 401, 38 | }) 39 | response.headers.set('WWW-Authenticate', `Basic realm="${REALM}"`) 40 | return response 41 | } 42 | /** 43 | * @param {string} USERNAME User name to access the page 44 | * @param {string} PASSWORD Password to access the page 45 | * @param {string} REALM A name of an area (a page or a group of pages) to protect. 46 | * Some browsers may show "Enter user name and password to access REALM" 47 | */ 48 | const USERNAME = 'demouser' 49 | const PASSWORD = 'demopassword' 50 | const REALM = 'Secure Area' 51 | -------------------------------------------------------------------------------- /templates/javascript/auth_with_headers.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY) 3 | if (psk === PRESHARED_AUTH_HEADER_VALUE) { 4 | // Correct preshared header key supplied. Fetching request 5 | // from origin 6 | return fetch(request) 7 | } 8 | // Incorrect key rejecting request 9 | return new Response('Sorry, you have supplied an invalid key.', { 10 | status: 403, 11 | }) 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | /** 17 | * @param {string} PRESHARED_AUTH_HEADER_KEY custom header to check for key 18 | * @param {string} PRESHARED_AUTH_HEADER_VALUE hard coded key value 19 | */ 20 | const PRESHARED_AUTH_HEADER_KEY = 'X-Custom-PSK' 21 | const PRESHARED_AUTH_HEADER_VALUE = 'mypresharedkey' 22 | -------------------------------------------------------------------------------- /templates/javascript/block_on_tls_version.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | try { 3 | const tlsVersion = request.cf.tlsVersion 4 | // Allow only TLS versions 1.2 and 1.3 5 | if (tlsVersion != 'TLSv1.2' && tlsVersion != 'TLSv1.3') { 6 | return new Response('Please use TLS version 1.2 or higher.', { 7 | status: 403, 8 | }) 9 | } 10 | return fetch(request) 11 | } catch (err) { 12 | console.error( 13 | 'request.cf does not exist in the previewer, only in production', 14 | ) 15 | return new Response('Error in workers script' + err.message, { 16 | status: 500, 17 | }) 18 | } 19 | } 20 | addEventListener('fetch', event => { 21 | event.respondWith(handleRequest(event.request)) 22 | }) 23 | -------------------------------------------------------------------------------- /templates/javascript/bulk_origin_proxies.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const url = new URL(request.url) 3 | // Check if incoming hostname is a key in the ORIGINS object 4 | if (url.hostname in ORIGINS) { 5 | const target = ORIGINS[url.hostname] 6 | url.hostname = target 7 | // If it is, proxy request to that third party origin 8 | return fetch(url.toString(), request) 9 | } 10 | // Otherwise, process request as normal 11 | return fetch(request) 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | /** 17 | * An object with different URLs to fetch 18 | * @param {Object} ORIGINS 19 | */ 20 | const ORIGINS = { 21 | 'starwarsapi.yourdomain.com': 'swapi.co', 22 | 'google.yourdomain.com': 'google.com', 23 | } 24 | -------------------------------------------------------------------------------- /templates/javascript/bulk_redirects.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const requestURL = new URL(request.url) 3 | const path = requestURL.pathname.split('/redirect')[1] 4 | const location = redirectMap.get(path) 5 | if (location) { 6 | return Response.redirect(location, 301) 7 | } 8 | // If in map, return the original request 9 | return fetch(request) 10 | } 11 | addEventListener('fetch', async event => { 12 | event.respondWith(handleRequest(event.request)) 13 | }) 14 | const externalHostname = 'workers-tooling.cf' 15 | const redirectMap = new Map([ 16 | ['/bulk1', 'https://' + externalHostname + '/redirect2'], 17 | ['/bulk2', 'https://' + externalHostname + '/redirect3'], 18 | ['/bulk3', 'https://' + externalHostname + '/redirect4'], 19 | ['/bulk4', 'https://google.com'], 20 | ]) 21 | -------------------------------------------------------------------------------- /templates/javascript/cache_api.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(event) { 2 | const request = event.request 3 | const cacheUrl = new URL(request.url) 4 | // hostname for a different zone 5 | cacheUrl.hostname = someOtherHostname 6 | const cacheKey = new Request(cacheUrl.toString(), request) 7 | const cache = caches.default 8 | // Get this request from this zone's cache 9 | let response = await cache.match(cacheKey) 10 | if (!response) { 11 | //if not in cache, grab it from the origin 12 | response = await fetch(request) 13 | // must use Response constructor to inherit all of response's fields 14 | response = new Response(response.body, response) 15 | // Cache API respects Cache-Control headers, so by setting max-age to 10 16 | // the response will only live in cache for max of 10 seconds 17 | response.headers.append('Cache-Control', 'max-age=10') 18 | // store the fetched response as cacheKey 19 | // use waitUntil so computational expensive tasks don't delay the response 20 | event.waitUntil(cache.put(cacheKey, response.clone())) 21 | } 22 | return response 23 | } 24 | async function handlePostRequest(event) { 25 | const request = event.request 26 | const body = await request.clone().text() 27 | const hash = await sha256(body) 28 | const cacheUrl = new URL(request.url) 29 | // get/store the URL in cache by prepending the body's hash 30 | cacheUrl.pathname = '/posts' + cacheUrl.pathname + hash 31 | // Convert to a GET to be able to cache 32 | const cacheKey = new Request(cacheUrl.toString(), { 33 | headers: request.headers, 34 | method: 'GET', 35 | }) 36 | const cache = caches.default 37 | //try to find the cache key in the cache 38 | let response = await cache.match(cacheKey) 39 | // otherwise, fetch response to POST request from origin 40 | if (!response) { 41 | response = await fetch(request) 42 | event.waitUntil(cache.put(cacheKey, response)) 43 | } 44 | return response 45 | } 46 | addEventListener('fetch', event => { 47 | try { 48 | const request = event.request 49 | if (request.method.toUpperCase() === 'POST') 50 | return event.respondWith(handlePostRequest(event)) 51 | return event.respondWith(handleRequest(event)) 52 | } catch (e) { 53 | return event.respondWith(new Response('Error thrown ' + e.message)) 54 | } 55 | }) 56 | async function sha256(message) { 57 | // encode as UTF-8 58 | const msgBuffer = new TextEncoder().encode(message) 59 | // hash the message 60 | const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) 61 | // convert ArrayBuffer to Array 62 | const hashArray = Array.from(new Uint8Array(hashBuffer)) 63 | // convert bytes to hex string 64 | const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('') 65 | return hashHex 66 | } 67 | const someOtherHostname = 'my.herokuapp.com' 68 | -------------------------------------------------------------------------------- /templates/javascript/cache_ttl.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const url = new URL(request.url) 3 | // Only use the path for the cache key, removing query strings 4 | // and always storing HTTPS e.g. https://www.example.com/file-uri-here 5 | const someCustomKey = `https://${url.hostname}${url.pathname}` 6 | let response = await fetch(request, { 7 | cf: { 8 | // Tell Cloudflare's CDN to always cache this fetch regardless of content type 9 | // for a max of 5 seconds before revalidating the resource 10 | cacheTtl: 5, 11 | cacheEverything: true, 12 | //Enterprise only feature, see Cache API for other plans 13 | cacheKey: someCustomKey, 14 | }, 15 | }) 16 | // Reconstruct the Response object to make its headers mutable. 17 | response = new Response(response.body, response) 18 | //Set cache control headers to cache on browser for 25 minutes 19 | response.headers.set('Cache-Control', 'max-age=1500') 20 | return response 21 | } 22 | addEventListener('fetch', event => { 23 | return event.respondWith(handleRequest(event.request)) 24 | }) 25 | -------------------------------------------------------------------------------- /templates/javascript/conditional_response.js: -------------------------------------------------------------------------------- 1 | const BLOCKED_HOSTNAMES = ['nope.mywebsite.com', 'bye.website.com'] 2 | async function handleRequest(request) { 3 | // Return a new Response based on.. 4 | // On URL's hostname 5 | const url = new URL(request.url) 6 | if (BLOCKED_HOSTNAMES.includes(url.hostname)) { 7 | return new Response('Blocked Host', { status: 403 }) 8 | } 9 | // On URL's file extension (e.g. block paths ending in .doc or .xml) 10 | const forbiddenExtRegExp = new RegExp(/\.(doc|xml)$/) 11 | if (forbiddenExtRegExp.test(url.pathname)) { 12 | return new Response('Blocked Extension', { status: 403 }) 13 | } 14 | // On HTTP method 15 | if (request.method === 'POST') { 16 | return new Response('Response for POST') 17 | } 18 | // On User Agent 19 | const userAgent = request.headers.get('User-Agent') || '' 20 | if (userAgent.includes('bot')) { 21 | return new Response('Block User Agent containing bot', { status: 403 }) 22 | } 23 | // On Client's IP address 24 | const clientIP = request.headers.get('CF-Connecting-IP') 25 | if (clientIP === '1.2.3.4') { 26 | return new Response('Block the IP 1.2.3.4', { status: 403 }) 27 | } 28 | // On ASN 29 | if (request.cf && request.cf.asn == 64512) { 30 | return new Response('Block the ASN 64512 response') 31 | } 32 | // On Device Type 33 | // Requires Enterprise "CF-Device-Type Header" zone setting or 34 | // Page Rule with "Cache By Device Type" setting applied. 35 | const device = request.headers.get('CF-Device-Type') 36 | if (device === 'mobile') { 37 | return Response.redirect('https://mobile.example.com') 38 | } 39 | console.error( 40 | "Getting Client's IP address, device type, and ASN are not supported in playground. Must test on a live worker", 41 | ) 42 | return fetch(request) 43 | } 44 | addEventListener('fetch', event => { 45 | event.respondWith(handleRequest(event.request)) 46 | }) 47 | -------------------------------------------------------------------------------- /templates/javascript/cookie_extract.js: -------------------------------------------------------------------------------- 1 | const COOKIE_NAME = '__uid' 2 | function handleRequest(request) { 3 | const cookie = getCookie(request, COOKIE_NAME) 4 | if (cookie) { 5 | // respond with the cookie value 6 | return new Response(cookie) 7 | } 8 | return new Response('No cookie with name: ' + COOKIE_NAME) 9 | } 10 | addEventListener('fetch', event => { 11 | event.respondWith(handleRequest(event.request)) 12 | }) 13 | /** 14 | * Grabs the cookie with name from the request headers 15 | * @param {Request} request incoming Request 16 | * @param {string} name of the cookie to grab 17 | */ 18 | function getCookie(request, name) { 19 | let result = '' 20 | const cookieString = request.headers.get('Cookie') 21 | if (cookieString) { 22 | const cookies = cookieString.split(';') 23 | cookies.forEach(cookie => { 24 | const cookieName = cookie.split('=')[0].trim() 25 | if (cookieName === name) { 26 | const cookieVal = cookie.split('=')[1] 27 | result = cookieVal 28 | } 29 | }) 30 | } 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /templates/javascript/cors_header_proxy.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const url = new URL(request.url) 3 | let apiUrl = url.searchParams.get('apiurl') 4 | if (apiUrl == null) { 5 | apiUrl = API_URL 6 | } 7 | // Rewrite request to point to API url. This also makes the request mutable 8 | // so we can add the correct Origin header to make the API server think 9 | // that this request isn't cross-site. 10 | request = new Request(apiUrl, request) 11 | request.headers.set('Origin', new URL(apiUrl).origin) 12 | let response = await fetch(request) 13 | // Recreate the response so we can modify the headers 14 | response = new Response(response.body, response) 15 | // Set CORS headers 16 | response.headers.set('Access-Control-Allow-Origin', url.origin) 17 | // Append to/Add Vary header so browser will cache response correctly 18 | response.headers.append('Vary', 'Origin') 19 | return response 20 | } 21 | function handleOptions(request) { 22 | // Make sure the necessary headers are present 23 | // for this to be a valid pre-flight request 24 | if ( 25 | request.headers.get('Origin') !== null && 26 | request.headers.get('Access-Control-Request-Method') !== null && 27 | request.headers.get('Access-Control-Request-Headers') !== null 28 | ) { 29 | // Handle CORS pre-flight request. 30 | // If you want to check the requested method + headers 31 | // you can do that here. 32 | return new Response(null, { 33 | headers: corsHeaders, 34 | }) 35 | } else { 36 | // Handle standard OPTIONS request. 37 | // If you want to allow other HTTP Methods, you can do that here. 38 | return new Response(null, { 39 | headers: { 40 | Allow: 'GET, HEAD, POST, OPTIONS', 41 | }, 42 | }) 43 | } 44 | } 45 | addEventListener('fetch', event => { 46 | const request = event.request 47 | const url = new URL(request.url) 48 | if (url.pathname.startsWith(PROXY_ENDPOINT)) { 49 | if (request.method === 'OPTIONS') { 50 | // Handle CORS preflight requests 51 | event.respondWith(handleOptions(request)) 52 | } else if ( 53 | request.method === 'GET' || 54 | request.method === 'HEAD' || 55 | request.method === 'POST' 56 | ) { 57 | // Handle requests to the API server 58 | event.respondWith(handleRequest(request)) 59 | } else { 60 | event.respondWith( 61 | new Response(null, { 62 | status: 405, 63 | statusText: 'Method Not Allowed', 64 | }), 65 | ) 66 | } 67 | } else { 68 | // Serve demo page 69 | event.respondWith(rawHtmlResponse(DEMO_PAGE)) 70 | } 71 | }) 72 | // We support the GET, POST, HEAD, and OPTIONS methods from any origin, 73 | // and accept the Content-Type header on requests. These headers must be 74 | // present on all responses to all CORS requests. In practice, this means 75 | // all responses to OPTIONS requests. 76 | const corsHeaders = { 77 | 'Access-Control-Allow-Origin': '*', 78 | 'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS', 79 | 'Access-Control-Allow-Headers': 'Content-Type', 80 | } 81 | // The URL for the remote third party API you want to fetch from 82 | // but does not implement CORS 83 | const API_URL = 'https://workers-tooling.cf/demos/demoapi' 84 | // The endpoint you want the CORS reverse proxy to be on 85 | const PROXY_ENDPOINT = '/corsproxy/' 86 | // The rest of this snippet for the demo page 87 | async function rawHtmlResponse(html) { 88 | return new Response(html, { 89 | headers: { 90 | 'content-type': 'text/html;charset=UTF-8', 91 | }, 92 | }) 93 | } 94 | const DEMO_PAGE = ` 95 | 96 | 97 | 98 |

API GET without CORS Proxy

99 | Shows TypeError: Failed to fetch since CORS is misconfigured 100 |

101 | Waiting 102 |

API GET with CORS Proxy

103 |

104 | Waiting 105 |

API POST with CORS Proxy + Preflight

106 |

107 | Waiting 108 | 142 | 143 | ` 144 | -------------------------------------------------------------------------------- /templates/javascript/country_code.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | return redirect(request) 3 | } 4 | addEventListener('fetch', event => { 5 | event.respondWith(handleRequest(event.request)) 6 | }) 7 | /** 8 | * Returns a redirect determined by the country code 9 | * @param {Request} request 10 | */ 11 | async function redirect(request) { 12 | // The `cf-ipcountry` header is not supported in the preview 13 | const country = request.headers.get('cf-ipcountry') 14 | if (country != null && country in countryMap) { 15 | const url = countryMap[country] 16 | return Response.redirect(url) 17 | } else { 18 | return await fetch(request) 19 | } 20 | } 21 | /** 22 | * A map of the URLs to redirect to 23 | * @param {Object} countryMap 24 | */ 25 | const countryMap = { 26 | US: 'https://example.com/us', 27 | EU: 'https://eu.example.com/', 28 | } 29 | -------------------------------------------------------------------------------- /templates/javascript/debugging_tips.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(event) { 2 | let response 3 | try { 4 | response = await fetch(event.request) 5 | if (!response.ok) { 6 | const body = await response.text() 7 | throw new Error( 8 | 'Bad response at origin. Status: ' + 9 | response.status + 10 | ' Body: ' + 11 | //ensures the string is small enough to be a header 12 | body.trim().substring(0, 10), 13 | ) 14 | } 15 | } catch (err) { 16 | // Without event.waitUntil(), our fetch() to our logging service may 17 | // or may not complete. 18 | event.waitUntil(postLog(err.toString())) 19 | const stack = JSON.stringify(err.stack) || err 20 | // Copy the response and initialize body to the stack trace 21 | response = new Response(stack, response) 22 | // Shove our rewritten URL into a header to find out what it was. 23 | response.headers.set('X-Debug-stack', stack) 24 | response.headers.set('X-Debug-err', err) 25 | } 26 | return response 27 | } 28 | addEventListener('fetch', event => { 29 | //Have any uncaught errors thrown go directly to origin 30 | event.passThroughOnException() 31 | event.respondWith(handleRequest(event)) 32 | }) 33 | function postLog(data) { 34 | return fetch(LOG_URL, { 35 | method: 'POST', 36 | body: data, 37 | }) 38 | } 39 | // Service configured to receive logs 40 | const LOG_URL = 'https://log-service.example.com/' 41 | -------------------------------------------------------------------------------- /templates/javascript/fetch_html.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'text/html;charset=UTF-8', 5 | }, 6 | } 7 | const response = await fetch(url, init) 8 | const results = await gatherResponse(response) 9 | return new Response(results, init) 10 | } 11 | addEventListener('fetch', event => { 12 | return event.respondWith(handleRequest()) 13 | }) 14 | /** 15 | * gatherResponse awaits and returns a response body as a string. 16 | * Use await gatherResponse(..) in an async function to get the response body 17 | * @param {Response} response 18 | */ 19 | async function gatherResponse(response) { 20 | const { headers } = response 21 | const contentType = headers.get('content-type') || '' 22 | if (contentType.includes('application/json')) { 23 | return JSON.stringify(await response.json()) 24 | } else if (contentType.includes('application/text')) { 25 | return await response.text() 26 | } else if (contentType.includes('text/html')) { 27 | return await response.text() 28 | } else { 29 | return await response.text() 30 | } 31 | } 32 | /** 33 | * Example someHost at url is set up to respond with HTML 34 | * Replace url with the host you wish to send requests to 35 | */ 36 | const someHost = 'https://workers-tooling.cf/demos' 37 | const url = someHost + '/static/html' 38 | -------------------------------------------------------------------------------- /templates/javascript/fetch_json.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'application/json;charset=UTF-8', 5 | }, 6 | } 7 | const response = await fetch(url, init) 8 | const results = await gatherResponse(response) 9 | return new Response(results, init) 10 | } 11 | addEventListener('fetch', event => { 12 | return event.respondWith(handleRequest()) 13 | }) 14 | /** 15 | * gatherResponse awaits and returns a response body as a string. 16 | * Use await gatherResponse(..) in an async function to get the response body 17 | * @param {Response} response 18 | */ 19 | async function gatherResponse(response) { 20 | const { headers } = response 21 | const contentType = headers.get('content-type') || '' 22 | if (contentType.includes('application/json')) { 23 | return JSON.stringify(await response.json()) 24 | } else if (contentType.includes('application/text')) { 25 | return await response.text() 26 | } else if (contentType.includes('text/html')) { 27 | return await response.text() 28 | } else { 29 | return await response.text() 30 | } 31 | } 32 | /** 33 | * Example someHost is set up to take in a JSON request 34 | * Replace url with the host you wish to send requests to 35 | * @param {string} someHost the host to send the request to 36 | * @param {string} url the URL to send the request to 37 | */ 38 | const someHost = 'https://workers-tooling.cf/demos' 39 | const url = someHost + '/static/json' 40 | -------------------------------------------------------------------------------- /templates/javascript/hot_link_protection.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | // Fetch the original request 3 | const response = await fetch(request) 4 | // If it's an image, engage hotlink protection based on the 5 | // Referer header. 6 | const referer = request.headers.get('Referer') 7 | const contentType = response.headers.get('Content-Type') || '' 8 | if (referer && contentType.startsWith(PROTECTED_TYPE)) { 9 | // If the hostnames don't match, it's a hotlink 10 | if (new URL(referer).hostname !== new URL(request.url).hostname) { 11 | // Redirect the user to your website 12 | return Response.redirect(HOMEPAGE_URL, 302) 13 | } 14 | } 15 | // Everything is fine, return the response normally. 16 | return response 17 | } 18 | addEventListener('fetch', event => { 19 | event.respondWith(handleRequest(event.request)) 20 | }) 21 | const HOMEPAGE_URL = 'https://tutorial.cloudflareworkers.com/' 22 | const PROTECTED_TYPE = 'images/' 23 | -------------------------------------------------------------------------------- /templates/javascript/http2_server_push.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | // If request is for test.css just serve the raw CSS 3 | if (/test.css$/.test(request.url)) { 4 | return new Response(CSS, { 5 | headers: { 6 | 'content-type': 'text/css', 7 | }, 8 | }) 9 | } else { 10 | // serve raw HTML using HTTP/2 for the CSS file 11 | return new Response(HTML, { 12 | headers: { 13 | 'content-type': 'text/html', 14 | Link: '; rel=preload;', 15 | }, 16 | }) 17 | } 18 | } 19 | addEventListener('fetch', event => { 20 | event.respondWith(handleRequest(event.request)) 21 | }) 22 | const CSS = `body { color: red; }` 23 | const HTML = ` 24 | 25 | 26 | 27 | 28 | Server push test 29 | 30 | 31 | 32 |

Server push test page

33 | 34 | 35 | ` 36 | -------------------------------------------------------------------------------- /templates/javascript/modify_req_props.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | /** 3 | * Best practice is to only assign new properties on the request 4 | * object (i.e. RequestInit props) through either a method or the constructor 5 | */ 6 | const newRequestInit = { 7 | // Change method 8 | method: 'POST', 9 | // Change body 10 | body: JSON.stringify({ bar: 'foo' }), 11 | // Change the redirect mode. 12 | redirect: 'follow', 13 | //Change headers, note this method will erase existing headers 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | // Change a Cloudflare feature on the outbound response 18 | cf: { apps: false }, 19 | } 20 | // Change just the host 21 | const url = new URL(someUrl) 22 | url.hostname = someHost 23 | // Best practice is to always use the original request to construct the new request 24 | // thereby cloning all the attributes, applying the URL also requires a constructor 25 | // since once a Request has been constructed, its URL is immutable. 26 | const newRequest = new Request( 27 | url.toString(), 28 | new Request(request, newRequestInit), 29 | ) 30 | // Set headers using method 31 | newRequest.headers.set('X-Example', 'bar') 32 | newRequest.headers.set('Content-Type', 'application/json') 33 | try { 34 | return await fetch(newRequest) 35 | } catch (e) { 36 | return new Response(JSON.stringify({ error: e.message }), { status: 500 }) 37 | } 38 | } 39 | addEventListener('fetch', event => { 40 | event.respondWith(handleRequest(event.request)) 41 | }) 42 | /** 43 | * Example someHost is set up to return raw JSON 44 | * @param {string} someUrl the URL to send the request to, since we are setting hostname too only path is applied 45 | * @param {string} someHost the host the request will resolve too 46 | */ 47 | const someHost = 'example.com' 48 | const someUrl = 'https://foo.example.com/api.js' 49 | -------------------------------------------------------------------------------- /templates/javascript/modify_res_props.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | /** 3 | * Response properties are immutable. To change them, construct a new 4 | * Response, passing modified status or statusText in the ResponseInit 5 | * object. 6 | * Response Headers can be modified through the headers `set` method. 7 | */ 8 | const originalResponse = await fetch(request) 9 | // Change status and statusText, but preserve body and headers 10 | let response = new Response(originalResponse.body, { 11 | status: 500, 12 | statusText: 'some message', 13 | headers: originalResponse.headers, 14 | }) 15 | // Change response body by adding the foo prop 16 | const originalBody = await originalResponse.json() 17 | const body = JSON.stringify({ foo: 'bar', ...originalBody }) 18 | response = new Response(body, response) 19 | // Add a header using set method 20 | response.headers.set('foo', 'bar') 21 | // Set destination header to the value of the source header 22 | const src = response.headers.get(headerNameSrc) 23 | if (src != null) { 24 | response.headers.set(headerNameDst, src) 25 | console.log( 26 | `Response header "${headerNameDst}" was set to "${response.headers.get( 27 | headerNameDst, 28 | )}"`, 29 | ) 30 | } 31 | return response 32 | } 33 | addEventListener('fetch', event => { 34 | event.respondWith(handleRequest(event.request)) 35 | }) 36 | /** 37 | * @param {string} headerNameSrc the header to get the new value from 38 | * @param {string} headerNameDst the header to set based off of value in src 39 | */ 40 | const headerNameSrc = 'foo' //'Orig-Header' 41 | const headerNameDst = 'Last-Modified' 42 | -------------------------------------------------------------------------------- /templates/javascript/post_data.js: -------------------------------------------------------------------------------- 1 | async function handleRequest(request) { 2 | const reqBody = await readRequestBody(request) 3 | const retBody = `The request body sent in was ${reqBody}` 4 | return new Response(retBody) 5 | } 6 | addEventListener('fetch', event => { 7 | const { request } = event 8 | const { url } = request 9 | if (url.includes('form')) { 10 | return event.respondWith(rawHtmlResponse(someForm)) 11 | } 12 | if (request.method === 'POST') { 13 | return event.respondWith(handleRequest(request)) 14 | } else if (request.method === 'GET') { 15 | return event.respondWith(new Response(`The request was a GET`)) 16 | } 17 | }) 18 | /** 19 | * rawHtmlResponse delivers a response with HTML inputted directly 20 | * into the worker script 21 | * @param {string} html 22 | */ 23 | function rawHtmlResponse(html) { 24 | const init = { 25 | headers: { 26 | 'content-type': 'text/html;charset=UTF-8', 27 | }, 28 | } 29 | return new Response(html, init) 30 | } 31 | /** 32 | * readRequestBody reads in the incoming request body 33 | * Use await readRequestBody(..) in an async function to get the string 34 | * @param {Request} request the incoming request to read from 35 | */ 36 | async function readRequestBody(request) { 37 | const { headers } = request 38 | const contentType = headers.get('content-type') || '' 39 | if (contentType.includes('application/json')) { 40 | return JSON.stringify(await request.json()) 41 | } else if (contentType.includes('application/text')) { 42 | return await request.text() 43 | } else if (contentType.includes('text/html')) { 44 | return await request.text() 45 | } else if (contentType.includes('form')) { 46 | const formData = await request.formData() 47 | const body = {} 48 | for (const entry of formData.entries()) { 49 | body[entry[0]] = entry[1] 50 | } 51 | return JSON.stringify(body) 52 | } else { 53 | const myBlob = await request.blob() 54 | const objectURL = URL.createObjectURL(myBlob) 55 | return objectURL 56 | } 57 | } 58 | const someForm = ` 59 | 60 | 61 | 62 |

Hello World

63 |

This is all generated using a Worker

64 |
65 |
66 | 67 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 | 79 | ` 80 | -------------------------------------------------------------------------------- /templates/javascript/post_json.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | body: JSON.stringify(body), 4 | method: 'POST', 5 | headers: { 6 | 'content-type': 'application/json;charset=UTF-8', 7 | }, 8 | } 9 | const response = await fetch(url, init) 10 | const results = await gatherResponse(response) 11 | return new Response(results, init) 12 | } 13 | addEventListener('fetch', event => { 14 | return event.respondWith(handleRequest()) 15 | }) 16 | /** 17 | * gatherResponse awaits and returns a response body as a string. 18 | * Use await gatherResponse(..) in an async function to get the response body 19 | * @param {Response} response 20 | */ 21 | async function gatherResponse(response) { 22 | const { headers } = response 23 | const contentType = headers.get('content-type') || '' 24 | if (contentType.includes('application/json')) { 25 | return JSON.stringify(await response.json()) 26 | } else if (contentType.includes('application/text')) { 27 | return await response.text() 28 | } else if (contentType.includes('text/html')) { 29 | return await response.text() 30 | } else { 31 | return await response.text() 32 | } 33 | } 34 | /** 35 | * Example someHost is set up to take in a JSON request 36 | * Replace url with the host you wish to send requests to 37 | * @param {string} url the URL to send the request to 38 | * @param {BodyInit} body the JSON data to send in the request 39 | */ 40 | const someHost = 'https://workers-tooling.cf/demos' 41 | const url = someHost + '/requests/json' 42 | const body = { 43 | results: ['default data to send'], 44 | errors: null, 45 | msg: 'I sent this to the fetch', 46 | } 47 | -------------------------------------------------------------------------------- /templates/javascript/private_data_loss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Define personal data with regular expressions 3 | * Respond with block if credit card data, and strip 4 | * emails and phone numbers from the response 5 | * Execution will be limited to MIME type "text/*" 6 | */ 7 | async function handleRequest(request) { 8 | const response = await fetch(request) 9 | // Return origin response, if response wasn't text 10 | const contentType = response.headers.get('content-type') || '' 11 | if (!contentType.toLowerCase().includes('text/')) { 12 | return response 13 | } 14 | let text = await response.text() 15 | text = DEBUG 16 | ? // for testing only - replace the response from the origin with an email 17 | text.replace('You may use this', 'me@example.com may use this') 18 | : text 19 | const sensitiveRegexsMap = { 20 | email: String.raw`\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b`, 21 | phone: String.raw`\b07\d{9}\b`, 22 | creditCard: String.raw`\b(?:4[0-9]{12}(?:[0-9]{3})?|(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b`, 23 | } 24 | for (const kind in sensitiveRegexsMap) { 25 | const sensitiveRegex = new RegExp(sensitiveRegexsMap[kind], 'ig') 26 | const match = await sensitiveRegex.test(text) 27 | if (match) { 28 | // alert a data breach by posting to a webhook server 29 | await postDataBreach(request) 30 | // respond with a block if credit card, else replace 31 | // sensitive text with *'s 32 | return kind === 'creditCard' 33 | ? new Response(kind + ' found\nForbidden\n', { 34 | status: 403, 35 | statusText: 'Forbidden', 36 | }) 37 | : new Response(text.replace(sensitiveRegex, '**********'), response) 38 | } 39 | } 40 | return new Response(text, response) 41 | } 42 | addEventListener('fetch', event => { 43 | event.respondWith(handleRequest(event.request)) 44 | }) 45 | async function postDataBreach(request) { 46 | const trueClientIp = request.headers.get('cf-connecting-ip') 47 | const epoch = new Date().getTime() 48 | const body = { 49 | ip: trueClientIp, 50 | time: epoch, 51 | request: request, 52 | } 53 | const init = { 54 | body: JSON.stringify(body), 55 | method: 'POST', 56 | headers: { 57 | 'content-type': 'application/json;charset=UTF-8', 58 | }, 59 | } 60 | return await fetch(SOME_HOOK_SERVER, init) 61 | } 62 | const SOME_HOOK_SERVER = 'https://webhook.flow-wolf.io/hook' 63 | const DEBUG = true 64 | -------------------------------------------------------------------------------- /templates/javascript/redirect.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | return Response.redirect(someURLToRedirectTo, code) 3 | } 4 | addEventListener('fetch', async event => { 5 | event.respondWith(handleRequest()) 6 | }) 7 | /** 8 | * @param {Request} url where to redirect the response 9 | * @param {number?=301|302} type permanent or temporary redirect 10 | */ 11 | const someURLToRedirectTo = 'https://www.google.com' 12 | const code = 301 13 | -------------------------------------------------------------------------------- /templates/javascript/rewrite_links_html.js: -------------------------------------------------------------------------------- 1 | const OLD_URL = 'developer.mozilla.org' 2 | const NEW_URL = 'mynewdomain.com' 3 | async function handleRequest(req) { 4 | const res = await fetch(req) 5 | return rewriter.transform(res) 6 | } 7 | class AttributeRewriter { 8 | constructor(attributeName) { 9 | this.attributeName = attributeName 10 | } 11 | element(element) { 12 | const attribute = element.getAttribute(this.attributeName) 13 | if (attribute) { 14 | element.setAttribute( 15 | this.attributeName, 16 | attribute.replace(OLD_URL, NEW_URL), 17 | ) 18 | } 19 | } 20 | } 21 | const rewriter = new HTMLRewriter() 22 | .on('a', new AttributeRewriter('href')) 23 | .on('img', new AttributeRewriter('src')) 24 | addEventListener('fetch', event => { 25 | event.respondWith(handleRequest(event.request)) 26 | }) 27 | -------------------------------------------------------------------------------- /templates/javascript/send_raw_html.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'text/html;charset=UTF-8', 5 | }, 6 | } 7 | return new Response(someHTML, init) 8 | } 9 | addEventListener('fetch', event => { 10 | return event.respondWith(handleRequest()) 11 | }) 12 | const someHTML = ` 13 | 14 | 15 |

Hello World

16 |

This is all generated using a Worker

17 | 25 | 26 | 27 | ` 28 | -------------------------------------------------------------------------------- /templates/javascript/send_raw_json.js: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'application/json;charset=UTF-8', 5 | }, 6 | } 7 | return new Response(JSON.stringify(someJSON), init) 8 | } 9 | addEventListener('fetch', event => { 10 | return event.respondWith(handleRequest()) 11 | }) 12 | const someJSON = { 13 | result: ['some', 'results'], 14 | errors: null, 15 | msg: 'this is some random json', 16 | } 17 | -------------------------------------------------------------------------------- /templates/javascript/signed_request.js: -------------------------------------------------------------------------------- 1 | // NOTE Requires ESM through webpack project type 2 | const crypto = require('crypto') 3 | const SECRET = 'SECRET_KEY' 4 | async function handleRequest(request) { 5 | let signed = await checkSignature(request) 6 | if (signed) { 7 | let responseBody = 'Hello worker!' 8 | return await signResponse(responseBody, new Response(responseBody)) 9 | } else { 10 | return new Response('Request not signed', { status: 400 }) 11 | } 12 | } 13 | addEventListener('fetch', event => { 14 | console.log(createHexSignature('asd')) 15 | event.respondWith(handleRequest(event.request)) 16 | }) 17 | async function createHexSignature(requestBody) { 18 | let hmac = crypto.createHmac('sha256', SECRET) 19 | hmac.update(requestBody) 20 | return hmac.digest('hex') 21 | } 22 | async function checkSignature(request) { 23 | // hash request with secret key 24 | let expectedSignature = await createHexSignature(await request.text()) 25 | let actualSignature = await request.headers.get('signature') 26 | // check that hash matches signature 27 | return expectedSignature === actualSignature 28 | } 29 | async function signResponse(responseBody, response) { 30 | // create signature 31 | const signature = await createHexSignature(responseBody) 32 | response.headers.set('signature', signature) 33 | //add header with signature 34 | return response 35 | } 36 | -------------------------------------------------------------------------------- /templates/javascript/webcrypto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | async function handleRequest() { 3 | let msg = "alice and bob"; 4 | let hmacresult = await generateSignandVerify({ 5 | name: "HMAC", 6 | hash: "sha-256" 7 | }); 8 | console.log("Result of HMAC generate, sign, verify: ", hmacresult); 9 | let aesresult = await generateEncryptDecrypt({ 10 | name: "AES-GCM", 11 | length: 256 12 | }, msg); 13 | var dec = new TextDecoder(); 14 | if (msg == dec.decode(aesresult)) { 15 | console.log("AES encrypt decrypt successful"); 16 | } 17 | else { 18 | console.log("AES encrypt decrypt failed"); 19 | } 20 | return new Response(); 21 | } 22 | addEventListener('fetch', event => { 23 | event.respondWith(handleRequest()); 24 | }); 25 | async function generateSignandVerify(algorithm) { 26 | let rawMessage = "alice and bob"; 27 | let key = await self.crypto.subtle.generateKey(algorithm, true, ["sign", "verify"]); 28 | let enc = new TextEncoder(); 29 | let encoded = enc.encode(rawMessage); 30 | let signature = await self.crypto.subtle.sign(algorithm, key, encoded); 31 | let result = await self.crypto.subtle.verify(algorithm, key, signature, encoded); 32 | return result; 33 | } 34 | async function generateEncryptDecrypt(algorithm, msg) { 35 | let key = await self.crypto.subtle.generateKey(algorithm, true, ["encrypt", "decrypt"]); 36 | let enc = new TextEncoder(); 37 | let encoded = enc.encode(msg); 38 | algorithm.iv = crypto.getRandomValues(new Uint8Array(16)); 39 | let signature = await self.crypto.subtle.encrypt(algorithm, key, encoded); 40 | let result = await self.crypto.subtle.decrypt(algorithm, key, signature); 41 | return result; 42 | } 43 | -------------------------------------------------------------------------------- /templates/meta_data/ab_testing.toml: -------------------------------------------------------------------------------- 1 | id = "ab_testing" 2 | weight = 1 3 | type = "snippet" 4 | title = "A/B Testing" 5 | description = "Set up an A/B test by controlling what response is served based on cookies" 6 | share_url = "/templates/snippets/ab_testing" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#12a2962014e1324e3a416a5a76e1f81f:https://tutorial.cloudflareworkers.com" 11 | -------------------------------------------------------------------------------- /templates/meta_data/aggregate_requests.toml: -------------------------------------------------------------------------------- 1 | id = "aggregate_requests" 2 | weight = 20 3 | type = "snippet" 4 | title = "Aggregate Requests" 5 | description = "Sends two GET request to two urls and aggregates the responses into one response." 6 | share_url = "/templates/snippets/aggregate_requests" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#eaaa52283784c21aec989c64b9db32d3:https://example.com" 11 | tags = [ "Middleware" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/alter_headers.toml: -------------------------------------------------------------------------------- 1 | id = "alter_headers" 2 | weight = 1 3 | type = "snippet" 4 | title = "Alter Headers" 5 | description = "Change the headers sent in a request or returned in a response" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#5386d315ec8c899370e8e5d00cf88939:https://tutorial.cloudflareworkers.com" 10 | share_url = "/templates/snippets/alter_headers" 11 | -------------------------------------------------------------------------------- /templates/meta_data/auth_basic_http.toml: -------------------------------------------------------------------------------- 1 | id = "auth_basic_http" 2 | weight = 1 3 | type = "snippet" 4 | title = "Basic HTTP authentication" 5 | description = "Password-protect whole websites or specific areas/pages. Browsers will prompt for a password and username - hardcoded as `demouser`/`demopassword` - before serving a protected page. This uses basic authentication through the [`WWW-Authorization` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). 6 | repository_url = "" 7 | 8 | [demos.main] 9 | text = "Demo (credentials: demouser/demopassword)" 10 | url = "https://cloudflareworkers.com/#f5d2cc53bd3d55486ddd14b1eb6e6c83:https://www.cloudflarestatus.com/" 11 | tags = [ "Middleware" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/auth_with_headers.toml: -------------------------------------------------------------------------------- 1 | id = "auth_with_headers" 2 | weight = 1 3 | type = "snippet" 4 | title = "Auth with headers" 5 | description = "Allow or deny a request based on a known pre-shared key in a header. Note while this simple implementation is helpful, it is not meant to replace more secure scripts such as signed requests using the [WebCrypto API](/reference/runtime/apis/web-crypto)." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#7373b8c3c9c46c03c81562f00f663f81:https://tutorial.cloudflareworkers.com" 10 | tags = [ "Middleware" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/binast_cf_worker.toml: -------------------------------------------------------------------------------- 1 | id = "binast_cf_worker" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Binast-Cf-Worker" 5 | description = "Serve BinAST via a Cloudflare Worker" 6 | repository_url = "https://github.com/xtuc/binast-cf-worker-template" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://serve-binjs.that-test.site/" 11 | tags = [ "Middleware" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/block_on_tls_version.toml: -------------------------------------------------------------------------------- 1 | id = "block_on_tls_version" 2 | weight = 1 3 | type = "snippet" 4 | title = "Block on TLS Version" 5 | description = "Inspects the incoming request's TLS version and blocks if under TLSv1.2." 6 | tags = [ "Middleware" ] 7 | -------------------------------------------------------------------------------- /templates/meta_data/bulk_origin_proxies.toml: -------------------------------------------------------------------------------- 1 | id = "bulk_origin_proxies" 2 | weight = 1 3 | type = "snippet" 4 | title = "Bulk Origin Proxies" 5 | description = "Resolve requests to your domain to a set of proxy third-party origins" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#545c79e098708ca658dcbdb860c5021a:https://starwarsapi.yourdomain.com/" 10 | share_url = "/templates/snippets/bulk_origin_proxies" 11 | -------------------------------------------------------------------------------- /templates/meta_data/bulk_redirects.toml: -------------------------------------------------------------------------------- 1 | id = "bulk_redirects" 2 | weight = 1 3 | type = "snippet" 4 | title = "Bulk Redirects" 5 | description = "Redirects requests to certain URLs based a mapped object to the request's URL." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#d17c3da192fd5c83ef7d28153ab32f3f:https://example.com/redirect/bulk1" 10 | -------------------------------------------------------------------------------- /templates/meta_data/cache_api.toml: -------------------------------------------------------------------------------- 1 | id = "cache_api" 2 | weight = 1 3 | type = "snippet" 4 | title = "Cache API" 5 | description = "Cache using Cloudflare's [Cache API](/workers/reference/apis/cache/). This example can cache POST requests as well as change what hostname to store a response in cache. Note the previewer is not available for using Cache API." 6 | tags = [ "Middleware" ] 7 | -------------------------------------------------------------------------------- /templates/meta_data/cache_ttl.toml: -------------------------------------------------------------------------------- 1 | id = "cache_ttl" 2 | weight = 1 3 | type = "snippet" 4 | title = "Cache using fetch" 5 | description = "Determine how/if to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch [`request`](/workers/reference/apis/request/) to gain granular control of browsers' and Cloudflare's caches for even your dynamic assets like caching HTML. " 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#8dd3e29985701b05e43b4dd74c387b74:https://example.com/" 10 | tags = [ "Middleware", "Enterprise" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/cloud_storage.toml: -------------------------------------------------------------------------------- 1 | id = "cloud_storage" 2 | weight = 50 3 | type = "featured_boilerplate" 4 | title = "Cloud Storage" 5 | description = "Serve private AWS bucket files from a Worker script" 6 | repository_url = "https://github.com/conzorkingkong/cloud-storage" 7 | tags = [ "Middleware" ] 8 | share_url = "/templates/featured_boilerplates/cloud_storage" 9 | -------------------------------------------------------------------------------- /templates/meta_data/cobol.toml: -------------------------------------------------------------------------------- 1 | id = "cobol" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "COBOL" 5 | description = "Return a Hello World Response in COBOL" 6 | repository_url = "https://github.com/cloudflare/cobol-worker-template" 7 | tags = [ "Wasm" ] 8 | -------------------------------------------------------------------------------- /templates/meta_data/conditional_response.toml: -------------------------------------------------------------------------------- 1 | id = "conditional_response" 2 | weight = 60 3 | type = "snippet" 4 | title = "Conditional Response" 5 | description = "Return a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type (e.g. mobile)" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#cec6695f67232bc76e3f396fcb2d5cc7:https://nope.mywebsite.com/" 10 | tags = [ "Enterprise" ] 11 | share_url = "/templates/snippets/conditional_response" 12 | -------------------------------------------------------------------------------- /templates/meta_data/cookie_extract.toml: -------------------------------------------------------------------------------- 1 | id = "cookie_extract" 2 | weight = 1 3 | type = "snippet" 4 | title = "Extract Cookie Value" 5 | description = "Extracts the value of a cookie, given the cookie name." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#605946f0f09b396361d3dadb5abf29c8:https://tutorial.cloudflareworkers.com/" 10 | share_url = "/templates/snippets/cookie_extract" 11 | -------------------------------------------------------------------------------- /templates/meta_data/cors_header_proxy.toml: -------------------------------------------------------------------------------- 1 | id = "cors_header_proxy" 2 | weight = 1 3 | type = "snippet" 4 | title = "CORS Header Proxy" 5 | description = "Add necessary CORS headers to a third party API response" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#00ad8fc3ff55f6b740ed09470359dc0d:https://example.com/" 10 | -------------------------------------------------------------------------------- /templates/meta_data/country_code.toml: -------------------------------------------------------------------------------- 1 | id = "country_code" 2 | weight = 20 3 | type = "snippet" 4 | title = "Country Code" 5 | description = "Redirect a response based on the country code of the visitor" 6 | tags = [ "Originless" ] 7 | -------------------------------------------------------------------------------- /templates/meta_data/dart_hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "dart_hello_world" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "Dart Hello World" 5 | description = "Return a Hello World Response in Dart" 6 | repository_url = "https://github.com/cloudflare/dart-worker-hello-world" 7 | -------------------------------------------------------------------------------- /templates/meta_data/debugging_tips.toml: -------------------------------------------------------------------------------- 1 | id = "debugging_tips" 2 | weight = 50 3 | type = "snippet" 4 | title = "Debugging Tips" 5 | description = "Send debug information in an errored response and to a logging service." 6 | tags = [ "Middleware" ] 7 | share_url = "/about/tips/debugging" 8 | 9 | [demos.main] 10 | text = "Demo" 11 | url = "https://cloudflareworkers.com/#5601380cdcb173e5b712bd82113ce47a:https://blah.workers-tooling.cf/fetch/error" 12 | 13 | -------------------------------------------------------------------------------- /templates/meta_data/emscripten.toml: -------------------------------------------------------------------------------- 1 | id = "emscripten" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Emscripten + Wasm Image Resizer" 5 | description = "Image Resizer in C compiled to Wasm with Emscripten" 6 | repository_url = "https://github.com/cloudflare/worker-emscripten-template" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#ddb7fa39e09cdf734180c5d083ddb390:http://placehold.jp/1200x800.png" 11 | tags = [ "Wasm" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/fetch_html.toml: -------------------------------------------------------------------------------- 1 | id = "fetch_html" 2 | weight = 20 3 | type = "snippet" 4 | title = "Fetch HTML" 5 | description = "Sends a request to a remote server, reads HTML from the response, then serves that HTML." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#14a82a672534b0dd25b385a5d3de744c:https://example.com" 10 | tags = [ "Middleware" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/fetch_json.toml: -------------------------------------------------------------------------------- 1 | id = "fetch_json" 2 | weight = 30 3 | type = "snippet" 4 | title = "Fetch JSON" 5 | description = "Sends a GET request and reads in JSON from the response." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#a45261f6682b048d9ed0e23a330d9cdb:https://example.com" 10 | tags = [ "Middleware" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/graphql_server.toml: -------------------------------------------------------------------------------- 1 | id = "graphql_server" 2 | type = "featured_boilerplate" 3 | title = "Apollo GraphQL Server" 4 | weight = 2 5 | description = "🔥Lightning-fast, globally distributed Apollo GraphQL server, deployed at the edge using Cloudflare Workers." 6 | repository_url = "https://github.com/signalnerve/workers-graphql-server" 7 | share_url = "/templates/featured_boilerplates/graphql" 8 | 9 | [demos.main] 10 | text = "Demo" 11 | url = "https://workers-graphql.signalnerve.workers.dev/___graphql" 12 | 13 | -------------------------------------------------------------------------------- /templates/meta_data/hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "hello_world" 2 | weight = 99 3 | type = "boilerplate" 4 | title = "Hello World" 5 | description = "Simple Hello World in JS" 6 | repository_url = "https://github.com/cloudflare/worker-template" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#6626eb50f7b53c2d42b79d1082b9bd37:https://tutorial.cloudflareworkers.com" 11 | tags = [ "Originless" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/hello_world_rust.toml: -------------------------------------------------------------------------------- 1 | id = "hello_world_rust" 2 | type = "boilerplate" 3 | title = "Hello World Rust" 4 | description = "Simple Hello World in Rust" 5 | repository_url = "https://github.com/cloudflare/rustwasm-worker-template" 6 | weight = 98 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#1992963c14c25bc8dc4c50f4cab740e5:https://tutorial.cloudflareworkers.com" 11 | tags = [ "Originless", "Wasm" ] 12 | 13 | share_url = "/templates/boilerplates/rustwasm" 14 | -------------------------------------------------------------------------------- /templates/meta_data/hot_link_protection.toml: -------------------------------------------------------------------------------- 1 | id = "hot_link_protection" 2 | weight = 1 3 | type = "snippet" 4 | title = "Hot-link Protection" 5 | description = "Block other websites from linking to your content" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#d659b20dcf91df6e6de1bedbb94f6702:https://www.cloudflare.com/img/logo-cloudflare-dark.svg" 10 | share_url = "/templates/snippets/hotlink-protection" 11 | -------------------------------------------------------------------------------- /templates/meta_data/http2_server_push.toml: -------------------------------------------------------------------------------- 1 | id = "http2_server_push" 2 | weight = 1 3 | type = "snippet" 4 | title = "HTTP/2 Server Push" 5 | description = "Push static assests to a client's browser without waiting for HTML to render" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#f7b7e99bec493ba4f4847a095c4fa61c:https://tutorial.cloudflareworkers.com/" 10 | -------------------------------------------------------------------------------- /templates/meta_data/img_color_worker.toml: -------------------------------------------------------------------------------- 1 | id = "img_color_worker" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Img-Color-Worker" 5 | description = "Retrieve the dominant color of a PNG or JPEG image" 6 | repository_url = "https://github.com/xtuc/img-color-worker" 7 | tags = [ "Middleware" ] 8 | -------------------------------------------------------------------------------- /templates/meta_data/kotlin_hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "kotlin_hello_world" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "Kotlin Hello World" 5 | description = "Return a Hello World Response in Kotlin" 6 | repository_url = "https://github.com/cloudflare/kotlin-worker-hello-world" 7 | -------------------------------------------------------------------------------- /templates/meta_data/modify_req_props.toml: -------------------------------------------------------------------------------- 1 | id = "modify_req_props" 2 | weight = 1 3 | type = "snippet" 4 | title = "Modify Request Property" 5 | description = "Recommended practice for forming a [request](/workers/reference/apis/request) based off the incoming request. First, takes in the incoming request then modifies specific properties like POST `body`, `redirect`, and the Cloudflare specific property `cf` and runs the fetch." 6 | share_url = "/templates/snippets/modify_req_props" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#27b91145f66b6cfce1b3190ed7ba0bc5:https://example.com" 11 | tags = [ "Middleware" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/modify_res_props.toml: -------------------------------------------------------------------------------- 1 | id = "modify_res_props" 2 | weight = 1 3 | type = "snippet" 4 | title = "Modify Response" 5 | description = "Recommended practice for mutating a fetched [response](/workers/reference/apis/response). First, fetches a request then modifies specific properties which are immutable: `status`, `statusText`, `headers` and `body`." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#daf0e96ae0ff4ce3f103c14bf6d968df:http://workers-tooling.cf/demos/static/json" 10 | tags = [ "Middleware" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/post_data.toml: -------------------------------------------------------------------------------- 1 | id = "post_data" 2 | weight = 1 3 | type = "snippet" 4 | title = "Read POST Data" 5 | description = "Serves an HTML form, then reads POSTs from that form data. Can also be used to read JSON or other POST data from an incoming request." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#a6a285e4e2bfbebea0be31b9eeaca3e6:https://example.com/form" 10 | tags = [ "Originless" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/post_json.toml: -------------------------------------------------------------------------------- 1 | id = "post_json" 2 | weight = 1 3 | type = "snippet" 4 | title = "Post JSON" 5 | description = "Sends a POST request with JSON data from the Workers script." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#edce60b7d57c1e98fbe2d931aaaaf25f:https://tutorial.cloudflareworkers.com" 10 | tags = [ "Middleware" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/private_data_loss.toml: -------------------------------------------------------------------------------- 1 | id = "private_data_loss" 2 | weight = 1 3 | type = "snippet" 4 | title = "Data Loss Prevention" 5 | description = "Prevent access to personal and sensitive data by inspecting response data from an origin server. In this example, sensitive data is defined by regexes for email, UK mobile number, or credit card number. If a match is detected, trigger a data breach alert and respond with either a block or the data stripped from the response." 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#1b7ac657cfaec2635a319441800a5158:https://example.com/" 10 | tags = [ "Middleware" ] 11 | share_url = "/templates/snippets/private_data_loss" 12 | -------------------------------------------------------------------------------- /templates/meta_data/python_hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "python_hello_world" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "Python Hello World" 5 | description = "Return a Hello World Response in Python" 6 | repository_url = "https://github.com/cloudflare/python-worker-hello-world" 7 | -------------------------------------------------------------------------------- /templates/meta_data/reason_hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "reason_hello_world" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "Reason Hello World" 5 | description = "Return a Hello World Response in Reason" 6 | repository_url = "https://github.com/cloudflare/reason-worker-hello-world" 7 | -------------------------------------------------------------------------------- /templates/meta_data/redirect.toml: -------------------------------------------------------------------------------- 1 | id = "redirect" 2 | weight = 1 3 | type = "snippet" 4 | title = "Redirect" 5 | description = "Redirect a request by sending a 301 or 302 HTTP response" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#ab385d4c4e43608684889eaa390d4218:https://example.com/" 10 | -------------------------------------------------------------------------------- /templates/meta_data/rewrite_links_html.toml: -------------------------------------------------------------------------------- 1 | id = "rewrite_links_html" 2 | weight = 1 3 | type = "snippet" 4 | title = "Rewrite links in HTML" 5 | description = "Rewrite URL links in HTML using [HTMLRewriter](/workers/reference/apis/html-rewriter)" 6 | -------------------------------------------------------------------------------- /templates/meta_data/router.toml: -------------------------------------------------------------------------------- 1 | id = "router" 2 | type = "boilerplate" 3 | title = "Router" 4 | description = "Selects the logic based on the `request` method and URL. Use with REST APIs or apps that require routing logic." 5 | repository_url = "https://github.com/cloudflare/worker-template-router" 6 | url = "/templates/boilerplates/router" 7 | weight = 97 8 | 9 | share_url = "/templates/boilerplates/router" 10 | 11 | [demos.bar] 12 | text = "Demo /bar" 13 | url = "https://cloudflareworkers.com/#6cbbd3ae7d4e928da3502cb9ce11227a:https://tutorial.cloudflareworkers.com/bar" 14 | 15 | [demos.foo] 16 | text = "Demo /foo" 17 | url = "https://cloudflareworkers.com/#6cbbd3ae7d4e928da3502cb9ce11227a:https://tutorial.cloudflareworkers.com/foo" 18 | -------------------------------------------------------------------------------- /templates/meta_data/scala_hello_world.toml: -------------------------------------------------------------------------------- 1 | id = "scala_hello_world" 2 | weight = 1 3 | type = "boilerplate" 4 | title = "Scala Hello World" 5 | description = "Return a Hello World Response in Scala" 6 | repository_url = "https://github.com/cloudflare/scala-worker-hello-world" 7 | -------------------------------------------------------------------------------- /templates/meta_data/send_raw_html.toml: -------------------------------------------------------------------------------- 1 | id = "send_raw_html" 2 | weight = 1 3 | type = "snippet" 4 | title = "Send Raw HTML" 5 | share_url = "/templates/snippets/send_raw_html" 6 | description = "Delivers an HTML page from HTML directly in the Worker script." 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/#ba06ef26637ab98b1f38a18dc527dc69:https://example.com" 11 | tags = [ "Originless" ] 12 | -------------------------------------------------------------------------------- /templates/meta_data/send_raw_json.toml: -------------------------------------------------------------------------------- 1 | id = "send_raw_json" 2 | weight = 50 3 | type = "snippet" 4 | title = "Send Raw JSON" 5 | description = "Renders a response of type `application/json` to the client" 6 | 7 | [demos.main] 8 | text = "Demo" 9 | url = "https://cloudflareworkers.com/#83bc6debecf1dd443d3fabfbde0d2b3a:https://example.com" 10 | tags = [ "Originless" ] 11 | -------------------------------------------------------------------------------- /templates/meta_data/sentry.toml: -------------------------------------------------------------------------------- 1 | id = "sentry" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Sentry" 5 | description = "Log exceptions and errors in your Workers application to Sentry.io - an error tracking tool" 6 | repository_url = "https://github.com/bustle/cf-sentry" 7 | tags = [ "Middleware" ] 8 | -------------------------------------------------------------------------------- /templates/meta_data/signed_request.toml: -------------------------------------------------------------------------------- 1 | id = "signed_request" 2 | weight = 10 3 | type = "snippet" 4 | title = "Signed Request/Response" 5 | description = "Check signatures of requests and sign responses with a private key" 6 | share_url = "/templates/snippets/signed_request" 7 | 8 | [demos.main] 9 | text = "Demo" 10 | url = "https://cloudflareworkers.com/?hide_editor#c22ea0102fb9dced927a9b870e9c546a:https://example.com/" 11 | -------------------------------------------------------------------------------- /templates/meta_data/sites.toml: -------------------------------------------------------------------------------- 1 | id = "sites" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Worker Sites" 5 | description = "Get started with Workers Sites to easily deploy static assets to the Cloudflare edge." 6 | repository_url = "https://github.com/cloudflare/worker-sites-template" 7 | tags = [ "Originless" ] 8 | 9 | share_url = "/sites/start-from-scratch" 10 | -------------------------------------------------------------------------------- /templates/meta_data/speedtest_worker.toml: -------------------------------------------------------------------------------- 1 | id = "speedtest_worker" 2 | weight = 1 3 | type = "featured_boilerplate" 4 | title = "Speedtest" 5 | description = "Measure download / upload connection speed from the client side, using the Performance Timing API" 6 | repository_url = "https://github.com/cloudflare/worker-speedtest-template" 7 | -------------------------------------------------------------------------------- /templates/meta_data/typescript.toml: -------------------------------------------------------------------------------- 1 | id = "typescript" 2 | type = "boilerplate" 3 | title = "Hello World TypeScript" 4 | description = "Simple Hello World in TypeScript" 5 | repository_url = "https://github.com/EverlastingBugstopper/worker-typescript-template" 6 | weight = 90 7 | tags = [ "Originless" ] 8 | -------------------------------------------------------------------------------- /templates/meta_data/webcrypto.toml: -------------------------------------------------------------------------------- 1 | id = "webcrypto" 2 | weight = 1 3 | type = "snippet" 4 | title = "WebCrypto Sign/Verify and Encrypt/Decrypt" 5 | description = "Demonstrates the most common WebCrypto operations" 6 | 7 | -------------------------------------------------------------------------------- /templates/typescript/ab_testing.ts: -------------------------------------------------------------------------------- 1 | function handleRequest(request: Request): Response { 2 | const NAME = 'experiment-0' 3 | // The Responses below are placeholders. You can set up a custom path for each test (e.g. /control/somepath ). 4 | const TEST_RESPONSE = new Response('Test group') // e.g. await fetch('/test/sompath', request) 5 | const CONTROL_RESPONSE = new Response('Control group') // e.g. await fetch('/control/sompath', request) 6 | // Determine which group this requester is in. 7 | const cookie = request.headers.get('cookie') 8 | if (cookie && cookie.includes(`${NAME}=control`)) { 9 | return CONTROL_RESPONSE 10 | } else if (cookie && cookie.includes(`${NAME}=test`)) { 11 | return TEST_RESPONSE 12 | } else { 13 | // If there is no cookie, this is a new client. Choose a group and set the cookie. 14 | const group = Math.random() < 0.5 ? 'test' : 'control' // 50/50 split 15 | const response = group === 'control' ? CONTROL_RESPONSE : TEST_RESPONSE 16 | response.headers.append('Set-Cookie', `${NAME}=${group}; path=/`) 17 | return response 18 | } 19 | } 20 | 21 | addEventListener('fetch', event => { 22 | event.respondWith(handleRequest(event.request)) 23 | }) 24 | 25 | export {} 26 | -------------------------------------------------------------------------------- /templates/typescript/aggregate_requests.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': type, 5 | }, 6 | } 7 | const responses = await Promise.all([fetch(url1, init), fetch(url2, init)]) 8 | const results = await Promise.all([ 9 | gatherResponse(responses[0]), 10 | gatherResponse(responses[1]), 11 | ]) 12 | return new Response(results.join(), init) 13 | } 14 | addEventListener('fetch', event => { 15 | return event.respondWith(handleRequest()) 16 | }) 17 | /** 18 | * gatherResponse awaits and returns a response body as a string. 19 | * Use await gatherResponse(..) in an async function to get the response body 20 | * @param {Response} response 21 | */ 22 | async function gatherResponse(response: Response): Promise { 23 | const { headers } = response 24 | const contentType = headers.get('content-type') || '' 25 | if (contentType.includes('application/json')) { 26 | return JSON.stringify(await response.json()) 27 | } else if (contentType.includes('application/text')) { 28 | return await response.text() 29 | } else if (contentType.includes('text/html')) { 30 | return await response.text() 31 | } else { 32 | return await response.text() 33 | } 34 | } 35 | /** 36 | * Example someHost is set up to return JSON responses 37 | * Replace url1 and url2 with the hosts you wish to 38 | * send requests to 39 | * @param {string} url the URL to send the request to 40 | */ 41 | const someHost = 'https://workers-tooling.cf/demos' 42 | const url1 = someHost + '/requests/json' 43 | const url2 = someHost + '/requests/json' 44 | const type = 'application/json;charset=UTF-8' 45 | 46 | export {} 47 | -------------------------------------------------------------------------------- /templates/typescript/alter_headers.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | // Make the headers mutable by re-constructing the Request. 3 | request = new Request(request) 4 | request.headers.set('x-my-header', 'custom value') 5 | const URL = 'https://workers-tooling.cf/demos/static/html' 6 | // URL is set up to respond with dummy HTML, remove to send requests to your own origin 7 | let response = await fetch(URL, request) 8 | // Make the headers mutable by re-constructing the Response. 9 | response = new Response(response.body, response) 10 | response.headers.set('x-my-header', 'custom value') 11 | return response 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | 17 | export {} 18 | -------------------------------------------------------------------------------- /templates/typescript/auth_basic_http.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | if (!request.headers.has('authorization')) { 3 | return getUnauthorizedResponse( 4 | 'Provide User Name and Password to access this page.', 5 | ) 6 | } 7 | const authorization = request.headers.get('authorization') || '' 8 | const credentials = parseCredentials(authorization) 9 | if (credentials[0] !== USERNAME || credentials[1] !== PASSWORD) { 10 | return getUnauthorizedResponse( 11 | 'The User Name and Password combination you have entered is invalid.', 12 | ) 13 | } 14 | return await fetch(request) 15 | } 16 | addEventListener('fetch', event => { 17 | event.respondWith(handleRequest(event.request)) 18 | }) 19 | /** 20 | * Break down base64 encoded authorization string into plain-text username and password 21 | * @param {string} authorization 22 | * @returns {string[]} 23 | */ 24 | function parseCredentials(authorization: string): string[] { 25 | const parts = authorization.split(' ') 26 | const plainAuth = atob(parts[1]) 27 | const credentials = plainAuth.split(':') 28 | return credentials 29 | } 30 | /** 31 | * Helper function to generate Response object 32 | * @param {string} message 33 | * @returns {Response} 34 | */ 35 | function getUnauthorizedResponse(message: string): Response { 36 | const response = new Response(message, { 37 | status: 401, 38 | }) 39 | response.headers.set('WWW-Authenticate', `Basic realm="${REALM}"`) 40 | return response 41 | } 42 | /** 43 | * @param {string} USERNAME User name to access the page 44 | * @param {string} PASSWORD Password to access the page 45 | * @param {string} REALM A name of an area (a page or a group of pages) to protect. 46 | * Some browsers may show "Enter user name and password to access REALM" 47 | */ 48 | const USERNAME = 'demouser' 49 | const PASSWORD = 'demopassword' 50 | const REALM = 'Secure Area' 51 | 52 | export {} 53 | -------------------------------------------------------------------------------- /templates/typescript/auth_with_headers.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY) 3 | if (psk === PRESHARED_AUTH_HEADER_VALUE) { 4 | // Correct preshared header key supplied. Fetching request 5 | // from origin 6 | return fetch(request) 7 | } 8 | // Incorrect key rejecting request 9 | return new Response('Sorry, you have supplied an invalid key.', { 10 | status: 403, 11 | }) 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | /** 17 | * @param {string} PRESHARED_AUTH_HEADER_KEY custom header to check for key 18 | * @param {string} PRESHARED_AUTH_HEADER_VALUE hard coded key value 19 | */ 20 | const PRESHARED_AUTH_HEADER_KEY = 'X-Custom-PSK' 21 | const PRESHARED_AUTH_HEADER_VALUE = 'mypresharedkey' 22 | 23 | export {} 24 | -------------------------------------------------------------------------------- /templates/typescript/block_on_tls_version.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | try { 3 | const tlsVersion = request.cf.tlsVersion 4 | // Allow only TLS versions 1.2 and 1.3 5 | if (tlsVersion != 'TLSv1.2' && tlsVersion != 'TLSv1.3') { 6 | return new Response('Please use TLS version 1.2 or higher.', { 7 | status: 403, 8 | }) 9 | } 10 | return fetch(request) 11 | } catch (err) { 12 | console.error( 13 | 'request.cf does not exist in the previewer, only in production', 14 | ) 15 | return new Response('Error in workers script' + err.message, { 16 | status: 500, 17 | }) 18 | } 19 | } 20 | addEventListener('fetch', event => { 21 | event.respondWith(handleRequest(event.request)) 22 | }) 23 | 24 | export {} 25 | -------------------------------------------------------------------------------- /templates/typescript/bulk_origin_proxies.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const url = new URL(request.url) 3 | // Check if incoming hostname is a key in the ORIGINS object 4 | if (url.hostname in ORIGINS) { 5 | const target = ORIGINS[url.hostname] 6 | url.hostname = target 7 | // If it is, proxy request to that third party origin 8 | return fetch(url.toString(), request) 9 | } 10 | // Otherwise, process request as normal 11 | return fetch(request) 12 | } 13 | addEventListener('fetch', event => { 14 | event.respondWith(handleRequest(event.request)) 15 | }) 16 | /** 17 | * An object with different URLs to fetch 18 | * @param {Object} ORIGINS 19 | */ 20 | const ORIGINS: { [key: string]: string } = { 21 | 'starwarsapi.yourdomain.com': 'swapi.co', 22 | 'google.yourdomain.com': 'google.com', 23 | } 24 | 25 | export {} 26 | -------------------------------------------------------------------------------- /templates/typescript/bulk_redirects.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const requestURL = new URL(request.url) 3 | const path = requestURL.pathname.split('/redirect')[1] 4 | const location = redirectMap.get(path) 5 | if (location) { 6 | return Response.redirect(location, 301) 7 | } 8 | // If in map, return the original request 9 | return fetch(request) 10 | } 11 | addEventListener('fetch', async event => { 12 | event.respondWith(handleRequest(event.request)) 13 | }) 14 | const externalHostname = 'workers-tooling.cf' 15 | const redirectMap = new Map([ 16 | ['/bulk1', 'https://' + externalHostname + '/redirect2'], 17 | ['/bulk2', 'https://' + externalHostname + '/redirect3'], 18 | ['/bulk3', 'https://' + externalHostname + '/redirect4'], 19 | ['/bulk4', 'https://google.com'], 20 | ]) 21 | 22 | export {} 23 | -------------------------------------------------------------------------------- /templates/typescript/cache_api.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(event: FetchEvent): Promise { 2 | const request = event.request 3 | const cacheUrl = new URL(request.url) 4 | // hostname for a different zone 5 | cacheUrl.hostname = someOtherHostname 6 | const cacheKey = new Request(cacheUrl.toString(), request) 7 | const cache = caches.default 8 | // Get this request from this zone's cache 9 | let response = await cache.match(cacheKey) 10 | if (!response) { 11 | //if not in cache, grab it from the origin 12 | response = await fetch(request) 13 | // must use Response constructor to inherit all of response's fields 14 | response = new Response(response.body, response) 15 | // Cache API respects Cache-Control headers, so by setting max-age to 10 16 | // the response will only live in cache for max of 10 seconds 17 | response.headers.append('Cache-Control', 'max-age=10') 18 | // store the fetched response as cacheKey 19 | // use waitUntil so computational expensive tasks don't delay the response 20 | event.waitUntil(cache.put(cacheKey, response.clone())) 21 | } 22 | return response 23 | } 24 | async function handlePostRequest(event: FetchEvent): Promise { 25 | const request = event.request 26 | const body = await request.clone().text() 27 | const hash = await sha256(body) 28 | const cacheUrl = new URL(request.url) 29 | // get/store the URL in cache by prepending the body's hash 30 | cacheUrl.pathname = '/posts' + cacheUrl.pathname + hash 31 | // Convert to a GET to be able to cache 32 | const cacheKey = new Request(cacheUrl.toString(), { 33 | headers: request.headers, 34 | method: 'GET', 35 | }) 36 | const cache = caches.default 37 | //try to find the cache key in the cache 38 | let response = await cache.match(cacheKey) 39 | // otherwise, fetch response to POST request from origin 40 | if (!response) { 41 | response = await fetch(request) 42 | event.waitUntil(cache.put(cacheKey, response)) 43 | } 44 | return response 45 | } 46 | addEventListener('fetch', event => { 47 | try { 48 | const request = event.request 49 | if (request.method.toUpperCase() === 'POST') 50 | return event.respondWith(handlePostRequest(event)) 51 | return event.respondWith(handleRequest(event)) 52 | } catch (e) { 53 | return event.respondWith(new Response('Error thrown ' + e.message)) 54 | } 55 | }) 56 | async function sha256(message: string): Promise { 57 | // encode as UTF-8 58 | const msgBuffer = new TextEncoder().encode(message) 59 | // hash the message 60 | const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer) 61 | // convert ArrayBuffer to Array 62 | const hashArray = Array.from(new Uint8Array(hashBuffer)) 63 | // convert bytes to hex string 64 | const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('') 65 | return hashHex 66 | } 67 | const someOtherHostname = 'my.herokuapp.com' 68 | 69 | export {} 70 | -------------------------------------------------------------------------------- /templates/typescript/cache_ttl.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const url = new URL(request.url) 3 | // Only use the path for the cache key, removing query strings 4 | // and always storing HTTPS e.g. https://www.example.com/file-uri-here 5 | const someCustomKey = `https://${url.hostname}${url.pathname}` 6 | let response = await fetch(request, { 7 | cf: { 8 | // Tell Cloudflare's CDN to always cache this fetch regardless of content type 9 | // for a max of 5 seconds before revalidating the resource 10 | cacheTtl: 5, 11 | cacheEverything: true, 12 | //Enterprise only feature, see Cache API for other plans 13 | cacheKey: someCustomKey, 14 | }, 15 | }) 16 | // Reconstruct the Response object to make its headers mutable. 17 | response = new Response(response.body, response) 18 | //Set cache control headers to cache on browser for 25 minutes 19 | response.headers.set('Cache-Control', 'max-age=1500') 20 | return response 21 | } 22 | addEventListener('fetch', event => { 23 | return event.respondWith(handleRequest(event.request)) 24 | }) 25 | 26 | export {} 27 | -------------------------------------------------------------------------------- /templates/typescript/conditional_response.ts: -------------------------------------------------------------------------------- 1 | const BLOCKED_HOSTNAMES = ['nope.mywebsite.com', 'bye.website.com'] 2 | async function handleRequest(request: Request): Promise { 3 | // Return a new Response based on.. 4 | // On URL's hostname 5 | const url = new URL(request.url) 6 | if (BLOCKED_HOSTNAMES.includes(url.hostname)) { 7 | return new Response('Blocked Host', { status: 403 }) 8 | } 9 | // On URL's file extension (e.g. block paths ending in .doc or .xml) 10 | const forbiddenExtRegExp = new RegExp(/\.(doc|xml)$/) 11 | if (forbiddenExtRegExp.test(url.pathname)) { 12 | return new Response('Blocked Extension', { status: 403 }) 13 | } 14 | // On HTTP method 15 | if (request.method === 'POST') { 16 | return new Response('Response for POST') 17 | } 18 | // On User Agent 19 | const userAgent = request.headers.get('User-Agent') || '' 20 | if (userAgent.includes('bot')) { 21 | return new Response('Block User Agent containing bot', { status: 403 }) 22 | } 23 | // On Client's IP address 24 | const clientIP = request.headers.get('CF-Connecting-IP') 25 | if (clientIP === '1.2.3.4') { 26 | return new Response('Block the IP 1.2.3.4', { status: 403 }) 27 | } 28 | // On ASN 29 | if (request.cf && request.cf.asn == 64512) { 30 | return new Response('Block the ASN 64512 response') 31 | } 32 | // On Device Type 33 | // Requires Enterprise "CF-Device-Type Header" zone setting or 34 | // Page Rule with "Cache By Device Type" setting applied. 35 | const device = request.headers.get('CF-Device-Type') 36 | if (device === 'mobile') { 37 | return Response.redirect('https://mobile.example.com') 38 | } 39 | console.error( 40 | "Getting Client's IP address, device type, and ASN are not supported in playground. Must test on a live worker", 41 | ) 42 | return fetch(request) 43 | } 44 | addEventListener('fetch', event => { 45 | event.respondWith(handleRequest(event.request)) 46 | }) 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /templates/typescript/cookie_extract.ts: -------------------------------------------------------------------------------- 1 | const COOKIE_NAME = '__uid' 2 | function handleRequest(request: Request): Response { 3 | const cookie = getCookie(request, COOKIE_NAME) 4 | if (cookie) { 5 | // respond with the cookie value 6 | return new Response(cookie) 7 | } 8 | return new Response('No cookie with name: ' + COOKIE_NAME) 9 | } 10 | addEventListener('fetch', event => { 11 | event.respondWith(handleRequest(event.request)) 12 | }) 13 | /** 14 | * Grabs the cookie with name from the request headers 15 | * @param {Request} request incoming Request 16 | * @param {string} name of the cookie to grab 17 | */ 18 | function getCookie(request: Request, name: string): string { 19 | let result = '' 20 | const cookieString = request.headers.get('Cookie') 21 | if (cookieString) { 22 | const cookies = cookieString.split(';') 23 | cookies.forEach(cookie => { 24 | const cookieName = cookie.split('=')[0].trim() 25 | if (cookieName === name) { 26 | const cookieVal = cookie.split('=')[1] 27 | result = cookieVal 28 | } 29 | }) 30 | } 31 | return result 32 | } 33 | 34 | export {} 35 | -------------------------------------------------------------------------------- /templates/typescript/cors_header_proxy.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const url = new URL(request.url) 3 | let apiUrl = url.searchParams.get('apiurl') 4 | if (apiUrl == null) { 5 | apiUrl = API_URL 6 | } 7 | // Rewrite request to point to API url. This also makes the request mutable 8 | // so we can add the correct Origin header to make the API server think 9 | // that this request isn't cross-site. 10 | request = new Request(apiUrl, request) 11 | request.headers.set('Origin', new URL(apiUrl).origin) 12 | let response = await fetch(request) 13 | // Recreate the response so we can modify the headers 14 | response = new Response(response.body, response) 15 | // Set CORS headers 16 | response.headers.set('Access-Control-Allow-Origin', url.origin) 17 | // Append to/Add Vary header so browser will cache response correctly 18 | response.headers.append('Vary', 'Origin') 19 | return response 20 | } 21 | function handleOptions(request: Request): Response { 22 | // Make sure the necessary headers are present 23 | // for this to be a valid pre-flight request 24 | if ( 25 | request.headers.get('Origin') !== null && 26 | request.headers.get('Access-Control-Request-Method') !== null && 27 | request.headers.get('Access-Control-Request-Headers') !== null 28 | ) { 29 | // Handle CORS pre-flight request. 30 | // If you want to check the requested method + headers 31 | // you can do that here. 32 | return new Response(null, { 33 | headers: corsHeaders, 34 | }) 35 | } else { 36 | // Handle standard OPTIONS request. 37 | // If you want to allow other HTTP Methods, you can do that here. 38 | return new Response(null, { 39 | headers: { 40 | Allow: 'GET, HEAD, POST, OPTIONS', 41 | }, 42 | }) 43 | } 44 | } 45 | addEventListener('fetch', event => { 46 | const request = event.request 47 | const url = new URL(request.url) 48 | if (url.pathname.startsWith(PROXY_ENDPOINT)) { 49 | if (request.method === 'OPTIONS') { 50 | // Handle CORS preflight requests 51 | event.respondWith(handleOptions(request)) 52 | } else if ( 53 | request.method === 'GET' || 54 | request.method === 'HEAD' || 55 | request.method === 'POST' 56 | ) { 57 | // Handle requests to the API server 58 | event.respondWith(handleRequest(request)) 59 | } else { 60 | event.respondWith( 61 | new Response(null, { 62 | status: 405, 63 | statusText: 'Method Not Allowed', 64 | }), 65 | ) 66 | } 67 | } else { 68 | // Serve demo page 69 | event.respondWith(rawHtmlResponse(DEMO_PAGE)) 70 | } 71 | }) 72 | // We support the GET, POST, HEAD, and OPTIONS methods from any origin, 73 | // and accept the Content-Type header on requests. These headers must be 74 | // present on all responses to all CORS requests. In practice, this means 75 | // all responses to OPTIONS requests. 76 | const corsHeaders = { 77 | 'Access-Control-Allow-Origin': '*', 78 | 'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS', 79 | 'Access-Control-Allow-Headers': 'Content-Type', 80 | } 81 | // The URL for the remote third party API you want to fetch from 82 | // but does not implement CORS 83 | const API_URL = 'https://workers-tooling.cf/demos/demoapi' 84 | // The endpoint you want the CORS reverse proxy to be on 85 | const PROXY_ENDPOINT = '/corsproxy/' 86 | // The rest of this snippet for the demo page 87 | async function rawHtmlResponse(html: string): Promise { 88 | return new Response(html, { 89 | headers: { 90 | 'content-type': 'text/html;charset=UTF-8', 91 | }, 92 | }) 93 | } 94 | const DEMO_PAGE = ` 95 | 96 | 97 | 98 |

API GET without CORS Proxy

99 | Shows TypeError: Failed to fetch since CORS is misconfigured 100 |

101 | Waiting 102 |

API GET with CORS Proxy

103 |

104 | Waiting 105 |

API POST with CORS Proxy + Preflight

106 |

107 | Waiting 108 | 142 | 143 | ` 144 | 145 | export {} 146 | -------------------------------------------------------------------------------- /templates/typescript/country_code.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | return redirect(request) 3 | } 4 | addEventListener('fetch', event => { 5 | event.respondWith(handleRequest(event.request)) 6 | }) 7 | /** 8 | * Returns a redirect determined by the country code 9 | * @param {Request} request 10 | */ 11 | async function redirect(request: Request): Promise { 12 | // The `cf-ipcountry` header is not supported in the preview 13 | const country = request.headers.get('cf-ipcountry') 14 | if (country != null && country in countryMap) { 15 | const url = countryMap[country] 16 | return Response.redirect(url) 17 | } else { 18 | return await fetch(request) 19 | } 20 | } 21 | /** 22 | * A map of the URLs to redirect to 23 | * @param {Object} countryMap 24 | */ 25 | const countryMap: { [key: string]: string } = { 26 | US: 'https://example.com/us', 27 | EU: 'https://eu.example.com/', 28 | } 29 | 30 | export {} 31 | -------------------------------------------------------------------------------- /templates/typescript/debugging_tips.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(event: FetchEvent): Promise { 2 | let response 3 | try { 4 | response = await fetch(event.request) 5 | if (!response.ok) { 6 | const body = await response.text() 7 | throw new Error( 8 | 'Bad response at origin. Status: ' + 9 | response.status + 10 | ' Body: ' + 11 | //ensures the string is small enough to be a header 12 | body.trim().substring(0, 10), 13 | ) 14 | } 15 | } catch (err) { 16 | // Without event.waitUntil(), our fetch() to our logging service may 17 | // or may not complete. 18 | event.waitUntil(postLog((err as Error).toString())) 19 | const stack = JSON.stringify(err.stack) || err 20 | // Copy the response and initialize body to the stack trace 21 | response = new Response(stack, response) 22 | // Shove our rewritten URL into a header to find out what it was. 23 | response.headers.set('X-Debug-stack', stack) 24 | response.headers.set('X-Debug-err', err) 25 | } 26 | return response 27 | } 28 | addEventListener('fetch', event => { 29 | //Have any uncaught errors thrown go directly to origin 30 | event.passThroughOnException() 31 | event.respondWith(handleRequest(event)) 32 | }) 33 | function postLog(data: string) { 34 | return fetch(LOG_URL, { 35 | method: 'POST', 36 | body: data, 37 | }) 38 | } 39 | // Service configured to receive logs 40 | const LOG_URL = 'https://log-service.example.com/' 41 | 42 | export {} 43 | -------------------------------------------------------------------------------- /templates/typescript/fetch_html.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(): Promise { 2 | const init = { 3 | headers: { 4 | 'content-type': 'text/html;charset=UTF-8', 5 | }, 6 | } 7 | const response = await fetch(url, init) 8 | const results = await gatherResponse(response) 9 | return new Response(results, init) 10 | } 11 | addEventListener('fetch', event => { 12 | return event.respondWith(handleRequest()) 13 | }) 14 | /** 15 | * gatherResponse awaits and returns a response body as a string. 16 | * Use await gatherResponse(..) in an async function to get the response body 17 | * @param {Response} response 18 | */ 19 | async function gatherResponse(response: Response): Promise { 20 | const { headers } = response 21 | const contentType = headers.get('content-type') || '' 22 | if (contentType.includes('application/json')) { 23 | return JSON.stringify(await response.json()) 24 | } else if (contentType.includes('application/text')) { 25 | return await response.text() 26 | } else if (contentType.includes('text/html')) { 27 | return await response.text() 28 | } else { 29 | return await response.text() 30 | } 31 | } 32 | /** 33 | * Example someHost at url is set up to respond with HTML 34 | * Replace url with the host you wish to send requests to 35 | */ 36 | const someHost = 'https://workers-tooling.cf/demos' 37 | const url = someHost + '/static/html' 38 | 39 | export {} 40 | -------------------------------------------------------------------------------- /templates/typescript/fetch_json.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(): Promise { 2 | const init = { 3 | headers: { 4 | 'content-type': 'application/json;charset=UTF-8', 5 | }, 6 | } 7 | const response = await fetch(url, init) 8 | const results = await gatherResponse(response) 9 | return new Response(results, init) 10 | } 11 | addEventListener('fetch', event => { 12 | return event.respondWith(handleRequest()) 13 | }) 14 | /** 15 | * gatherResponse awaits and returns a response body as a string. 16 | * Use await gatherResponse(..) in an async function to get the response body 17 | * @param {Response} response 18 | */ 19 | async function gatherResponse(response: Response): Promise { 20 | const { headers } = response 21 | const contentType = headers.get('content-type') || '' 22 | if (contentType.includes('application/json')) { 23 | return JSON.stringify(await response.json()) 24 | } else if (contentType.includes('application/text')) { 25 | return await response.text() 26 | } else if (contentType.includes('text/html')) { 27 | return await response.text() 28 | } else { 29 | return await response.text() 30 | } 31 | } 32 | /** 33 | * Example someHost is set up to take in a JSON request 34 | * Replace url with the host you wish to send requests to 35 | * @param {string} someHost the host to send the request to 36 | * @param {string} url the URL to send the request to 37 | */ 38 | const someHost = 'https://workers-tooling.cf/demos' 39 | const url = someHost + '/static/json' 40 | 41 | export {} 42 | -------------------------------------------------------------------------------- /templates/typescript/hot_link_protection.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | // Fetch the original request 3 | const response = await fetch(request) 4 | // If it's an image, engage hotlink protection based on the 5 | // Referer header. 6 | const referer = request.headers.get('Referer') 7 | const contentType = response.headers.get('Content-Type') || '' 8 | if (referer && contentType.startsWith(PROTECTED_TYPE)) { 9 | // If the hostnames don't match, it's a hotlink 10 | if (new URL(referer).hostname !== new URL(request.url).hostname) { 11 | // Redirect the user to your website 12 | return Response.redirect(HOMEPAGE_URL, 302) 13 | } 14 | } 15 | // Everything is fine, return the response normally. 16 | return response 17 | } 18 | addEventListener('fetch', event => { 19 | event.respondWith(handleRequest(event.request)) 20 | }) 21 | const HOMEPAGE_URL = 'https://tutorial.cloudflareworkers.com/' 22 | const PROTECTED_TYPE = 'images/' 23 | 24 | export {} 25 | -------------------------------------------------------------------------------- /templates/typescript/http2_server_push.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | // If request is for test.css just serve the raw CSS 3 | if (/test.css$/.test(request.url)) { 4 | return new Response(CSS, { 5 | headers: { 6 | 'content-type': 'text/css', 7 | }, 8 | }) 9 | } else { 10 | // serve raw HTML using HTTP/2 for the CSS file 11 | return new Response(HTML, { 12 | headers: { 13 | 'content-type': 'text/html', 14 | Link: '; rel=preload;', 15 | }, 16 | }) 17 | } 18 | } 19 | addEventListener('fetch', event => { 20 | event.respondWith(handleRequest(event.request)) 21 | }) 22 | const CSS = `body { color: red; }` 23 | const HTML = ` 24 | 25 | 26 | 27 | 28 | Server push test 29 | 30 | 31 | 32 |

Server push test page

33 | 34 | 35 | ` 36 | 37 | export {} 38 | -------------------------------------------------------------------------------- /templates/typescript/modify_req_props.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | /** 3 | * Best practice is to only assign new properties on the request 4 | * object (i.e. RequestInit props) through either a method or the constructor 5 | */ 6 | const newRequestInit = { 7 | // Change method 8 | method: 'POST', 9 | // Change body 10 | body: JSON.stringify({ bar: 'foo' }), 11 | // Change the redirect mode. 12 | redirect: 'follow' as RequestRedirect, 13 | //Change headers, note this method will erase existing headers 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | // Change a Cloudflare feature on the outbound response 18 | cf: { apps: false }, 19 | } 20 | // Change just the host 21 | const url = new URL(someUrl) 22 | url.hostname = someHost 23 | // Best practice is to always use the original request to construct the new request 24 | // thereby cloning all the attributes, applying the URL also requires a constructor 25 | // since once a Request has been constructed, its URL is immutable. 26 | const newRequest = new Request( 27 | url.toString(), 28 | new Request(request, newRequestInit), 29 | ) 30 | // Set headers using method 31 | newRequest.headers.set('X-Example', 'bar') 32 | newRequest.headers.set('Content-Type', 'application/json') 33 | try { 34 | return await fetch(newRequest) 35 | } catch (e) { 36 | return new Response(JSON.stringify({ error: e.message }), { status: 500 }) 37 | } 38 | } 39 | addEventListener('fetch', event => { 40 | event.respondWith(handleRequest(event.request)) 41 | }) 42 | /** 43 | * Example someHost is set up to return raw JSON 44 | * @param {string} someUrl the URL to send the request to, since we are setting hostname too only path is applied 45 | * @param {string} someHost the host the request will resolve too 46 | */ 47 | const someHost = 'example.com' 48 | const someUrl = 'https://foo.example.com/api.js' 49 | 50 | export {} 51 | -------------------------------------------------------------------------------- /templates/typescript/modify_res_props.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | /** 3 | * Response properties are immutable. To change them, construct a new 4 | * Response, passing modified status or statusText in the ResponseInit 5 | * object. 6 | * Response Headers can be modified through the headers `set` method. 7 | */ 8 | const originalResponse = await fetch(request) 9 | // Change status and statusText, but preserve body and headers 10 | let response = new Response(originalResponse.body, { 11 | status: 500, 12 | statusText: 'some message', 13 | headers: originalResponse.headers, 14 | }) 15 | // Change response body by adding the foo prop 16 | const originalBody = await originalResponse.json() 17 | const body = JSON.stringify({ foo: 'bar', ...originalBody }) 18 | response = new Response(body, response) 19 | // Add a header using set method 20 | response.headers.set('foo', 'bar') 21 | // Set destination header to the value of the source header 22 | const src = response.headers.get(headerNameSrc) 23 | if (src != null) { 24 | response.headers.set(headerNameDst, src) 25 | console.log( 26 | `Response header "${headerNameDst}" was set to "${response.headers.get( 27 | headerNameDst, 28 | )}"`, 29 | ) 30 | } 31 | return response 32 | } 33 | addEventListener('fetch', event => { 34 | event.respondWith(handleRequest(event.request)) 35 | }) 36 | /** 37 | * @param {string} headerNameSrc the header to get the new value from 38 | * @param {string} headerNameDst the header to set based off of value in src 39 | */ 40 | const headerNameSrc = 'foo' //'Orig-Header' 41 | const headerNameDst = 'Last-Modified' 42 | 43 | export {} 44 | -------------------------------------------------------------------------------- /templates/typescript/post_data.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest(request: Request): Promise { 2 | const reqBody = await readRequestBody(request) 3 | const retBody = `The request body sent in was ${reqBody}` 4 | return new Response(retBody) 5 | } 6 | addEventListener('fetch', event => { 7 | const { request } = event 8 | const { url } = request 9 | if (url.includes('form')) { 10 | return event.respondWith(rawHtmlResponse(someForm)) 11 | } 12 | if (request.method === 'POST') { 13 | return event.respondWith(handleRequest(request)) 14 | } else if (request.method === 'GET') { 15 | return event.respondWith(new Response(`The request was a GET`)) 16 | } 17 | }) 18 | /** 19 | * rawHtmlResponse delivers a response with HTML inputted directly 20 | * into the worker script 21 | * @param {string} html 22 | */ 23 | function rawHtmlResponse(html: string): Response { 24 | const init = { 25 | headers: { 26 | 'content-type': 'text/html;charset=UTF-8', 27 | }, 28 | } 29 | return new Response(html, init) 30 | } 31 | /** 32 | * readRequestBody reads in the incoming request body 33 | * Use await readRequestBody(..) in an async function to get the string 34 | * @param {Request} request the incoming request to read from 35 | */ 36 | async function readRequestBody(request: Request): Promise { 37 | const { headers } = request 38 | const contentType = headers.get('content-type') || '' 39 | if (contentType.includes('application/json')) { 40 | return JSON.stringify(await request.json()) 41 | } else if (contentType.includes('application/text')) { 42 | return await request.text() 43 | } else if (contentType.includes('text/html')) { 44 | return await request.text() 45 | } else if (contentType.includes('form')) { 46 | const formData = await request.formData() 47 | const body: { [key: string]: FormDataEntryValue } = {} 48 | for (const entry of formData.entries()) { 49 | body[entry[0]] = entry[1] 50 | } 51 | return JSON.stringify(body) 52 | } else { 53 | const myBlob = await request.blob() 54 | const objectURL = URL.createObjectURL(myBlob) 55 | return objectURL 56 | } 57 | } 58 | const someForm = ` 59 | 60 | 61 | 62 |

Hello World

63 |

This is all generated using a Worker

64 |
65 |
66 | 67 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 | 79 | ` 80 | 81 | export {} 82 | -------------------------------------------------------------------------------- /templates/typescript/post_json.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | body: JSON.stringify(body), 4 | method: 'POST', 5 | headers: { 6 | 'content-type': 'application/json;charset=UTF-8', 7 | }, 8 | } 9 | const response = await fetch(url, init) 10 | const results = await gatherResponse(response) 11 | return new Response(results, init) 12 | } 13 | addEventListener('fetch', event => { 14 | return event.respondWith(handleRequest()) 15 | }) 16 | /** 17 | * gatherResponse awaits and returns a response body as a string. 18 | * Use await gatherResponse(..) in an async function to get the response body 19 | * @param {Response} response 20 | */ 21 | async function gatherResponse(response: Response): Promise { 22 | const { headers } = response 23 | const contentType = headers.get('content-type') || '' 24 | if (contentType.includes('application/json')) { 25 | return JSON.stringify(await response.json()) 26 | } else if (contentType.includes('application/text')) { 27 | return await response.text() 28 | } else if (contentType.includes('text/html')) { 29 | return await response.text() 30 | } else { 31 | return await response.text() 32 | } 33 | } 34 | /** 35 | * Example someHost is set up to take in a JSON request 36 | * Replace url with the host you wish to send requests to 37 | * @param {string} url the URL to send the request to 38 | * @param {BodyInit} body the JSON data to send in the request 39 | */ 40 | const someHost = 'https://workers-tooling.cf/demos' 41 | const url = someHost + '/requests/json' 42 | const body = { 43 | results: ['default data to send'], 44 | errors: null, 45 | msg: 'I sent this to the fetch', 46 | } 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /templates/typescript/private_data_loss.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Define personal data with regular expressions 3 | * Respond with block if credit card data, and strip 4 | * emails and phone numbers from the response 5 | * Execution will be limited to MIME type "text/*" 6 | */ 7 | async function handleRequest(request: Request): Promise { 8 | const response = await fetch(request) 9 | // Return origin response, if response wasn't text 10 | const contentType = response.headers.get('content-type') || '' 11 | if (!contentType.toLowerCase().includes('text/')) { 12 | return response 13 | } 14 | let text = await response.text() 15 | text = DEBUG 16 | ? // for testing only - replace the response from the origin with an email 17 | text.replace('You may use this', 'me@example.com may use this') 18 | : text 19 | const sensitiveRegexsMap: { [key: string]: string } = { 20 | email: String.raw`\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b`, 21 | phone: String.raw`\b07\d{9}\b`, 22 | creditCard: String.raw`\b(?:4[0-9]{12}(?:[0-9]{3})?|(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b`, 23 | } 24 | for (const kind in sensitiveRegexsMap) { 25 | const sensitiveRegex = new RegExp(sensitiveRegexsMap[kind], 'ig') 26 | const match = await sensitiveRegex.test(text) 27 | if (match) { 28 | // alert a data breach by posting to a webhook server 29 | await postDataBreach(request) 30 | // respond with a block if credit card, else replace 31 | // sensitive text with *'s 32 | return kind === 'creditCard' 33 | ? new Response(kind + ' found\nForbidden\n', { 34 | status: 403, 35 | statusText: 'Forbidden', 36 | }) 37 | : new Response(text.replace(sensitiveRegex, '**********'), response) 38 | } 39 | } 40 | return new Response(text, response) 41 | } 42 | 43 | addEventListener('fetch', event => { 44 | event.respondWith(handleRequest(event.request)) 45 | }) 46 | 47 | async function postDataBreach(request: Request): Promise { 48 | const trueClientIp = request.headers.get('cf-connecting-ip') 49 | const epoch = new Date().getTime() 50 | const body = { 51 | ip: trueClientIp, 52 | time: epoch, 53 | request: request, 54 | } 55 | const init = { 56 | body: JSON.stringify(body), 57 | method: 'POST', 58 | headers: { 59 | 'content-type': 'application/json;charset=UTF-8', 60 | }, 61 | } 62 | return await fetch(SOME_HOOK_SERVER, init) 63 | } 64 | 65 | const SOME_HOOK_SERVER = 'https://webhook.flow-wolf.io/hook' 66 | const DEBUG = true 67 | 68 | export {} 69 | -------------------------------------------------------------------------------- /templates/typescript/redirect.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | return Response.redirect(someURLToRedirectTo, code) 3 | } 4 | addEventListener('fetch', async event => { 5 | event.respondWith(handleRequest()) 6 | }) 7 | /** 8 | * @param {Request} url where to redirect the response 9 | * @param {number?=301|302} type permanent or temporary redirect 10 | */ 11 | const someURLToRedirectTo = 'https://www.google.com' 12 | const code = 301 13 | 14 | export {} 15 | -------------------------------------------------------------------------------- /templates/typescript/rewrite_links_html.ts: -------------------------------------------------------------------------------- 1 | const OLD_URL = 'developer.mozilla.org' 2 | const NEW_URL = 'mynewdomain.com' 3 | 4 | async function handleRequest(req: Request): Promise { 5 | const res = await fetch(req) 6 | return rewriter.transform(res) 7 | } 8 | 9 | class AttributeRewriter { 10 | attributeName: string 11 | 12 | constructor(attributeName: string) { 13 | this.attributeName = attributeName 14 | } 15 | 16 | element(element: Element) { 17 | const attribute = element.getAttribute(this.attributeName) 18 | if (attribute) { 19 | element.setAttribute( 20 | this.attributeName, 21 | attribute.replace(OLD_URL, NEW_URL), 22 | ) 23 | } 24 | } 25 | } 26 | 27 | const rewriter = new HTMLRewriter() 28 | .on('a', new AttributeRewriter('href')) 29 | .on('img', new AttributeRewriter('src')) 30 | 31 | addEventListener('fetch', event => { 32 | event.respondWith(handleRequest(event.request)) 33 | }) 34 | 35 | export {} 36 | -------------------------------------------------------------------------------- /templates/typescript/send_raw_html.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'text/html;charset=UTF-8', 5 | }, 6 | } 7 | return new Response(someHTML, init) 8 | } 9 | addEventListener('fetch', event => { 10 | return event.respondWith(handleRequest()) 11 | }) 12 | const someHTML = ` 13 | 14 | 15 |

Hello World

16 |

This is all generated using a Worker

17 | 25 | 26 | 27 | ` 28 | 29 | export {} 30 | -------------------------------------------------------------------------------- /templates/typescript/send_raw_json.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | const init = { 3 | headers: { 4 | 'content-type': 'application/json;charset=UTF-8', 5 | }, 6 | } 7 | return new Response(JSON.stringify(someJSON), init) 8 | } 9 | addEventListener('fetch', event => { 10 | return event.respondWith(handleRequest()) 11 | }) 12 | const someJSON = { 13 | result: ['some', 'results'], 14 | errors: null, 15 | msg: 'this is some random json', 16 | } 17 | 18 | export {} 19 | -------------------------------------------------------------------------------- /templates/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": false, 4 | "lib": ["ES2020", "webworker"], 5 | "module": "ES2020", 6 | "outDir": "../javascript", 7 | "sourceMap": false, 8 | "strict": true, 9 | "target": "ES2020", 10 | "types": ["@cloudflare/workers-types"] 11 | }, 12 | "include": ["./*.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /templates/typescript/webcrypto.ts: -------------------------------------------------------------------------------- 1 | async function handleRequest() { 2 | let msg = "alice and bob" 3 | let hmacresult = await generateSignandVerify({ 4 | name: "HMAC", 5 | hash: "sha-256" 6 | }) 7 | console.log("Result of HMAC generate, sign, verify: ", hmacresult) 8 | let aesresult = await generateEncryptDecrypt({ 9 | name: "AES-GCM", 10 | length: 256 11 | }, msg) 12 | var dec = new TextDecoder(); 13 | if (msg == dec.decode(aesresult)) { 14 | console.log("AES encrypt decrypt successful") 15 | } else { 16 | console.log("AES encrypt decrypt failed") 17 | } 18 | return new Response() 19 | } 20 | 21 | addEventListener('fetch', event => { 22 | event.respondWith(handleRequest()) 23 | }) 24 | 25 | async function generateSignandVerify(algorithm: any) { 26 | let rawMessage = "alice and bob" 27 | let key = await self.crypto.subtle.generateKey(algorithm, true, ["sign", "verify"]) as CryptoKey; 28 | let enc = new TextEncoder(); 29 | let encoded = enc.encode(rawMessage); 30 | let signature = await self.crypto.subtle.sign( 31 | algorithm, 32 | key, 33 | encoded 34 | ); 35 | let result = await self.crypto.subtle.verify( 36 | algorithm, 37 | key, 38 | signature, 39 | encoded 40 | ); 41 | return result; 42 | } 43 | 44 | async function generateEncryptDecrypt(algorithm: any, msg: string) { 45 | let key = await self.crypto.subtle.generateKey( 46 | algorithm, 47 | true, 48 | ["encrypt", "decrypt"] 49 | ) as CryptoKey; 50 | let enc = new TextEncoder(); 51 | let encoded = enc.encode(msg); 52 | algorithm.iv = crypto.getRandomValues(new Uint8Array(16)) 53 | let signature = await self.crypto.subtle.encrypt( 54 | algorithm, 55 | key, 56 | encoded 57 | ); 58 | let result = await self.crypto.subtle.decrypt( 59 | algorithm, 60 | key, 61 | signature 62 | ); 63 | return result; 64 | } 65 | -------------------------------------------------------------------------------- /workers-site/.cargo-ok: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/template-registry/efe84d23ba96843bb5155a3e8b2902b7388ece82/workers-site/.cargo-ok -------------------------------------------------------------------------------- /workers-site/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | worker 3 | -------------------------------------------------------------------------------- /workers-site/index.ts: -------------------------------------------------------------------------------- 1 | import * as toml from 'toml' 2 | //relative paths of the stored data (e.g. folders in ../templates) 3 | const META_DATA_PATH = 'meta_data' 4 | const JS_PATH = 'javascript' 5 | 6 | declare global { 7 | var __STATIC_CONTENT: any, __STATIC_CONTENT_MANIFEST: any 8 | } 9 | class CustomError extends Error { 10 | constructor(status: number, message?: string) { 11 | super(message) // 'Error' breaks prototype chain here 12 | this.status = status 13 | Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain 14 | } 15 | code: number 16 | message: string 17 | status: number 18 | } 19 | /** 20 | * The DEBUG flag will do two things that help during development: 21 | * 1. we will skip caching on the edge, which makes it easier to 22 | * debug. 23 | * 2. we will return an error message on exception in your Response rather 24 | * than the default 404.html page. 25 | */ 26 | 27 | const DEBUG = true 28 | 29 | addEventListener('fetch', (event: FetchEvent) => { 30 | event.respondWith(handleEvent(event)) 31 | }) 32 | 33 | async function handleEvent(event: FetchEvent): Promise { 34 | try { 35 | const url: string = event.request.url 36 | 37 | let jsonResponse 38 | let path = new URL(url).pathname 39 | if (new RegExp(/^\/templates\/*$/).test(path)) { 40 | jsonResponse = await grabTemplates() 41 | } else if (new RegExp(/^\/templates\/\w+$/).test(path)) { 42 | let id = getIDFromPath(path) 43 | const key = formTomlKey(id) 44 | const jsonData = await grabTemplate(key) 45 | jsonResponse = new Response(JSON.stringify(jsonData)) 46 | } else { 47 | return new Response('Not a valid endpoint ' + path + ' e.g. /templates/ab_testing is valid', { 48 | status: 404, 49 | }) 50 | } 51 | return jsonResponse 52 | } catch (e) { 53 | if (DEBUG) { 54 | return new Response(e.stack, { 55 | status: 500, 56 | }) 57 | } 58 | } 59 | } 60 | /** 61 | * Looks for all the keys in __STATIC_CONTENT_MANIFEST 62 | * and then locates and serves the TOML files 63 | * from __STATIC_CONTENT */ 64 | async function grabTemplates(): Promise { 65 | const manifest = JSON.parse(__STATIC_CONTENT_MANIFEST) 66 | const allKeys = Object.keys(manifest).filter(key => key.includes('.toml')) 67 | let results = [] 68 | for (const key of allKeys) { 69 | // const allTomls = allKeys.reduce(async key => { 70 | const id = getIDFromKey(key) 71 | try { 72 | const jsonData = await grabTemplate(key) 73 | results.push(jsonData) 74 | } catch (e) { 75 | console.log('e', e) 76 | new Response(JSON.stringify(e)) //, e.status) 77 | } 78 | } 79 | return new Response(JSON.stringify(results)) 80 | } 81 | 82 | /** 83 | * Same as grabTemplates but for individual template 84 | * @param key the file path to the toml 85 | */ 86 | async function grabTemplate(key: string): Promise { 87 | const manifest = JSON.parse(__STATIC_CONTENT_MANIFEST) 88 | const tomlData = await __STATIC_CONTENT.get(manifest[key]) 89 | if (!tomlData) { 90 | throw new CustomError(404, 'Key ' + key + ' not Found') 91 | } 92 | const jsonData = toml.parse(tomlData) 93 | // grab the javascript file if it exists from templates/javascript/:id.js 94 | const jsKey = formJsKey(getIDFromKey(key)) 95 | 96 | const jsData = await __STATIC_CONTENT.get(manifest[jsKey]) 97 | if (jsData) { 98 | jsonData.code = jsData 99 | } 100 | return jsonData 101 | } 102 | const formTomlKey = (id: string) => META_DATA_PATH + '/' + id + '.toml' 103 | 104 | const formJsKey = (id: string) => JS_PATH + '/' + id + '.js' 105 | 106 | /** 107 | * Takes a URL path and gives the :id 108 | * @param path a URL path (e.g. /templates/:id) 109 | */ 110 | const getIDFromPath = (path: string) => { 111 | let fileName = 112 | path.search(/^\/templates\/[\w]+$/) !== -1 ? path.match(/^\/templates\/\w+$/)[0] : '' 113 | return fileName.replace('/templates/', '') 114 | } 115 | 116 | /** 117 | * Takes in a file key and returns the IF 118 | * @param key the KV / filepath key (e.g. javascript/:id.js) 119 | */ 120 | const getIDFromKey = (key: string) => { 121 | let fileName = key.search(/\/\w+\.\w+$/) !== -1 ? key.match(/[\/|\w]+\.\w+$/)[0] : '' 122 | return fileName 123 | .replace('.toml', '') 124 | .replace('.js', '') 125 | .replace(JS_PATH + '/', '') 126 | .replace(META_DATA_PATH + '/', '') 127 | } 128 | -------------------------------------------------------------------------------- /workers-site/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "toml": { 8 | "version": "3.0.0", 9 | "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", 10 | "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /workers-site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "worker", 4 | "version": "1.0.0", 5 | "main": "./dist/index.js", 6 | "author": "Victoria Bernard ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "toml": "^3.0.0" 10 | }, 11 | "scripts": { 12 | "build": "cd workers-site && tsc -d" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /workers-site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "noImplicitAny": true, 5 | "target": "es5", 6 | "allowJs": false, 7 | "lib": ["webworker", "es7"] 8 | }, 9 | "include": ["./*.ts", "./**/*.ts", "./test/**/*.ts", "./test/*.ts", "./types.d.ts"], 10 | "exclude": ["node_modules/", "dist/"] 11 | } 12 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | account_id = "95e065d2e3f97a1e50bae58aea71df6d" 2 | name = "template-registry" 3 | type = "webpack" 4 | workers_dev = true 5 | 6 | [site] 7 | bucket = "templates" 8 | entry-point = "workers-site" 9 | --------------------------------------------------------------------------------