-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d6dcf91
Showing
9 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Google Analytics tracking ID (Optional) | ||
GA4_MEASUREMENT_ID= | ||
|
||
# Fallback image url | ||
FALLBACK_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
name: Deploy | ||
on: | ||
push: | ||
branches: [main] | ||
pull_request: | ||
branches: main | ||
|
||
jobs: | ||
deploy: | ||
name: Deploy | ||
runs-on: ubuntu-latest | ||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
steps: | ||
- name: Clone repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Install Deno | ||
uses: denoland/setup-deno@v1 | ||
with: | ||
deno-version: v1.x | ||
|
||
- name: Upload to Deno Deploy | ||
uses: denoland/deployctl@v1 | ||
with: | ||
project: "brownie" | ||
entrypoint: "./main.ts" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"deno.enable": true, | ||
"deno.lint": true, | ||
"deno.unstable": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Copyright 2023 Jabolo <[email protected]> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
interface RouteConfig { | ||
routes: Record<string, string>; | ||
} | ||
|
||
/** | ||
* Route mapping configuration object. | ||
* The convention is to use brownie ingredients as paths. | ||
*/ | ||
export const config: RouteConfig = { | ||
routes: { | ||
cocoa: | ||
"https://jabolo-stats.vercel.app/api?username=Jabolol&theme=dracula&hide_border=false&include_all_commits=false&count_private=true&show_icons=true", | ||
vanilla: | ||
"https://github-readme-streak-stats-eight-iota.vercel.app/?user=Jabolol&theme=dracula&hide_border=false", | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"lock": false, | ||
"tasks": { | ||
"start": "deno run --watch --env --allow-env=GA4_MEASUREMENT_ID --allow-net --unstable main.ts" | ||
}, | ||
"imports": { | ||
"fp-ts": "npm:fp-ts", | ||
"ga4": "https://raw.githubusercontent.com/denoland/ga4/04a1ce209116f158b5ef1658b957bdb109db68ed/mod.ts", | ||
"~/": "./" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { report } from "~/utils.ts"; | ||
import { config } from "~/config.ts"; | ||
import { function as func, option, task, taskEither } from "fp-ts"; | ||
|
||
/** | ||
* Fetches the bytes from the specified URL. | ||
* @param url - The URL to fetch the bytes from. | ||
* @returns A promise that resolves to an ArrayBuffer. | ||
*/ | ||
const fetchBytes = async (url: string) => { | ||
const response = await fetch(url); | ||
if (!response.ok) throw new Error("Failed to fetch resource"); | ||
return response.arrayBuffer(); | ||
}; | ||
|
||
/** | ||
* Retrieves the value from the config.routes object based on the request URL. | ||
* If the value is not found, it falls back to the value of the `FALLBACK_URL` environment variable. | ||
* @param request - The request object. | ||
* @returns The retrieved value or the fallback URL. | ||
*/ | ||
const retrieve = func.flow( | ||
taskEither.tryCatchK( | ||
fetchBytes, | ||
(reason) => new Error(String(reason)), | ||
), | ||
); | ||
|
||
/** | ||
* Retrieves the path of the request URL given the request object. | ||
* @param request - The request object. | ||
* @returns The path of the request URL. | ||
*/ | ||
const path = func.flow( | ||
(request: Request) => new URL(request.url), | ||
(url) => url.pathname, | ||
(pathname) => pathname.slice(1), | ||
(pathname) => config.routes[pathname], | ||
option.fromNullable, | ||
option.getOrElse(() => Deno.env.get("FALLBACK_URL")!), | ||
); | ||
|
||
/** | ||
* Builds a task that transforms the input taskEither into a task that resolves to a Response object. | ||
* @param input - The input taskEither. | ||
* @returns A task that resolves to a Response object. | ||
*/ | ||
const build = ( | ||
input: taskEither.TaskEither<Error, ArrayBuffer>, | ||
) => | ||
func.pipe( | ||
input, | ||
taskEither.map<ArrayBuffer, Response>((bytes) => | ||
new Response(bytes, { headers: { "Content-Type": "image/svg+xml" } }) | ||
), | ||
taskEither.getOrElse<Error, Response>((error) => | ||
task.of(new Response(error.message)) | ||
), | ||
); | ||
|
||
/** | ||
* Handles the incoming request and returns a task that reports the request and response. | ||
* @param request - The incoming request object. | ||
* @param ctx - The Deno.ServeHandlerInfo object. | ||
* @returns A task that reports the request and response. | ||
*/ | ||
const handler = (request: Request, ctx: Deno.ServeHandlerInfo) => | ||
func.pipe( | ||
request, | ||
path, | ||
retrieve, | ||
build, | ||
task.map((response) => report(request, response, ctx)), | ||
)(); | ||
|
||
Deno.serve(handler); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* @file Google Analytics 4 reporting utility | ||
* @description This file is based on the original work from Deno's ga4 project. | ||
* For details, see: {@link https://github.com/denoland/ga4/blob/main/mod.ts} | ||
* | ||
* @modifications | ||
* - Removed deprecated interface `ConnInfo` | ||
* - Refactored `handler` to make it fresh-agnostic | ||
* | ||
* @license MIT The original work is licensed under the MIT License. | ||
* {@link https://github.com/denoland/ga4/blob/main/LICENSE.md} | ||
* | ||
* Additional terms: | ||
* © Jabolo 2023 | ||
* Licensed under the terms of the MIT License. | ||
*/ | ||
import { Event, GA4Report, isDocument, isServerError } from "ga4"; | ||
|
||
const GA4_MEASUREMENT_ID = Deno.env.get("GA4_MEASUREMENT_ID"); | ||
|
||
let showedMissingEnvWarning = false; | ||
|
||
function ga4( | ||
request: Request, | ||
conn: Deno.ServeHandlerInfo, | ||
response: Response, | ||
_start: number, | ||
error?: unknown, | ||
) { | ||
if (GA4_MEASUREMENT_ID === undefined) { | ||
if (!showedMissingEnvWarning) { | ||
showedMissingEnvWarning = true; | ||
console.warn( | ||
"GA4_MEASUREMENT_ID environment variable not set. Google Analytics reporting disabled.", | ||
); | ||
} | ||
return; | ||
} | ||
Promise.resolve().then(async () => { | ||
// We're tracking page views and file downloads. These are the only two | ||
// HTTP methods that _might_ be used. | ||
if (!/^(GET|POST)$/.test(request.method)) { | ||
return; | ||
} | ||
|
||
// If the visitor is using a web browser, only create events when we serve | ||
// a top level documents or download; skip assets like css, images, fonts. | ||
if (!isDocument(request, response) && error == null) { | ||
return; | ||
} | ||
|
||
let event: Event | null = null; | ||
const contentType = response.headers.get("content-type"); | ||
if (/text\/html/.test(contentType!)) { | ||
event = { name: "page_view", params: {} }; // Probably an old browser. | ||
} | ||
|
||
if (event == null && error == null) { | ||
return; | ||
} | ||
|
||
// If an exception was thrown, build a separate event to report it. | ||
let exceptionEvent; | ||
if (error != null) { | ||
exceptionEvent = { | ||
name: "exception", | ||
params: { | ||
description: String(error), | ||
fatal: isServerError(response), | ||
}, | ||
}; | ||
} else { | ||
exceptionEvent = undefined; | ||
} | ||
|
||
// Create basic report. | ||
const measurementId = GA4_MEASUREMENT_ID; | ||
// @ts-ignore GA4Report doesn't even use the localAddress parameter | ||
const report = new GA4Report({ measurementId, request, response, conn }); | ||
|
||
// Override the default (page_view) event. | ||
report.event = event; | ||
|
||
// Add the exception event, if any. | ||
if (exceptionEvent != null) { | ||
report.events.push(exceptionEvent); | ||
} | ||
|
||
await report.send(); | ||
}).catch((err) => { | ||
console.error(`Internal error: ${err}`); | ||
}); | ||
} | ||
|
||
export function report( | ||
req: Request, | ||
resp: Response, | ||
conn: Deno.ServeHandlerInfo, | ||
): Response { | ||
let err; | ||
let res: Response; | ||
const start = performance.now(); | ||
try { | ||
const headers = new Headers(resp.headers); | ||
res = new Response(resp.body, { status: resp.status, headers }); | ||
return res; | ||
} catch (e) { | ||
res = new Response("Internal Server Error", { | ||
status: 500, | ||
}); | ||
err = e; | ||
throw e; | ||
} finally { | ||
ga4( | ||
req, | ||
conn, | ||
res!, | ||
start, | ||
err, | ||
); | ||
} | ||
} |