refacto/migrate-turborepo
This commit is contained in:
parent
9905366e9a
commit
a9507e7d3d
134 changed files with 20842 additions and 475 deletions
5
packages/electron/.gitignore
vendored
Normal file
5
packages/electron/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
dist
|
||||
example/node_modules
|
||||
example/dist-electron
|
||||
example/dist
|
10
packages/electron/.vscode/settings.json
vendored
Normal file
10
packages/electron/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true
|
||||
}
|
31
packages/electron/CHANGELOG.md
Normal file
31
packages/electron/CHANGELOG.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
## 0.2.2
|
||||
|
||||
Reduced minimum Electron version to v8.x
|
||||
|
||||
## 0.2.1
|
||||
|
||||
Fix typescript definitions
|
||||
|
||||
## 0.2.0
|
||||
|
||||
Preload script is no longer required, the SDK now uses fetch via a custom protocol
|
||||
|
||||
## 0.1.4
|
||||
|
||||
Refactored submodules to simplify setup
|
||||
|
||||
## 0.1.3
|
||||
|
||||
Added notice when importing `@aptabase/electron` module
|
||||
|
||||
## 0.1.2
|
||||
|
||||
Fix for disabled contextIsolation
|
||||
|
||||
## 0.1.1
|
||||
|
||||
Support for contextIsolation
|
||||
|
||||
## 0.1.0
|
||||
|
||||
Initial release of Electron SDK
|
21
packages/electron/LICENSE
Normal file
21
packages/electron/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Sumbit Labs Ltd.
|
||||
|
||||
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.
|
51
packages/electron/README.md
Normal file
51
packages/electron/README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||

|
||||
|
||||
# Electron SDK for Aptabase
|
||||
|
||||
Instrument your Electron apps with Aptabase, an Open Source, Privacy-First, and Simple Analytics for Mobile, Desktop, and Web Apps.
|
||||
|
||||
## Install
|
||||
|
||||
Install the SDK using your preferred JavaScript package manager
|
||||
|
||||
```bash
|
||||
npm add @aptabase/electron
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, you need to get your `App Key` from Aptabase, you can find it in the `Instructions` menu on the left side menu.
|
||||
|
||||
On your Electron main's process, initialize the SDK before the app is ready:
|
||||
|
||||
```js
|
||||
import { initialize } from "@aptabase/electron/main";
|
||||
|
||||
initialize("<YOUR_APP_KEY>"); // 👈 this is where you enter your App Key
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// ... the rest of your app initialization code
|
||||
});
|
||||
```
|
||||
|
||||
Afterward, you can start tracking events with `trackEvent`:
|
||||
|
||||
```js
|
||||
import { trackEvent } from "@aptabase/electron";
|
||||
|
||||
trackEvent("app_started"); // An event with no properties
|
||||
trackEvent("screen_view", { name: "Settings" }); // An event with a custom property
|
||||
```
|
||||
|
||||
**NOTE:** The `trackEvent` function is available under separate import paths, depending on where you want to track the event from.
|
||||
|
||||
- import from `@aptabase/electron` to track events from the `renderer` process
|
||||
- import from `@aptabase/electron/main` to track events from the `main` process
|
||||
|
||||
A few important notes:
|
||||
|
||||
1. The SDK will automatically enhance the event with some useful information, like the OS, the app version, and other things.
|
||||
2. You're in control of what gets sent to Aptabase. This SDK does not automatically track any events, you need to call `trackEvent` manually.
|
||||
- Because of this, it's generally recommended to at least track an event at startup
|
||||
3. You do not need to await for the `trackEvent` function, it'll run in the background.
|
||||
4. Only strings and numbers values are allowed on custom properties
|
21
packages/electron/example/electron/main.ts
Normal file
21
packages/electron/example/electron/main.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { initialize, trackEvent } from "@aptabase/electron/main";
|
||||
import { BrowserWindow, app } from "electron";
|
||||
|
||||
initialize("A-DEV-7523634193");
|
||||
|
||||
app.whenReady().then(() => {
|
||||
trackEvent("app_started");
|
||||
|
||||
const win = new BrowserWindow({
|
||||
title: "Main window",
|
||||
});
|
||||
|
||||
// You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(process.env.VITE_DEV_SERVER_URL);
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
// Load your file
|
||||
win.loadFile("dist/index.html");
|
||||
}
|
||||
});
|
13
packages/electron/example/index.html
Normal file
13
packages/electron/example/index.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
1417
packages/electron/example/package-lock.json
generated
Normal file
1417
packages/electron/example/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
packages/electron/example/package.json
Normal file
17
packages/electron/example/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aptabase/electron": "file:..",
|
||||
"electron": "12.2.3",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "4.3.8",
|
||||
"vite-plugin-electron": "0.11.2"
|
||||
}
|
||||
}
|
1
packages/electron/example/public/vite.svg
Normal file
1
packages/electron/example/public/vite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
15
packages/electron/example/src/counter.ts
Normal file
15
packages/electron/example/src/counter.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { trackEvent } from "@aptabase/electron";
|
||||
|
||||
export function setupCounter(element: HTMLButtonElement) {
|
||||
let counter = 0;
|
||||
const setCounter = (count: number) => {
|
||||
counter = count;
|
||||
element.innerHTML = `count is ${counter}`;
|
||||
};
|
||||
setCounter(0);
|
||||
|
||||
element.addEventListener("click", () => {
|
||||
setCounter(counter + 1);
|
||||
trackEvent("increment", { counter });
|
||||
});
|
||||
}
|
24
packages/electron/example/src/main.ts
Normal file
24
packages/electron/example/src/main.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { setupCounter } from "./counter";
|
||||
import "./style.css";
|
||||
import typescriptLogo from "./typescript.svg";
|
||||
import viteLogo from "/vite.svg";
|
||||
|
||||
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src="${viteLogo}" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://www.typescriptlang.org/" target="_blank">
|
||||
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
|
||||
</a>
|
||||
<h1>Vite + TypeScript</h1>
|
||||
<div class="card">
|
||||
<button id="counter" type="button"></button>
|
||||
</div>
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and TypeScript logos to learn more
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
setupCounter(document.querySelector<HTMLButtonElement>("#counter")!);
|
97
packages/electron/example/src/style.css
Normal file
97
packages/electron/example/src/style.css
Normal file
|
@ -0,0 +1,97 @@
|
|||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vanilla:hover {
|
||||
filter: drop-shadow(0 0 2em #3178c6aa);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
1
packages/electron/example/src/typescript.svg
Normal file
1
packages/electron/example/src/typescript.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
packages/electron/example/src/vite-env.d.ts
vendored
Normal file
1
packages/electron/example/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
18
packages/electron/example/tsconfig.json
Normal file
18
packages/electron/example/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
12
packages/electron/example/vite.config.ts
Normal file
12
packages/electron/example/vite.config.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from "vite";
|
||||
import electron from "vite-plugin-electron";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
electron([
|
||||
{
|
||||
entry: "electron/main.ts",
|
||||
},
|
||||
]),
|
||||
],
|
||||
});
|
54
packages/electron/package.json
Normal file
54
packages/electron/package.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "@aptabase/electron",
|
||||
"version": "0.2.2",
|
||||
"private": false,
|
||||
"description": "Electron SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./dist/index.es.js",
|
||||
"require": "./dist/index.cjs.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./main": {
|
||||
"import": "./dist/main.es.js",
|
||||
"require": "./dist/main.cjs.js",
|
||||
"types": "./dist/main.d.ts"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"dist/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/aptabase/aptabase-electron.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/aptabase/aptabase-electron/issues"
|
||||
},
|
||||
"homepage": "https://github.com/aptabase/aptabase-electron",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist",
|
||||
"package.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
"vite": "4.3.9",
|
||||
"vite-plugin-dts": "2.3.0",
|
||||
"electron": ">= 8.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"electron": ">= 8.x"
|
||||
}
|
||||
}
|
97
packages/electron/src/env.ts
Normal file
97
packages/electron/src/env.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { exec } from "child_process";
|
||||
import type { App } from "electron";
|
||||
import { readFile } from "fs";
|
||||
import { release } from "os";
|
||||
|
||||
// env.PKG_VERSION is replaced by Vite during build phase
|
||||
const sdkVersion = "aptabase-electron@env.PKG_VERSION";
|
||||
|
||||
export interface EnvironmentInfo {
|
||||
isDebug: boolean;
|
||||
locale: string;
|
||||
appVersion: string;
|
||||
sdkVersion: string;
|
||||
osName: String;
|
||||
osVersion: String;
|
||||
engineName: String;
|
||||
engineVersion: String;
|
||||
}
|
||||
|
||||
export async function getEnvironmentInfo(app: App): Promise<EnvironmentInfo> {
|
||||
const [osName, osVersion] = await getOperatingSystem();
|
||||
|
||||
return {
|
||||
appVersion: app.getVersion(),
|
||||
isDebug: !app.isPackaged,
|
||||
locale: app.getLocale(),
|
||||
osName,
|
||||
osVersion,
|
||||
engineName: "Chromium",
|
||||
engineVersion: process.versions.chrome,
|
||||
sdkVersion,
|
||||
};
|
||||
}
|
||||
|
||||
async function getOperatingSystem(): Promise<[string, string]> {
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
return ["Windows", release()];
|
||||
case "darwin":
|
||||
const macOSVersion = await getMacOSVersion();
|
||||
return ["macOS", macOSVersion];
|
||||
default:
|
||||
return await getLinuxInfo();
|
||||
}
|
||||
}
|
||||
|
||||
async function getMacOSVersion() {
|
||||
try {
|
||||
const output = await new Promise<string>((resolve, reject) => {
|
||||
exec(
|
||||
"/usr/bin/sw_vers -productVersion",
|
||||
(error: Error | null, stdout: string) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(stdout);
|
||||
}
|
||||
);
|
||||
});
|
||||
return output.trim();
|
||||
} catch (ex) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
async function getLinuxInfo(): Promise<[string, string]> {
|
||||
try {
|
||||
const content = await new Promise<string>((resolve, reject) => {
|
||||
readFile(
|
||||
"/etc/os-release",
|
||||
"utf8",
|
||||
(error: Error | null, output: string) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(output);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const lines = content.split("\n");
|
||||
const osData: Record<string, string> = {};
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split("=");
|
||||
if (key && value) {
|
||||
osData[key] = value.replace(/"/g, ""); // Remove quotes if present
|
||||
}
|
||||
}
|
||||
const osName = osData["NAME"] ?? "Linux";
|
||||
const osVersion = osData["VERSION_ID"] ?? "";
|
||||
return [osName, osVersion];
|
||||
} catch {
|
||||
return ["Linux", ""];
|
||||
}
|
||||
}
|
20
packages/electron/src/index.ts
Normal file
20
packages/electron/src/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
export async function trackEvent(
|
||||
eventName: string,
|
||||
props?: Record<string, string | number | boolean>
|
||||
) {
|
||||
if (typeof window === "undefined") {
|
||||
console.error(
|
||||
"Aptabase: to track events in the main process you must import 'trackEvent' from '@aptabase/electron/main'."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch("aptabase-ipc://trackEvent", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ eventName, props }),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Aptabase: Failed to send event", err);
|
||||
}
|
||||
}
|
180
packages/electron/src/main.ts
Normal file
180
packages/electron/src/main.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { app, net, protocol } from "electron";
|
||||
import { EnvironmentInfo, getEnvironmentInfo } from "./env";
|
||||
import { newSessionId } from "./session";
|
||||
|
||||
export type AptabaseOptions = {
|
||||
host?: string;
|
||||
};
|
||||
|
||||
// Session expires after 1 hour of inactivity
|
||||
const SESSION_TIMEOUT = 1 * 60 * 60;
|
||||
let _sessionId = newSessionId();
|
||||
let _lastTouched = new Date();
|
||||
let _appKey = "";
|
||||
let _apiUrl = "";
|
||||
let _env: EnvironmentInfo | undefined;
|
||||
|
||||
const _hosts: { [region: string]: string } = {
|
||||
US: "https://us.aptabase.com",
|
||||
EU: "https://eu.aptabase.com",
|
||||
DEV: "http://localhost:3000",
|
||||
SH: "",
|
||||
};
|
||||
|
||||
export async function initialize(
|
||||
appKey: string,
|
||||
options?: AptabaseOptions
|
||||
): Promise<void> {
|
||||
if (app.isReady()) {
|
||||
console.warn(
|
||||
"Aptabase: `initialize` must be invoked before the app is ready. Tracking will be disabled."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = appKey.split("-");
|
||||
if (parts.length !== 3 || _hosts[parts[1]] === undefined) {
|
||||
console.warn(
|
||||
`Aptabase: App Key "${appKey}" is invalid. Tracking will be disabled.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
registerAptabaseProtocol();
|
||||
await app.whenReady();
|
||||
|
||||
registerEventHandler();
|
||||
|
||||
const baseUrl = getBaseUrl(parts[1], options);
|
||||
_apiUrl = `${baseUrl}/api/v0/event`;
|
||||
_env = await getEnvironmentInfo(app);
|
||||
_appKey = appKey;
|
||||
|
||||
// some events might be emitted before the initialization is complete
|
||||
// so we drain the buffer here
|
||||
drainBuffer();
|
||||
}
|
||||
|
||||
const buffer: {
|
||||
eventName: string;
|
||||
props?: Record<string, string | number | boolean>;
|
||||
}[] = [];
|
||||
|
||||
export function trackEvent(
|
||||
eventName: string,
|
||||
props?: Record<string, string | number | boolean>
|
||||
): Promise<void> {
|
||||
if (!_appKey || !_env) {
|
||||
buffer.push({ eventName, props });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let now = new Date();
|
||||
const diffInMs = now.getTime() - _lastTouched.getTime();
|
||||
const diffInSec = Math.floor(diffInMs / 1000);
|
||||
if (diffInSec > SESSION_TIMEOUT) {
|
||||
_sessionId = newSessionId();
|
||||
}
|
||||
_lastTouched = now;
|
||||
|
||||
const body = {
|
||||
timestamp: now.toISOString(),
|
||||
sessionId: _sessionId,
|
||||
eventName: eventName,
|
||||
systemProps: {
|
||||
isDebug: _env.isDebug,
|
||||
locale: _env.locale,
|
||||
osName: _env.osName,
|
||||
osVersion: _env.osVersion,
|
||||
engineName: _env.engineName,
|
||||
engineVersion: _env.engineVersion,
|
||||
appVersion: _env.appVersion,
|
||||
sdkVersion: _env.sdkVersion,
|
||||
},
|
||||
props: props,
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const onReject = (err: Error) => {
|
||||
console.error(`Aptabase: Failed to send event`, err);
|
||||
resolve();
|
||||
};
|
||||
|
||||
const req = net.request({
|
||||
method: "POST",
|
||||
url: _apiUrl,
|
||||
credentials: "omit",
|
||||
});
|
||||
|
||||
req.setHeader("Content-Type", "application/json");
|
||||
req.setHeader("App-Key", _appKey);
|
||||
|
||||
req.on("error", onReject);
|
||||
req.on("abort", onReject);
|
||||
req.on("response", (res) => {
|
||||
if (res.statusCode >= 300) {
|
||||
console.warn(
|
||||
`Aptabase: Failed to send event "${eventName}": ${res.statusCode} ${res.statusMessage}`
|
||||
);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
req.write(JSON.stringify(body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function drainBuffer() {
|
||||
while (buffer.length > 0) {
|
||||
const data = buffer.shift();
|
||||
if (data) {
|
||||
trackEvent(data.eventName, data.props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerAptabaseProtocol() {
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: "aptabase-ipc",
|
||||
privileges: {
|
||||
bypassCSP: true,
|
||||
corsEnabled: true,
|
||||
supportFetchAPI: true,
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
function registerEventHandler() {
|
||||
protocol.registerStringProtocol("aptabase-ipc", (request, callback) => {
|
||||
try {
|
||||
const data = request.uploadData?.[0]?.bytes;
|
||||
const { eventName, props } = JSON.parse(data?.toString() ?? "{}");
|
||||
trackEvent(eventName, props);
|
||||
} catch (err) {
|
||||
console.error("Aptabase: Failed to send event", err);
|
||||
}
|
||||
|
||||
callback("");
|
||||
});
|
||||
}
|
||||
|
||||
function getBaseUrl(
|
||||
region: string,
|
||||
options?: AptabaseOptions
|
||||
): string | undefined {
|
||||
if (region === "SH") {
|
||||
if (!options?.host) {
|
||||
console.warn(
|
||||
`Aptabase: Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
return options.host;
|
||||
}
|
||||
|
||||
return _hosts[region];
|
||||
}
|
25
packages/electron/src/session.ts
Normal file
25
packages/electron/src/session.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
const crypto = require("crypto");
|
||||
|
||||
export function newSessionId(): string {
|
||||
if (crypto && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
return [
|
||||
randomStr(8),
|
||||
randomStr(4),
|
||||
randomStr(4),
|
||||
randomStr(4),
|
||||
randomStr(12),
|
||||
].join("-");
|
||||
}
|
||||
|
||||
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charactersLength = characters.length;
|
||||
function randomStr(len: number) {
|
||||
let result = "";
|
||||
for (let i = 0; i < len; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
24
packages/electron/tsconfig.json
Normal file
24
packages/electron/tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": ["vite/client"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/index.ts", "src/main.ts", "*.d.ts"]
|
||||
}
|
28
packages/electron/vite.config.mjs
Normal file
28
packages/electron/vite.config.mjs
Normal file
|
@ -0,0 +1,28 @@
|
|||
import replace from "@rollup/plugin-replace";
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import pkg from "./package.json" assert { type: "json" };
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
formats: ["cjs", "es"],
|
||||
entry: {
|
||||
index: path.resolve(__dirname, "src/index.ts"),
|
||||
main: path.resolve(__dirname, "src/main.ts"),
|
||||
},
|
||||
name: "@aptabase/electron",
|
||||
fileName: (format, entryName) => `${entryName}.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ["electron", "os", "fs", "child_process", "crypto"],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts(),
|
||||
replace({
|
||||
"env.PKG_VERSION": pkg.version,
|
||||
}),
|
||||
],
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue