commit c8965f7aee1067d5f21cece49d6d8f43e62d5e6b Author: didier amyot Date: Fri Sep 1 09:27:56 2023 -0400 Moved files here diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/import_map.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..174c75e --- /dev/null +++ b/public/index.html @@ -0,0 +1,21 @@ + + + + + + Hello Bulma! + + + +
+
+

+ Hello World +

+

+ My first website with Bulma! +

+
+
+ + \ No newline at end of file diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..936a48d --- /dev/null +++ b/src/app.ts @@ -0,0 +1,51 @@ +import { Router, Context, Request } from "https://deno.land/x/oak/mod.ts"; +import { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts"; +import { createBundle } from "./bundle.ts"; +const clientBundle = createBundle( + new URL("./client.js", import.meta.url), + new URL("../import_map.json", import.meta.url), + false, +); + +export function buildApp(pool: Pool): Router{ + const router = new Router(); + function getHeaders(request: Request): Record{ + const ret: Record = {}; + for (const [k, v] of request.headers.entries()) { + ret[k] = v; + } + return ret; + } + + router.post("/api/v1/spy", async (ctx: Context) => { + const payload = await ctx.request.body().value; + payload.request = { + ip: ctx.request.ip, + ips: ctx.request.ips, + url: ctx.request.url.toString(), + date: Date.now(), + method: ctx.request.method, + headers: getHeaders(ctx.request), + }; + const client = await pool.connect(); + try{ + const query ="INSERT INTO blog_statistics(raw_data) VALUES ($1)"; + await client.queryArray(query, [payload]); + ctx.response.body = '{}'; + }finally { + client.release() + } + }); + + router.get("/stats", (ctx:Context) => { + ctx.response.body = 'hello world' + }); + + router.get("/toto.js", async (ctx: Context) => { + + ctx.response.body = await clientBundle; + }); + + return router; + +} \ No newline at end of file diff --git a/src/bundle.ts b/src/bundle.ts new file mode 100644 index 0000000..fefe15d --- /dev/null +++ b/src/bundle.ts @@ -0,0 +1,70 @@ +/** + * This part of the code is based on the way `fresh` is bundling the code. + * See https://github.com/denoland/fresh/blob/main/src/server/bundle.ts + */ + +// @deno-types="https://deno.land/x/esbuild@v0.14.51/mod.d.ts" +import * as esbuildNative from "https://deno.land/x/esbuild@v0.14.51/mod.js"; +import * as esbuildWasm from "https://deno.land/x/esbuild@v0.14.51/wasm.js"; +import { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.5.2/mod.ts"; + +const esbuild: typeof esbuildWasm = Deno.run === undefined + ? esbuildWasm + : esbuildNative; + +let esbuildInitialized = false; +async function verifyEsbuildInitialized() { + if (esbuildInitialized) { + return; + } + console.log(new Date(), "start initializing esbuild"); + if (Deno.run === undefined) { + const wasmURL = new URL("./esbuild@v0.14.51.wasm", import.meta.url).href; + const resp = await fetch(wasmURL); + const wasm = new Response(resp.body, { + headers: { "Content-Type": "application/wasm" }, + }); + const wasmModule = await WebAssembly.compileStreaming(wasm); + await esbuild.initialize({ + wasmModule, + worker: false, + }); + } else { + await esbuild.initialize({}); + } + esbuildInitialized = true; + console.log(new Date(), "esbuild initialized"); +} + +export async function createBundle( + entryPoint: URL, + importMapURL: URL, + isDev?: boolean, +) { + await verifyEsbuildInitialized(); + console.log(new Date(), "Bundling", entryPoint.href); + const absWorkingDir = Deno.cwd(); + const res = await esbuild.build({ + bundle: true, + entryPoints: [entryPoint.href], + platform: "neutral", + treeShaking: true, + minify: !isDev, + sourcemap: isDev ? "inline" : false, + outfile: "", + absWorkingDir, + outdir: ".", + target: ["chrome99", "firefox99", "safari15"], + write: false, + format: "esm", + plugins: [denoPlugin({ importMapURL })], + splitting: false, + jsx: "automatic", + jsxImportSource: "react", + }); + if (res.errors.length > 0) { + throw res.errors; + } + console.log(new Date(), "Finished bundling", entryPoint.href); + return res.outputFiles[0].text; +} \ No newline at end of file diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..25fc2a9 --- /dev/null +++ b/src/client.js @@ -0,0 +1,90 @@ +(function(window){ + const SPY_KEY = 'spy-key'; + + function randomWord(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for(let i = 0; i < length; i++){ + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; + } + + + function getSpyKey(){ + return window.localStorage.getItem(SPY_KEY); + } + + function generateSpyKey(){ + const spyKey = randomWord(80); + window.localStorage.setItem(SPY_KEY, spyKey); + return spyKey; + } + + function getOrCreateSpyKey(){ + return getSpyKey() || generateSpyKey() + } + + async function canvasFingerprint() { + + let canvasElement = document.createElement('canvas'); + let canvasContext = canvasElement.getContext('2d'); + canvasContext.fillStyle = 'rgba(0,0,0,0.5)'; + + canvasContext.fillText('Hello mon coco ☀️ 🙋🏽', 0, 100) + + canvasContext.fillText('d\ne\tdkd', 50, 50) + canvasContext.beginPath() + canvasContext.arc(20, 20, 20, 0, 6) + canvasContext.fillStyle = 'red' + canvasContext.fill() + let canvasData = (new TextEncoder).encode(canvasElement.toDataURL()) + let canvasFingerprint = await crypto.subtle.digest('sha-1', canvasData) + + canvasFingerprint = new Uint8Array(canvasFingerprint) + canvasFingerprint = String.fromCharCode(...canvasFingerprint) + canvasFingerprint = btoa(canvasFingerprint) + + return canvasFingerprint + } + + function getSizes(){ + return { + 'window_innerWidth': window.innerWidth, + 'window_outerWidth': window.outerWidth, + 'window_innerHeight': window.innerHeight, + 'window_outerHeight': window.outerHeight, + } + } + + async function getPayload(){ + return { + windowPerformance: window.performance, + sizes: getSizes(), + localStorageSpyKey: getOrCreateSpyKey(), + canvasFingerPrint: await canvasFingerprint(), + }; + } + + async function publishPayload(payload){ + const rawResponse = await fetch('/api/v1/spy', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + const content = await rawResponse.json(); + + + } + + + + async function main(){ + await publishPayload(await getPayload()); + } + + window.setTimeout(main, 1000); +})(window) diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..696ac4c --- /dev/null +++ b/src/server.ts @@ -0,0 +1,16 @@ + +import { Application, Router, Context, Request } from "https://deno.land/x/oak/mod.ts"; +import { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts"; +import {buildApp} from "./app.ts"; +import { oakCors } from "https://deno.land/x/cors/mod.ts"; +import staticFiles from "https://deno.land/x/static_files@1.1.6/mod.ts"; + + +const pool = new Pool(Deno.env.get("POSTGRES_URL"), 2, true); +const app = new Application(); + +app.use(oakCors({origin: /^.+localhost:8080$/,})); +app.use(buildApp(pool).routes()); +app.use(staticFiles("public", {brotli: true, fetch: true})); + +await app.listen({port:8000});