From bfebca6e0e0d3cca9f733e88443751b602b92023 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Wed, 7 Jul 2021 00:28:32 +0530 Subject: [PATCH] widget static resources are now built locally --- .github/workflows/coverage.yml | 26 +- .github/workflows/linux.yml | 44 +- Cargo.lock | 134 ++++- Cargo.toml | 6 + Makefile | 45 +- browser/.appveyor.yml | 11 + browser/.gitignore | 14 + browser/.travis.yml | 69 +++ browser/Cargo.lock | 556 ++++++++++++++++++ browser/Cargo.toml | 46 ++ browser/LICENSE_APACHE | 176 ++++++ browser/LICENSE_MIT | 25 + browser/README.md | 92 +++ browser/src/lib.rs | 154 +++++ browser/static/embeded.html | 49 ++ browser/static/index.html | 124 ++++ browser/tests/web.rs | 42 ++ build.rs | 22 +- src/api/v1/auth.rs | 2 +- src/main.rs | 1 + src/widget/mod.rs | 53 +- .../widget/1476099975f2b060264c.module.wasm | Bin 67071 -> 0 bytes .../widget/858fd6c482cc75111d54.module.wasm | Bin 69618 -> 0 bytes static/widget/bundle.js | 1 - templates/widget/footer.html | 2 - templates/widget/js/const.ts | 152 +++++ templates/widget/js/fetchPoWConfig.ts | 48 ++ templates/widget/js/index.ts | 68 +++ templates/widget/js/prove.ts | 55 ++ templates/widget/js/sendToParent.ts | 24 + templates/widget/js/sendWork.ts | 42 ++ templates/widget/js/tests/const.test.ts | 50 ++ templates/widget/js/tests/setupTests.ts | 40 ++ .../widget/js/utils/genJsonPayload.test.ts | 30 + templates/widget/js/utils/genJsonPayload.ts | 23 + webpack.config.js | 28 +- 36 files changed, 2086 insertions(+), 168 deletions(-) create mode 100644 browser/.appveyor.yml create mode 100644 browser/.gitignore create mode 100644 browser/.travis.yml create mode 100644 browser/Cargo.lock create mode 100644 browser/Cargo.toml create mode 100644 browser/LICENSE_APACHE create mode 100644 browser/LICENSE_MIT create mode 100644 browser/README.md create mode 100644 browser/src/lib.rs create mode 100644 browser/static/embeded.html create mode 100644 browser/static/index.html create mode 100644 browser/tests/web.rs delete mode 100644 static/widget/1476099975f2b060264c.module.wasm delete mode 100644 static/widget/858fd6c482cc75111d54.module.wasm delete mode 100644 static/widget/bundle.js create mode 100644 templates/widget/js/const.ts create mode 100644 templates/widget/js/fetchPoWConfig.ts create mode 100644 templates/widget/js/index.ts create mode 100644 templates/widget/js/prove.ts create mode 100644 templates/widget/js/sendToParent.ts create mode 100644 templates/widget/js/sendWork.ts create mode 100644 templates/widget/js/tests/const.test.ts create mode 100644 templates/widget/js/tests/setupTests.ts create mode 100644 templates/widget/js/utils/genJsonPayload.test.ts create mode 100644 templates/widget/js/utils/genJsonPayload.ts diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 430b6cf1..b11761ed 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -65,15 +65,6 @@ jobs: - name: start smtp server run: docker run -d -p 1080:1080 -p 10025:1025 maildev/maildev --incoming-user admin --incoming-pass password - - name: Install JavaScript Dependencies - run: yarn install - - - name: Build frontend - run: yarn build - - - name: Run the tests - run: yarn test - - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: @@ -81,20 +72,15 @@ jobs: profile: minimal override: true - - name: Run migrations - uses: actions-rs/cargo@v1 - with: - command: run - args: --bin tests-migrate -- --build - env: - DATABASE_URL: postgres://postgres:password@localhost:5432/postgres + - name: Build frontend + run: make frontend + + - name: Run the frontend tests + run: make frontend-test - name: Generate coverage file if: matrix.version == '1.51.0' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') - uses: actions-rs/tarpaulin@v0.1 - with: - version: '0.15.0' - args: '-t 1200' + run: make xml-test-coverage env: DATABASE_URL: postgres://postgres:password@localhost:5432/postgres # GIT_HASH is dummy value. I guess build.rs is skipped in tarpaulin diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c926144b..e97cae64 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -38,14 +38,6 @@ jobs: image: mcaptcha/cache ports: - 6379:6379 - # smtp: - # image: maildev/maildev - # ports: - # - 10025:1025 - # - 1080:1080 - # env: - # MAILDEV_INCOMING_USER: admin - # MAILDEV_INCOMING_PASS: password steps: - uses: actions/checkout@v2 @@ -64,13 +56,7 @@ jobs: node-version: '14.x' - name: start smtp server - run: docker run -d -p 1080:1080 -p 10025:1025 maildev/maildev --incoming-user admin --incoming-pass password - - - name: Install JavaScript Dependencies - run: yarn install - - - name: Build Frontend - run: yarn build + run: docker run -d -p 1080:1080 -p 10025:1025 maildev/maildev --incoming-user admin --incoming-pass password - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 @@ -79,37 +65,27 @@ jobs: profile: minimal override: true + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Run migrations - uses: actions-rs/cargo@v1 - with: - command: run - args: --bin tests-migrate + run: make migrations env: DATABASE_URL: postgres://postgres:password@localhost:5432/postgres - - name: check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --bins --examples --tests + - name: build + run: make env: DATABASE_URL: postgres://postgres:password@localhost:5432/postgres - - name: tests - uses: actions-rs/cargo@v1 - timeout-minutes: 40 - with: - command: test - args: --all --all-features --no-fail-fast + - name: run tests + run: make test env: DATABASE_URL: postgres://postgres:password@localhost:5432/postgres - name: generate documentation if: matrix.version == 'stable' && (github.repository == 'mCaptcha/mCaptcha') - uses: actions-rs/cargo@v1 - with: - command: doc - args: --no-deps --workspace --all-features + run: make doc env: DATABASE_URL: postgres://postgres:password@localhost:5432/postgres GIT_HASH: 8e77345f1597e40c2e266cb4e6dee74888918a61 # dummy value diff --git a/Cargo.lock b/Cargo.lock index a9b09d83..2e650a69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,7 @@ dependencies = [ "actix-web-codegen 0.5.0-beta.3 (registry+https://github.com/rust-lang/crates.io-index)", "ahash", "bytes", - "cfg-if", + "cfg-if 1.0.0", "cookie", "derive_more", "either", @@ -449,7 +449,7 @@ dependencies = [ "actix-service", "base64", "bytes", - "cfg-if", + "cfg-if 1.0.0", "cookie", "derive_more", "futures-core", @@ -579,7 +579,7 @@ dependencies = [ [[package]] name = "cache-buster" version = "0.2.0" -source = "git+https://github.com/realaravinth/cache-buster#d970b7031cd90649e30dc297eb5eba74832153e8" +source = "git+https://github.com/realaravinth/cache-buster#e75ac689d2155092b90d1421ec05ec84c6917350" dependencies = [ "data-encoding", "derive_builder", @@ -600,6 +600,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -644,6 +650,16 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen", +] + [[package]] name = "const_fn" version = "0.4.8" @@ -732,7 +748,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -741,7 +757,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -751,7 +767,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -761,7 +777,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -930,7 +946,7 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -961,7 +977,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "winapi", @@ -973,7 +989,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", @@ -1136,7 +1152,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -1147,7 +1163,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.10.2+wasi-snapshot-preview1", ] @@ -1348,7 +1364,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1426,7 +1442,7 @@ checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec", "bitflags", - "cfg-if", + "cfg-if 1.0.0", "ryu", "static_assertions", ] @@ -1494,7 +1510,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1586,6 +1602,20 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "mcaptcha-browser" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "pow_sha256", + "serde 1.0.126", + "serde_derive", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", + "wee_alloc", +] + [[package]] name = "md-5" version = "0.9.1" @@ -1603,6 +1633,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "mime" version = "0.3.16" @@ -1824,7 +1860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", @@ -1867,7 +1903,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -2408,6 +2444,12 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2543,7 +2585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", "opaque-debug", @@ -2562,7 +2604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", "opaque-debug", @@ -2854,7 +2896,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "rand 0.8.4", "redox_syscall", @@ -3045,7 +3087,7 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "pin-project-lite", "tracing-core", ] @@ -3228,7 +3270,7 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -3247,6 +3289,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.74" @@ -3276,6 +3330,30 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cab416a9b970464c2882ed92d55b0c33046b08e0bdc9d59b3b718acd4e1bae8" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4543fc6cf3541ef0d98bf720104cc6bd856d7eba449fd2aa365ef4fed0e782" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "web-sys" version = "0.3.51" @@ -3305,6 +3383,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + [[package]] name = "whoami" version = "1.1.2" diff --git a/Cargo.toml b/Cargo.toml index c9702ce1..e292ad3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,12 @@ path = "./src/main.rs" name = "tests-migrate" path = "./src/tests-migrate.rs" +[workspace] +members = [ +".", +"browser", +] + [dependencies] #actix-web = "3.3.2" actix-web = "4.0.0-beta.8" diff --git a/Makefile b/Makefile index 00c28463..8933501f 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,45 @@ default: frontend cargo build -run: frontend-dev +run: frontend cargo run dev-env: cargo fetch yarn install -docs: +doc: + #yarn doc cargo doc --no-deps --workspace --all-features - -frontend-dev: - yarn build + cd browser && cargo doc --no-deps --workspace --all-features frontend: + cd browser && wasm-pack build --release + yarn install yarn build -test: migrate - cargo test +test: migrations + cd browser && wasm-pack test --release --headless --chrome + cd browser && wasm-pack test --release --headless --firefox + cargo test --all --all-features --no-fail-fast + ${MAKE} frontend-test -xml-test-coverage: migrate +frontend-test: + cd browser && wasm-pack test --release --headless --chrome + cd browser && wasm-pack test --release --headless --firefox + yarn test + +a: + echo a +b: + ${MAKE} a + +xml-test-coverage: migrations + cd browser && cargo tarpaulin -t 1200 --out Xml cargo tarpaulin -t 1200 --out Xml -coverage: migrate +coverage: migrations + cd browser && cargo tarpaulin -t 1200 --out Html cargo tarpaulin -t 1200 --out Html release: frontend @@ -35,13 +51,12 @@ clean: docker-build: docker build -t mcaptcha/mcaptcha:master -t mcaptcha/mcaptcha:latest . + docker-publish: docker-build docker push mcaptcha/mcaptcha:master docker push mcaptcha/mcaptcha:latest - - -migrate: +migrations: cargo run --bin tests-migrate help: @@ -50,10 +65,10 @@ help: @echo ' dev-env - download dependencies' @echo ' docker-build - build docker image' @echo ' docker-publish - build and publish docker image' - @echo ' docs - build documentation' - @echo ' frontend-dev - build static assets in dev mode' + @echo ' doc - build documentation' @echo ' frontend - build static assets in prod mode' - @echo ' migrate - run database migrations' + @echo ' frontend-test - run frontend tests' + @echo ' migrations - run database migrations' @echo ' run - run developer instance' @echo ' test - run unit and integration tests' @echo ' xml-coverage - build test coverage in XML for upload to codecov' diff --git a/browser/.appveyor.yml b/browser/.appveyor.yml new file mode 100644 index 00000000..50910bd6 --- /dev/null +++ b/browser/.appveyor.yml @@ -0,0 +1,11 @@ +install: + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo test --locked diff --git a/browser/.gitignore b/browser/.gitignore new file mode 100644 index 00000000..009ba71b --- /dev/null +++ b/browser/.gitignore @@ -0,0 +1,14 @@ +/target +tarpaulin-report.html +.env +.env +cobertura.xml +prod/ +node_modules/ +/static-assets/bundle +./templates/**/*.js +/static-assets/bundle/* +src/cache_buster_data.json +coverage +dist/ +docs diff --git a/browser/.travis.yml b/browser/.travis.yml new file mode 100644 index 00000000..7a913256 --- /dev/null +++ b/browser/.travis.yml @@ -0,0 +1,69 @@ +language: rust +sudo: false + +cache: cargo + +matrix: + include: + + # Builds with wasm-pack. + - rust: beta + env: RUST_BACKTRACE=1 + addons: + firefox: latest + chrome: stable + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f + script: + - cargo generate --git . --name testing + # Having a broken Cargo.toml (in that it has curlies in fields) anywhere + # in any of our parent dirs is problematic. + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - wasm-pack build + - wasm-pack test --chrome --firefox --headless + + # Builds on nightly. + - rust: nightly + env: RUST_BACKTRACE=1 + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - rustup target add wasm32-unknown-unknown + script: + - cargo generate --git . --name testing + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - cargo check + - cargo check --target wasm32-unknown-unknown + - cargo check --no-default-features + - cargo check --target wasm32-unknown-unknown --no-default-features + - cargo check --no-default-features --features console_error_panic_hook + - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook + - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" + - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" + + # Builds on beta. + - rust: beta + env: RUST_BACKTRACE=1 + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - rustup target add wasm32-unknown-unknown + script: + - cargo generate --git . --name testing + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - cargo check + - cargo check --target wasm32-unknown-unknown + - cargo check --no-default-features + - cargo check --target wasm32-unknown-unknown --no-default-features + - cargo check --no-default-features --features console_error_panic_hook + - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook + # Note: no enabling the `wee_alloc` feature here because it requires + # nightly for now. diff --git a/browser/Cargo.lock b/browser/Cargo.lock new file mode 100644 index 00000000..2156030e --- /dev/null +++ b/browser/Cargo.lock @@ -0,0 +1,556 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen", +] + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "darling" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mcaptcha-browser" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "pow_sha256", + "serde", + "serde_derive", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", + "wee_alloc", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pow_sha256" +version = "0.2.1" +source = "git+https://github.com/mcaptcha/pow_sha256#807fa7c75284f6d8d488a6f66a3a1b3301f2f412" +dependencies = [ + "bincode", + "derive_builder", + "num", + "serde", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cab416a9b970464c2882ed92d55b0c33046b08e0bdc9d59b3b718acd4e1bae8" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4543fc6cf3541ef0d98bf720104cc6bd856d7eba449fd2aa365ef4fed0e782" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/browser/Cargo.toml b/browser/Cargo.toml new file mode 100644 index 00000000..7945b5d2 --- /dev/null +++ b/browser/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "mcaptcha-browser" +version = "0.1.0" +authors = ["realaravinth "] +edition = "2018" +license = "MIT OR Apache-2.0" +repository = "https://github.com/mCaptcha/browser" +description = "mCaptcha client library for the web" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.63" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.6", optional = true } + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +# +# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. +wee_alloc = { version = "0.4.5", optional = true } + +serde = "1.0.114" +serde_derive = "1.0.114" +serde_json = "1" + +pow_sha256 = { version = "0.2.1", git = "https://github.com/mcaptcha/pow_sha256" } + +[dev-dependencies] +wasm-bindgen-test = "0.3.13" + +#[profile.release] +## Tell `rustc` to optimize for small code size. +#opt-level = "s" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false diff --git a/browser/LICENSE_APACHE b/browser/LICENSE_APACHE new file mode 100644 index 00000000..1b5ec8b7 --- /dev/null +++ b/browser/LICENSE_APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/browser/LICENSE_MIT b/browser/LICENSE_MIT new file mode 100644 index 00000000..e58e5ec5 --- /dev/null +++ b/browser/LICENSE_MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 realaravinth + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/browser/README.md b/browser/README.md new file mode 100644 index 00000000..732b3f20 --- /dev/null +++ b/browser/README.md @@ -0,0 +1,92 @@ +
+ +

PoW JavaScript library

+ +JavaScript library to generate PoW for mCaptcha + +[![0.1.0](https://img.shields.io/badge/Rust_docs-master-dea584)](https://mcaptcha.github.io/browser/rust/mcaptcha_browser/index.html) +[![0.1.0](https://img.shields.io/badge/TypeScript_docs-master-2b7489)](https://mcaptcha.github.io/browser/ts/docs/modules.html) +![Build)]() +[![dependency status](https://deps.rs/repo/github/mCaptcha/browser/status.svg)](https://deps.rs/repo/github/mCaptcha/browser) +
+[![codecov](https://codecov.io/gh/mCaptcha/browser/branch/master/graph/badge.svg)](https://codecov.io/gh/mCaptcha/browser) + +
+ +**NOTE:** wasm compilation currently requires `rustc` nightly and +wasm optimization of this library will have to be done manually at the +moment. Please refer to https://github.com/rustwasm/wasm-pack/issues/886 +for more information. + +### Optimization: + +``` +$ /path/to/wasm-opt pkg/pow_bg.wasm -o pkg/pow_bg.wasm -O --enable-mutable-globals +``` + +My `/path/to/wasm-opt` is `~/.cache/.wasm-pack/wasm-opt-4d7a65327e9363b7/wasm-opt` + +--- + +

Default documentation provided by Rust wasm:

+ +

+ Tutorial + | + Chat +

+ +Built with 🦀🕸 by The Rust and WebAssembly Working Group + + + +## About + +[**📚 Read this template tutorial! 📚**][template-docs] + +This template is designed for compiling Rust libraries into WebAssembly and +publishing the resulting package to NPM. + +Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other +templates and usages of `wasm-pack`. + +[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html +[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html + +## 🚴 Usage + +### 🐑 Use `cargo generate` to Clone this Template + +[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) + +``` +cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project +cd my-project +``` + +### 🛠️ Build with `wasm-pack build` + +``` +wasm-pack build +``` + +### 🔬 Test in Headless Browsers with `wasm-pack test` + +``` +wasm-pack test --headless --firefox +``` + +### 🎁 Publish to NPM with `wasm-pack publish` + +``` +wasm-pack publish +``` + +## 🔋 Batteries Included + +- [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating + between WebAssembly and JavaScript. +- [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) + for logging panic messages to the developer console. +- [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized + for small code size. diff --git a/browser/src/lib.rs b/browser/src/lib.rs new file mode 100644 index 00000000..ed974858 --- /dev/null +++ b/browser/src/lib.rs @@ -0,0 +1,154 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ +//! mCaptcha is a proof of work based Denaial-of-Service attack protection system. +//! This is is a WASM library that you can embed in your frontend code to protect your +//! service. +//! +//! A commercial managed solution is in the works but I'd much rather prefer +//! folks host their own instances as it will make the more decentralized and free. +//! +//! ## Workflow: +//! mCaptcha workflow in the frontend is simple. +//! 1. Call service to get a proof of work(PoW) configuration +//! 2. Call into mCaptcha to get PoW +//! 3. Send PoW to mCaptcha service +//! 4. If proof is valid, the service will return a token to the client +//! 5. Submit token to your backend along with your app data(if any) +//! 6. In backend, validate client's token with mCaptcha service +//! +//! ## Example: +//! +//! generate proof-of-work +//! ```rust +//! fn main() { +//! use mcaptcha_browser::*; +//! use pow_sha256::*; +//! +//! +//! // salt using which PoW should be computed +//! const SALT: &str = "yrandomsaltisnotlongenoug"; +//! // one-time phrase over which PoW should be computed +//! const PHRASE: &str = "ironmansucks"; +//! // and the difficulty factor +//! const DIFFICULTY: u32 = 1000; +//! +//! // currently gen_pow() returns a JSON formated string to better communicate +//! // with JavaScript. See [PoW][pow_sha256::PoW] for schema +//! let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY); +//! +//! +//! let work: Work = serde_json::from_str(&serialised_work).unwrap(); +//! +//! let work = PoWBuilder::default() +//! .result(work.result) +//! .nonce(work.nonce) +//! .build() +//! .unwrap(); +//! +//! let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap(); +//! assert!(config.is_valid_proof(&work, &PHRASE.to_string())); +//! assert!(config.is_sufficient_difficulty(&work, DIFFICULTY)); +//! } +//! ``` + +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global +// allocator. +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +use pow_sha256::{ConfigBuilder, PoW}; + +#[derive(Deserialize, Serialize)] +pub struct Work { + pub result: String, + pub nonce: u64, +} + +impl From> for Work { + fn from(p: PoW) -> Self { + Work { + result: p.result, + nonce: p.nonce, + } + } +} + +/// generate proof-of-work +/// ```rust +/// fn main() { +/// use mcaptcha_browser::*; +/// use pow_sha256::*; +/// +/// +/// // salt using which PoW should be computed +/// const SALT: &str = "yrandomsaltisnotlongenoug"; +/// // one-time phrase over which PoW should be computed +/// const PHRASE: &str = "ironmansucks"; +/// // and the difficulty factor +/// const DIFFICULTY: u32 = 1000; +/// +/// // currently gen_pow() returns a JSON formated string to better communicate +/// // with JavaScript. See [PoW][pow_sha256::PoW] for schema +/// let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY); +/// +/// +/// let work: Work = serde_json::from_str(&serialised_work).unwrap(); +/// +/// let work = PoWBuilder::default() +/// .result(work.result) +/// .nonce(work.nonce) +/// .build() +/// .unwrap(); +/// +/// let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap(); +/// assert!(config.is_valid_proof(&work, &PHRASE.to_string())); +/// assert!(config.is_sufficient_difficulty(&work, DIFFICULTY)); +/// } +/// ``` +#[wasm_bindgen] +pub fn gen_pow(salt: String, phrase: String, difficulty_factor: u32) -> String { + let config = ConfigBuilder::default().salt(salt).build().unwrap(); + + let work = config.prove_work(&phrase, difficulty_factor).unwrap(); + let work: Work = work.into(); + + let payload = serde_json::to_string(&work).unwrap(); + payload +} + +#[cfg(test)] +mod tests { + use super::*; + use pow_sha256::PoWBuilder; + + const SALT: &str = "yrandomsaltisnotlongenoug"; + const PHRASE: &str = "ironmansucks"; + const DIFFICULTY: u32 = 1000; + #[test] + fn it_works() { + let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY); + let work: Work = serde_json::from_str(&serialised_work).unwrap(); + + let work = PoWBuilder::default() + .result(work.result) + .nonce(work.nonce) + .build() + .unwrap(); + + let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap(); + assert!(config.is_valid_proof(&work, &PHRASE.to_string())); + assert!(config.is_sufficient_difficulty(&work, DIFFICULTY)); + } +} diff --git a/browser/static/embeded.html b/browser/static/embeded.html new file mode 100644 index 00000000..5e937f3f --- /dev/null +++ b/browser/static/embeded.html @@ -0,0 +1,49 @@ + + + + + + mcaptcha demo form + + +
+ +
+
+
+ + +
+ +
+ +
+ +
+ + diff --git a/browser/static/index.html b/browser/static/index.html new file mode 100644 index 00000000..0b3475db --- /dev/null +++ b/browser/static/index.html @@ -0,0 +1,124 @@ + + + + + + mCaptcha + + +
+ + + +
+ + + + diff --git a/browser/tests/web.rs b/browser/tests/web.rs new file mode 100644 index 00000000..b16843df --- /dev/null +++ b/browser/tests/web.rs @@ -0,0 +1,42 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +//! Test suite for the Web and headless browsers. + +#![cfg(target_arch = "wasm32")] + +extern crate wasm_bindgen_test; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn pow_generation_works() { + use mcaptcha_browser::*; + use pow_sha256::*; + + const SALT: &str = "yrandomsaltisnotlongenoug"; + const PHRASE: &str = "ironmansucks"; + const DIFFICULTY: u32 = 1000; + let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY); + + let work: Work = serde_json::from_str(&serialised_work).unwrap(); + + let work = PoWBuilder::default() + .result(work.result) + .nonce(work.nonce) + .build() + .unwrap(); + + let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap(); + assert!(config.is_valid_proof(&work, &PHRASE.to_string())); + assert!(config.is_sufficient_difficulty(&work, DIFFICULTY)); +} diff --git a/build.rs b/build.rs index b4cf8613..87dcb943 100644 --- a/build.rs +++ b/build.rs @@ -37,20 +37,24 @@ fn main() { } fn cache_bust() { - let types = vec![ - mime::IMAGE_PNG, - mime::IMAGE_SVG, - mime::IMAGE_JPEG, - mime::IMAGE_GIF, - mime::APPLICATION_JAVASCRIPT, - mime::TEXT_CSS, - ]; + // until APPLICATION_WASM gets added to mime crate + // PR: https://github.com/hyperium/mime/pull/138 + // let types = vec![ + // mime::IMAGE_PNG, + // mime::IMAGE_SVG, + // mime::IMAGE_JPEG, + // mime::IMAGE_GIF, + // mime::APPLICATION_JAVASCRIPT, + // mime::TEXT_CSS, + // ]; + + let no_hash = vec!["bundle/6b88f6ccf97567b46745.module.wasm"]; let config = BusterBuilder::default() .source("./static/cache") .result("./assets") - .mime_types(types) .copy(true) + .no_hash(no_hash) .follow_links(true) .build() .unwrap(); diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index 508d1b57..64d0dc81 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -226,6 +226,6 @@ async fn signout(id: Identity) -> impl Responder { id.forget(); } HttpResponse::Found() - .append_header((header::LOCATION, "/login")) + .append_header((header::LOCATION, crate::PAGES.auth.login)) .finish() } diff --git a/src/main.rs b/src/main.rs index f03a1776..27b269cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,7 @@ lazy_static! { FILES.get("./static/cache/bundle/bundle.css").unwrap(); pub static ref MOBILE_CSS: &'static str = FILES.get("./static/cache/bundle/mobile.css").unwrap(); + pub static ref VERIFICATIN_WIDGET_JS: &'static str = FILES.get("./static/cache/bundle/verificationWidget.js").unwrap(); pub static ref VERIFICATIN_WIDGET_CSS: &'static str = diff --git a/src/widget/mod.rs b/src/widget/mod.rs index ce81ab74..e766d466 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -14,13 +14,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -use std::borrow::Cow; - -use actix_web::body::Body; -use actix_web::{get, http::header, web, HttpResponse, Responder}; +//! User facing CAPTCHA widget +use actix_web::{web, HttpResponse, Responder}; use lazy_static::lazy_static; -use mime_guess::from_path; -use rust_embed::RustEmbed; use sailfish::TemplateOnce; use crate::errors::*; @@ -30,16 +26,12 @@ pub const WIDGET_ROUTES: routes::Widget = routes::Widget::new(); pub mod routes { pub struct Widget { pub verification_widget: &'static str, - pub js: &'static str, - pub wasm: &'static str, } impl Widget { pub const fn new() -> Self { Widget { verification_widget: "/widget", - js: "/widget/bundle.js", - wasm: "/widget/1476099975f2b060264c.module.wasm", } } } @@ -69,37 +61,9 @@ async fn show_widget() -> PageResult { .body(&*INDEX_PAGE)) } -#[derive(RustEmbed)] -#[folder = "static/widget/"] -struct WidgetAssets; - -fn handle_widget_assets(path: &str) -> HttpResponse { - match WidgetAssets::get(path) { - Some(content) => { - let body: Body = match content { - Cow::Borrowed(bytes) => bytes.into(), - Cow::Owned(bytes) => bytes.into(), - }; - - HttpResponse::Ok() - .insert_header(header::CacheControl(vec![ - header::CacheDirective::MaxAge(crate::CACHE_AGE), - ])) - .content_type(from_path(path).first_or_octet_stream().as_ref()) - .body(body) - } - None => HttpResponse::NotFound().body("404 Not Found"), - } -} - -#[get("/widget/{_:.*}")] -pub async fn widget_assets(path: web::Path) -> impl Responder { - handle_widget_assets(&path) -} - +/// widget services pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(show_widget); - cfg.service(widget_assets); } #[cfg(test)] @@ -112,17 +76,6 @@ mod test { #[actix_rt::test] async fn captcha_widget_route_works() { let mut app = get_app!().await; - // let list_sitekey_resp = test::call_service( - // &mut app, - // test::TestRequest::get() - // .uri(crate::WIDGET_ROUTES.verification_widget) - // .to_request(), - // ) - // .await; - // assert_eq!(list_sitekey_resp.status(), StatusCode::OK); - get_works!(app, crate::WIDGET_ROUTES.verification_widget); - get_works!(app, crate::WIDGET_ROUTES.js); - get_works!(app, crate::WIDGET_ROUTES.wasm); } } diff --git a/static/widget/1476099975f2b060264c.module.wasm b/static/widget/1476099975f2b060264c.module.wasm deleted file mode 100644 index 016d531f022df2a5ea8d4ef47c43a96c94af2afc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67071 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

#&xCv@Rqdr9Ruvnw*vT>YJ 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 O(lCQ2PP`)X`~M%Z4Z5`e diff --git a/static/widget/858fd6c482cc75111d54.module.wasm b/static/widget/858fd6c482cc75111d54.module.wasm deleted file mode 100644 index 183cc571ad6c85de7d763b56b78087934b78b658..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69618 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 diff --git a/static/widget/bundle.js b/static/widget/bundle.js deleted file mode 100644 index 3bbfe9a0..00000000 --- a/static/widget/bundle.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";var e,n,t,r,o,i={180:(e,n,t)=>{t.d(n,{Nj:()=>r,Z6:()=>o,yY:()=>i,yM:()=>a});var r=function(){var e;return function(){if(null==e&&null==(e=new URL(window.location.href).searchParams.get("sitekey")))throw new Error("Define sitekey in query parameter");return e}()},o={getConfig:"/api/v1/pow/config",verififyPoW:"/api/v1/pow/verify"},i=function(){var e;return function(){if(null==e&&null==(e=document.getElementById("widget__verification-checkbox")))throw new Error("mCaptcha button not found)");return e}()},a=function(){var e,n,t,r,o=function(e){return e.style.display="block"},i=function(e){return e.style.display="none"},a=function(){if(null==e){if(null==(e=document.querySelector(".widget__verification-text--before")))throw new Error("before element not found)");return e}},c=function(){if(null==n&&null==(n=document.querySelector(".widget__verification-text--after")))throw new Error("after element not found)");return n},u=function(){if(null==r&&null==(r=document.querySelector(".widget__verification-text--error")))throw new Error("before error not found)");return r},l=function(){if(null==t&&null==(t=document.querySelector(".widget__verification-text--during")))throw new Error("before during not found)");return t};return{before:function(){o(a()),i(c()),i(l()),i(u())},after:function(){i(a()),o(c()),i(l()),i(u())},during:function(){i(a()),i(c()),o(l()),i(u())},error:function(){i(a()),i(c()),i(l()),o(u())}}}},731:(e,n,t)=>{t.d(n,{Z:()=>i});var r=t(525),o=t(180);const i=function(){return e=void 0,n=void 0,i=function(){var e,n,t;return function(e,n){var t,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(t)throw new TypeError("Generator is already executing.");for(;a;)try{if(t=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{t.a(e,(async e=>{var n=t(720),r=t(731),o=t(792),i=t(243),a=t(180),c=e([n]);n=(c.then?await c:c)[0];var u=!1,l=function(e){return t=void 0,c=void 0,s=function(){var t,c,l,s;return function(e,n){var t,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(t)throw new TypeError("Generator is already executing.");for(;a;)try{if(t=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{t.a(e,(async e=>{t.d(n,{Z:()=>a});var r=t(838),o=t(180),i=e([r]);r=(i.then?await i:i)[0];const a=function(e){return n=void 0,t=void 0,a=function(){var n,t;return function(e,n){var t,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(t)throw new TypeError("Generator is already executing.");for(;a;)try{if(t=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{t.d(n,{Z:()=>r});const r=function(e){window.parent.postMessage(e,"*")}},792:(e,n,t)=>{t.d(n,{Z:()=>i});var r=t(525),o=t(180);const i=function(e){return n=void 0,t=void 0,a=function(){var n,t,i,a;return function(e,n){var t,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(t)throw new TypeError("Generator is already executing.");for(;a;)try{if(t=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{t.d(n,{Z:()=>r});const r=function(e){return{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}}},838:(e,n,t)=>{t.a(e,(async r=>{t.d(n,{I:()=>y});var o=t(716);e=t.hmd(e);var i=r([o]);o=(i.then?await i:i)[0];let a=0,c=null;function u(){return null!==c&&c.buffer===o.memory.buffer||(c=new Uint8Array(o.memory.buffer)),c}let l=new("undefined"==typeof TextEncoder?(0,e.require)("util").TextEncoder:TextEncoder)("utf-8");const s="function"==typeof l.encodeInto?function(e,n){return l.encodeInto(e,n)}:function(e,n){const t=l.encode(e);return n.set(t),{read:e.length,written:t.length}};function f(e,n,t){if(void 0===t){const t=l.encode(e),r=n(t.length);return u().subarray(r,r+t.length).set(t),a=t.length,r}let r=e.length,o=n(r);const i=u();let c=0;for(;c127)break;i[o+c]=n}if(c!==r){0!==c&&(e=e.slice(c)),o=t(o,r,r=c+3*e.length);const n=u().subarray(o+c,o+r);c+=s(e,n).written}return a=c,o}let d=null;function p(){return null!==d&&d.buffer===o.memory.buffer||(d=new Int32Array(o.memory.buffer)),d}let h=new("undefined"==typeof TextDecoder?(0,e.require)("util").TextDecoder:TextDecoder)("utf-8",{ignoreBOM:!0,fatal:!0});function y(e,n,t){try{const w=o.__wbindgen_add_to_stack_pointer(-16);var r=f(e,o.__wbindgen_malloc,o.__wbindgen_realloc),i=a,c=f(n,o.__wbindgen_malloc,o.__wbindgen_realloc),l=a;o.gen_pow(w,r,i,c,l,t);var s=p()[w/4+0],d=p()[w/4+1];return y=s,b=d,h.decode(u().subarray(y,y+b))}finally{o.__wbindgen_add_to_stack_pointer(16),o.__wbindgen_free(s,d)}var y,b}h.decode()}))},716:(e,n,t)=>{e.exports=t.v(n,e.id,"858fd6c482cc75111d54")}},a={};function c(e){var n=a[e];if(void 0!==n)return n.exports;var t=a[e]={id:e,loaded:!1,exports:{}};return i[e](t,t.exports,c),t.loaded=!0,t.exports}e="function"==typeof Symbol?Symbol("webpack then"):"__webpack_then__",n="function"==typeof Symbol?Symbol("webpack exports"):"__webpack_exports__",t=e=>{e&&(e.forEach((e=>e.r--)),e.forEach((e=>e.r--?e.r++:e())))},r=e=>!--e.r&&e(),o=(e,n)=>e?e.push(n):r(n),c.a=(i,a,c)=>{var u,l,s,f=c&&[],d=i.exports,p=!0,h=!1,y=(n,t,r)=>{h||(h=!0,t.r+=n.length,n.map(((n,o)=>n[e](t,r))),h=!1)},b=new Promise(((e,n)=>{s=n,l=()=>(e(d),t(f),f=0)}));b[n]=d,b[e]=(e,n)=>{if(p)return r(e);u&&y(u,e,n),o(f,e),b.catch(n)},i.exports=b,a((i=>{if(!i)return l();var a,c;u=(i=>i.map((i=>{if(null!==i&&"object"==typeof i){if(i[e])return i;if(i.then){var a=[];i.then((e=>{c[n]=e,t(a),a=0}));var c={[e]:(e,n)=>(o(a,e),i.catch(n))};return c}}return{[e]:e=>r(e),[n]:i}})))(i);var s=new Promise(((e,t)=>{(a=()=>e(c=u.map((e=>e[n])))).r=0,y(u,a,t)}));return a.r?s:c})).then(l,s),p=!1},c.d=(e,n)=>{for(var t in n)c.o(n,t)&&!c.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:()=>{throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),c.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e;c.g.importScripts&&(e=c.g.location+"");var n=c.g.document;if(!e&&n&&(n.currentScript&&(e=n.currentScript.src),!e)){var t=n.getElementsByTagName("script");t.length&&(e=t[t.length-1].src)}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),c.p=e})(),c.v=(e,n,t,r)=>{var o=fetch(c.p+""+t+".module.wasm");return"function"==typeof WebAssembly.instantiateStreaming?WebAssembly.instantiateStreaming(o,r).then((n=>Object.assign(e,n.instance.exports))):o.then((e=>e.arrayBuffer())).then((e=>WebAssembly.instantiate(e,r))).then((n=>Object.assign(e,n.instance.exports)))},c(404)})(); \ No newline at end of file diff --git a/templates/widget/footer.html b/templates/widget/footer.html index ca99118b..ff0f908f 100644 --- a/templates/widget/footer.html +++ b/templates/widget/footer.html @@ -5,7 +5,5 @@ href="<.= &*crate::VERIFICATIN_WIDGET_CSS .>" /> - - diff --git a/templates/widget/js/const.ts b/templates/widget/js/const.ts new file mode 100644 index 00000000..0dbf7469 --- /dev/null +++ b/templates/widget/js/const.ts @@ -0,0 +1,152 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +/** mcaptcha checkbox ID **/ +export const btnId = 'widget__verification-checkbox'; + +/** get sitekey */ +export const sitekey = () => { + let sitekey; + return (() => { + if (sitekey === null || sitekey === undefined) { + sitekey = new URL(window.location.href).searchParams.get('sitekey'); + if (sitekey === null || sitekey === undefined) { + throw new Error(`Define sitekey in query parameter`); + } + } + return sitekey; + })(); +}; + +/** mCaptcha API routes */ +export const ROUTES = (() => { + const getConfig = '/api/v1/pow/config'; + const verififyPoW = '/api/v1/pow/verify'; + + return { + /** get URL to fetch PoW configuration */ + getConfig, + /** get URL to verify PoW*/ + verififyPoW, + }; +})(); + +/** get mCaptcha verifify checkbox button */ +export const btn = () => { + let btn; + return (() => { + if (btn === null || btn === undefined) { + btn = document.getElementById(btnId); + if (btn === null || btn === undefined) { + throw new Error(`mCaptcha button not found)`); + } + } + return btn; + })(); +}; + +export const messageText = () => { + let beforeClass = 'widget__verification-text--before'; + let duringClass = 'widget__verification-text--during'; + let errorClass = 'widget__verification-text--error'; + let afterClass = 'widget__verification-text--after'; + + let before: HTMLElement; + let after: HTMLElement; + let during: HTMLElement; + let error: HTMLElement; + + /** runner fn to display HTMLElement **/ + const showMsg = (e: HTMLElement) => (e.style.display = 'block'); + /** runner fn to hide HTMLElement **/ + const hideMsg = (e: HTMLElement) => (e.style.display = 'none'); + + /** lazy init and get before elementt **/ + const getBefore = () => { + if (before === null || before === undefined) { + before = document.querySelector(`.${beforeClass}`); + if (before === null || before === undefined) { + throw new Error(`before element not found)`); + } + return before; + } + }; + + /** lazy init and get after elementt **/ + const getAfter = () => { + if (after === null || after === undefined) { + after = document.querySelector(`.${afterClass}`); + if (after === null || after === undefined) { + throw new Error(`after element not found)`); + } + } + + return after; + }; + + /** lazy init and get error elementt **/ + const getError = () => { + if (error === null || error === undefined) { + error = document.querySelector(`.${errorClass}`); + if (error === null || error === undefined) { + throw new Error(`before error not found)`); + } + } + return error; + }; + + /** lazy init and get during elementt **/ + const getDuring = () => { + if (during === null || during === undefined) { + during = document.querySelector(`.${duringClass}`); + if (during === null || during === undefined) { + throw new Error(`before during not found)`); + } + } + + return during; + }; + return { + /** display "before" message **/ + before: () => { + showMsg(getBefore()); + hideMsg(getAfter()); + hideMsg(getDuring()); + hideMsg(getError()); + }, + + /** display "after" message **/ + after: () => { + hideMsg(getBefore()); + showMsg(getAfter()); + hideMsg(getDuring()); + hideMsg(getError()); + }, + + /** display "during" message **/ + during: () => { + hideMsg(getBefore()); + hideMsg(getAfter()); + showMsg(getDuring()); + hideMsg(getError()); + }, + + /** display "error" message **/ + error: () => { + hideMsg(getBefore()); + hideMsg(getAfter()); + hideMsg(getDuring()); + showMsg(getError()); + }, + }; +}; + +export const inputId = 'mcaptcha-response'; diff --git a/templates/widget/js/fetchPoWConfig.ts b/templates/widget/js/fetchPoWConfig.ts new file mode 100644 index 00000000..1a3ae779 --- /dev/null +++ b/templates/widget/js/fetchPoWConfig.ts @@ -0,0 +1,48 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +import genJsonPayload from './utils/genJsonPayload'; +import * as CONST from './const'; + +type GetConfigPayload = { + key: string; +}; + +export type PoWConfig = { + string: string; + difficulty_factor: number; + salt: string; +}; + +/** + * fetch proof-of-work configuration + * @returns {PoWConfig} pow config + * */ +export const fetchPoWConfig = async () => { + try { + const payload: GetConfigPayload = { + key: CONST.sitekey(), + }; + + const res = await fetch(CONST.ROUTES.getConfig, genJsonPayload(payload)); + if (res.ok) { + const config: PoWConfig = await res.json(); + return config; + } else { + const err = await res.json(); + throw new Error(err); + } + } catch (err) { + throw err; + } +}; + +export default fetchPoWConfig; diff --git a/templates/widget/js/index.ts b/templates/widget/js/index.ts new file mode 100644 index 00000000..071409fe --- /dev/null +++ b/templates/widget/js/index.ts @@ -0,0 +1,68 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +import prove from './prove'; +import fetchPoWConfig from './fetchPoWConfig'; +import sendWork from './sendWork'; +import sendToParent from './sendToParent'; +import * as CONST from './const'; + +import '../main.scss'; + +let LOCK = false; + +/** add mcaptcha widget element to DOM */ +export const registerVerificationEventHandler = () => { + const verificationContainer = ( + document.querySelector('.widget__verification-container') + ); + verificationContainer.style.display = 'flex'; + CONST.btn().addEventListener('click', e => solveCaptchaRunner(e)); +}; + +export const solveCaptchaRunner = async (e: Event) => { + if (LOCK) { + e.preventDefault(); + return; + } + + try { + LOCK = true; + if (CONST.btn().checked == false) { + CONST.messageText().before(); + LOCK = false; + return; + } + e.preventDefault(); + // steps: + + // 1. show during + CONST.messageText().during(); + // 1. get config + const config = await fetchPoWConfig(); + // 2. prove work + const proof = await prove(config); + // 3. submit work + const token = await sendWork(proof); + // 4. send token + sendToParent(token); + // 5. mark checkbox checked + CONST.btn().checked = true; + CONST.messageText().after(); + LOCK = false; + } catch (e) { + CONST.messageText().error(); + console.error(e); + LOCK = false; + } +}; + +registerVerificationEventHandler(); diff --git a/templates/widget/js/prove.ts b/templates/widget/js/prove.ts new file mode 100644 index 00000000..3d4c1553 --- /dev/null +++ b/templates/widget/js/prove.ts @@ -0,0 +1,55 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +import {gen_pow} from '../../../browser/pkg/index'; +import {PoWConfig} from './fetchPoWConfig'; +import * as CONST from './const'; + +export type Work = { + result: string; + nonce: number; + string: string; + key: string; +}; + +type WasmWork = { + result: string; + nonce: number; +}; + +/** + * proove work + * @param {PoWConfig} config - the proof-of-work configuration using which + * work needs to be computed + * */ +const prove = async (config: PoWConfig) => { + try { + const proofString = gen_pow( + config.salt, + config.string, + config.difficulty_factor, + ); + const proof: WasmWork = JSON.parse(proofString); + + const res: Work = { + key: CONST.sitekey(), + string: config.string, + nonce: proof.nonce, + result: proof.result, + }; + + return res; + } catch (err) { + throw err; + } +}; + +export default prove; diff --git a/templates/widget/js/sendToParent.ts b/templates/widget/js/sendToParent.ts new file mode 100644 index 00000000..bfdf6c19 --- /dev/null +++ b/templates/widget/js/sendToParent.ts @@ -0,0 +1,24 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ +import {Token} from './sendWork'; + +/** + * send pow validation token as message to parant of the iframe + * @param {Token} token: token received from mCaptcha service + * upon successful PoW validation + * */ +export const sendToParent = (token: Token) => { + window.parent.postMessage(token, '*'); + // TODO set origin. Make parent send origin as query parameter + // or as a message to iframe +}; + +export default sendToParent; diff --git a/templates/widget/js/sendWork.ts b/templates/widget/js/sendWork.ts new file mode 100644 index 00000000..2c7f2c96 --- /dev/null +++ b/templates/widget/js/sendWork.ts @@ -0,0 +1,42 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +import genJsonPayload from './utils/genJsonPayload'; +import * as CONST from './const'; +import {Work} from './prove'; + +export type Token = { + token: string; +}; + +export const sendWork = async (payload: Work) => { + try { + const res = await fetch(CONST.ROUTES.verififyPoW, genJsonPayload(payload)); + if (res.ok) { + console.debug('work verified'); + const token: Token = await res.json(); + console.debug(`token ${token.token}`); + return token; + } else { + const err = await res.json(); + console.error(`error: ${err.error}`); + throw new Error(err); + } + } catch (err) { + CONST.messageText().error(); + console.error(err); + await new Promise(r => setTimeout(r, 1000)); + window.location.reload(); + throw err; + } +}; + +export default sendWork; diff --git a/templates/widget/js/tests/const.test.ts b/templates/widget/js/tests/const.test.ts new file mode 100644 index 00000000..66521349 --- /dev/null +++ b/templates/widget/js/tests/const.test.ts @@ -0,0 +1,50 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ +import * as CONST from '../const'; + +import {getBaseHtml, sitekey, checkbox} from './setupTests'; +import * as TESTElements from './setupTests'; + +it('const works', () => { + const body = document.querySelector('body'); + const container = getBaseHtml(); + body.appendChild(container); + expect(CONST.sitekey()).toBe(sitekey); + expect(CONST.btn()).toBe(checkbox); + + // display after + CONST.messageText().after(); + expect(TESTElements.afterMsg.style.display).toBe('block'); + expect(TESTElements.beforeMsg.style.display).toBe('none'); + expect(TESTElements.duringMsg.style.display).toBe('none'); + expect(TESTElements.errorMsg.style.display).toBe('none'); + + // display before + CONST.messageText().before(); + expect(TESTElements.afterMsg.style.display).toBe('none'); + expect(TESTElements.beforeMsg.style.display).toBe('block'); + expect(TESTElements.duringMsg.style.display).toBe('none'); + expect(TESTElements.errorMsg.style.display).toBe('none'); + + // display during + CONST.messageText().during(); + expect(TESTElements.afterMsg.style.display).toBe('none'); + expect(TESTElements.beforeMsg.style.display).toBe('none'); + expect(TESTElements.duringMsg.style.display).toBe('block'); + expect(TESTElements.errorMsg.style.display).toBe('none'); + + // display error + CONST.messageText().error(); + expect(TESTElements.afterMsg.style.display).toBe('none'); + expect(TESTElements.beforeMsg.style.display).toBe('none'); + expect(TESTElements.duringMsg.style.display).toBe('none'); + expect(TESTElements.errorMsg.style.display).toBe('block'); +}); diff --git a/templates/widget/js/tests/setupTests.ts b/templates/widget/js/tests/setupTests.ts new file mode 100644 index 00000000..c61ff110 --- /dev/null +++ b/templates/widget/js/tests/setupTests.ts @@ -0,0 +1,40 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ +import * as CONST from '../const'; + +export const sitekey = 'imbatman'; + +export const checkbox = document.createElement('input'); +checkbox.type = 'checkbox'; +checkbox.id = CONST.btnId; + +const getMessages = (state: string) => { + const msg = document.createElement('span'); + msg.className = `widget__verification-text--${state}`; + return msg; +}; + +export const beforeMsg = getMessages('before'); +export const afterMsg = getMessages('after'); +export const duringMsg = getMessages('during'); +export const errorMsg = getMessages('error'); + +/** get base HTML with empty mCaptcha container */ +export const getBaseHtml = () => { + const form = document.createElement('form'); + form.appendChild(checkbox); + form.appendChild(beforeMsg); + form.appendChild(duringMsg); + form.appendChild(afterMsg); + form.appendChild(errorMsg); + + return form; +}; diff --git a/templates/widget/js/utils/genJsonPayload.test.ts b/templates/widget/js/utils/genJsonPayload.test.ts new file mode 100644 index 00000000..0524222f --- /dev/null +++ b/templates/widget/js/utils/genJsonPayload.test.ts @@ -0,0 +1,30 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +import genJsonPayload from './genJsonPayload'; + +'use strict'; + +const payload = { + username: 'Jhon', +}; + +const value = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), +}; + +it('getFromUrl workds', () => { + expect(genJsonPayload(payload)).toEqual(value); +}); diff --git a/templates/widget/js/utils/genJsonPayload.ts b/templates/widget/js/utils/genJsonPayload.ts new file mode 100644 index 00000000..8da5220f --- /dev/null +++ b/templates/widget/js/utils/genJsonPayload.ts @@ -0,0 +1,23 @@ +/* + * mCaptcha is a PoW based DoS protection software. + * This is the frontend web component of the mCaptcha system + * Copyright © 2021 Aravinth Manivnanan . + * + * Use of this source code is governed by Apache 2.0 or MIT license. + * You shoud have received a copy of MIT and Apache 2.0 along with + * this program. If not, see for + * MIT or for Apache. + */ + +const genJsonPayload = (payload: any) => { + const value = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }; + return value; +}; + +export default genJsonPayload; diff --git a/webpack.config.js b/webpack.config.js index 470c0251..222a7a14 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,7 @@ module.exports = { entry: { bundle: './templates/index.ts', mobile: './templates/mobile.ts', - verificationWidget: './templates/widget/index.ts', + verificationWidget: './templates/widget/js/index.ts', }, output: { filename: '[name].js', @@ -45,10 +45,10 @@ module.exports = { plugins: [ new MiniCssExtractPlugin(), -// new WasmPackPlugin({ -// crateDirectory: __dirname, -// outName: "pow.wasm", -// }), + // new WasmPackPlugin({ + // crateDirectory: __dirname, + // outName: "pow.wasm", + // }), ], optimization: { minimizer: [ @@ -57,13 +57,13 @@ module.exports = { new CssMinimizerPlugin(), ], }, -// experiments: { -// // executeModule: true, -// // outputModule: true, -// //syncWebAssembly: true, -// // topLevelAwait: true, -// asyncWebAssembly: true, -// // layers: true, -// // lazyCompilation: true, -// }, + experiments: { + // executeModule: true, + // outputModule: true, + //syncWebAssembly: true, + // topLevelAwait: true, + asyncWebAssembly: true, + // layers: true, + // lazyCompilation: true, + }, };