diff --git a/.vscode/settings.json b/.vscode/settings.json
index cfc509c..7aa6142 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,5 @@
{
+ "editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.tabCompletion": "on",
"[typescript]": {
diff --git a/examples/next-with-approuter/next.config.js b/examples/next-with-approuter/next.config.js
index 767719f..56d2cc7 100644
--- a/examples/next-with-approuter/next.config.js
+++ b/examples/next-with-approuter/next.config.js
@@ -1,4 +1,8 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {}
+const nextConfig = {
+ experimental: {
+ instrumentationHook: true,
+ },
+};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/examples/next-with-approuter/package.json b/examples/next-with-approuter/package.json
index e42b907..a0d0291 100644
--- a/examples/next-with-approuter/package.json
+++ b/examples/next-with-approuter/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "next dev -p 4000",
"build": "next build",
"start": "next start",
"lint": "next lint"
@@ -16,6 +16,6 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "5.2.2",
- "@aptabase/next": "*"
+ "@aptabase/nextjs": "*"
}
}
diff --git a/examples/next-with-approuter/src/app/Counter.tsx b/examples/next-with-approuter/src/app/Counter.tsx
new file mode 100644
index 0000000..cdac6dd
--- /dev/null
+++ b/examples/next-with-approuter/src/app/Counter.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import { useAptabase } from '@aptabase/nextjs';
+import { useState } from 'react';
+
+export function Counter() {
+ const { trackEvent } = useAptabase();
+ const [count, setCount] = useState(0);
+
+ function increment() {
+ setCount((c) => c + 1);
+ trackEvent('increment');
+ }
+
+ function decrement() {
+ setCount((c) => c - 1);
+ trackEvent('decrement');
+ }
+
+ return (
+
+
Count: {count}
+
+
+
+ );
+}
diff --git a/examples/next-with-approuter/src/app/layout.tsx b/examples/next-with-approuter/src/app/layout.tsx
index dbce4ea..3d5895e 100644
--- a/examples/next-with-approuter/src/app/layout.tsx
+++ b/examples/next-with-approuter/src/app/layout.tsx
@@ -1,11 +1,11 @@
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
+import { AptabaseProvider } from '@aptabase/nextjs';
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
- {children}
+
+ {children}
+
- )
+ );
}
diff --git a/examples/next-with-approuter/src/app/page.tsx b/examples/next-with-approuter/src/app/page.tsx
index c853c44..86d95df 100644
--- a/examples/next-with-approuter/src/app/page.tsx
+++ b/examples/next-with-approuter/src/app/page.tsx
@@ -1,9 +1,11 @@
-import { getName } from '@aptabase/next'
+import { trackEvent } from '@aptabase/nextjs/server';
+import { Counter } from './Counter';
-export default function Home() {
+export default async function Home() {
+ await trackEvent('page_view', { page: 'home' });
return (
- Hello from Next.js App Router: {getName()}
+ Hello from Next.js App Router
- )
+ );
}
diff --git a/examples/next-with-approuter/src/instrumentation.ts b/examples/next-with-approuter/src/instrumentation.ts
new file mode 100644
index 0000000..9de5fce
--- /dev/null
+++ b/examples/next-with-approuter/src/instrumentation.ts
@@ -0,0 +1,5 @@
+import { init } from '@aptabase/nextjs';
+
+export function register() {
+ init('A-DEV-0000000000');
+}
diff --git a/package-lock.json b/package-lock.json
index 3341b9a..c99fbdc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
"examples/next-with-approuter": {
"version": "0.1.0",
"dependencies": {
- "@aptabase/next": "*",
+ "@aptabase/nextjs": "*",
"@types/node": "20.5.7",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
@@ -33,8 +33,8 @@
"typescript": "5.2.2"
}
},
- "node_modules/@aptabase/next": {
- "resolved": "packages/next",
+ "node_modules/@aptabase/nextjs": {
+ "resolved": "packages/nextjs",
"link": true
},
"node_modules/@aptabase/web": {
@@ -989,6 +989,7 @@
"packages/next": {
"name": "@aptabase/next",
"version": "0.0.1",
+ "extraneous": true,
"license": "MIT",
"devDependencies": {
"@rollup/plugin-replace": "5.0.2",
@@ -999,7 +1000,24 @@
"typescript": "5.2.2"
}
},
- "packages/next/node_modules/rollup": {
+ "packages/nextjs": {
+ "name": "@aptabase/nextjs",
+ "version": "0.0.1",
+ "license": "MIT",
+ "devDependencies": {
+ "@rollup/plugin-replace": "5.0.2",
+ "@rollup/plugin-terser": "0.4.3",
+ "@rollup/plugin-typescript": "11.1.3",
+ "rollup": "3.28.1",
+ "tslib": "2.6.2",
+ "typescript": "5.2.2"
+ },
+ "peerDependencies": {
+ "next": "^13.0.0",
+ "react": "^18.0.0"
+ }
+ },
+ "packages/nextjs/node_modules/rollup": {
"version": "3.28.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
"integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
@@ -1015,7 +1033,7 @@
"fsevents": "~2.3.2"
}
},
- "packages/next/node_modules/tslib": {
+ "packages/nextjs/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
diff --git a/package.json b/package.json
index 2e5d148..f708d49 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
],
"scripts": {
"build": "turbo run build",
- "watch": "turbo run watch --parallel",
+ "build-packages": "turbo run build --filter=./packages/*",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"devDependencies": {
diff --git a/packages/next/rollup.config.mjs b/packages/next/rollup.config.mjs
deleted file mode 100644
index b466a96..0000000
--- a/packages/next/rollup.config.mjs
+++ /dev/null
@@ -1,38 +0,0 @@
-import replace from '@rollup/plugin-replace';
-import terser from '@rollup/plugin-terser';
-import typescript from '@rollup/plugin-typescript';
-import pkg from './package.json' assert { type: 'json' };
-
-const plugins = [
- terser(),
- replace({
- 'env.PKG_VERSION': pkg.version,
- preventAssignment: true,
- }),
- typescript({
- tsconfig: './tsconfig.json',
- moduleResolution: 'node',
- }),
-];
-
-const cjs = {
- input: './src/index.ts',
- output: {
- dir: './dist',
- entryFileNames: '[name].js',
- format: 'cjs',
- },
- plugins,
-};
-
-const es = {
- input: './src/index.ts',
- output: {
- dir: './dist',
- entryFileNames: '[name].mjs',
- format: 'es',
- },
- plugins,
-};
-
-export default [cjs, es];
diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts
deleted file mode 100644
index 0eab8ce..0000000
--- a/packages/next/src/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function getName() {
- return "next"
-}
\ No newline at end of file
diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json
deleted file mode 100644
index 28c5fae..0000000
--- a/packages/next/tsconfig.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES5",
- "strict": true,
- "allowJs": true,
- "esModuleInterop": true,
- "baseUrl": ".",
- "paths": {
- "types": ["@types"]
- },
- "declaration": true,
- "declarationDir": "./dist",
- "rootDir": "./src"
- },
- "include": ["./"]
-}
diff --git a/packages/next/package.json b/packages/nextjs/package.json
similarity index 66%
rename from packages/next/package.json
rename to packages/nextjs/package.json
index a0c1859..0587992 100644
--- a/packages/next/package.json
+++ b/packages/nextjs/package.json
@@ -1,23 +1,23 @@
{
- "name": "@aptabase/next",
+ "name": "@aptabase/nextjs",
"version": "0.0.1",
- "private": false,
"type": "module",
"description": "Next.js SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
- "main": "./dist/index.js",
- "module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
- "require": "./dist/index.js",
- "import": "./dist/index.mjs",
- "types": "./dist/index.d.ts"
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ },
+ "./server": {
+ "types": "./dist/server.d.ts",
+ "default": "./dist/server.js"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/aptabase/aptabase-js.git",
- "directory": "packages/js"
+ "directory": "packages/nextjs"
},
"bugs": {
"url": "https://github.com/aptabase/aptabase-js/issues"
@@ -25,10 +25,7 @@
"homepage": "https://github.com/aptabase/aptabase-js",
"license": "MIT",
"scripts": {
- "build": "rollup -c ./rollup.config.mjs",
- "watch": "rollup -c ./rollup.config.mjs -w",
- "prepublishOnly": "npm run build",
- "pretest": "npm run build"
+ "build": "tsc"
},
"files": [
"README.md",
@@ -43,5 +40,9 @@
"@rollup/plugin-terser": "0.4.3",
"tslib": "2.6.2",
"typescript": "5.2.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "next": "^13.0.0"
}
}
diff --git a/packages/nextjs/src/client.tsx b/packages/nextjs/src/client.tsx
new file mode 100644
index 0000000..1920f0c
--- /dev/null
+++ b/packages/nextjs/src/client.tsx
@@ -0,0 +1,82 @@
+'use client';
+
+import { createContext, useContext } from 'react';
+import { AptabaseOptions } from './types';
+
+type TrackEventFn = (eventName: string, props?: Record) => Promise;
+
+type ContextProps = {
+ appKey?: string;
+ client?: AptabaseClient;
+} & AptabaseOptions;
+
+export type AptabaseClient = {
+ trackEvent: TrackEventFn;
+};
+
+const AptabaseContext = createContext({});
+
+type Props = {
+ appKey: string;
+ options?: AptabaseOptions;
+ children: React.ReactNode;
+};
+
+export function AptabaseProvider({ appKey, options, children }: Props) {
+ const client: AptabaseClient = {
+ trackEvent: (eventName, props) => sendEvent(appKey, eventName, props),
+ };
+
+ return {children};
+}
+
+export function useAptabase(): AptabaseClient {
+ const ctx = useContext(AptabaseContext);
+ if (!ctx.client) {
+ throw new Error(
+ 'useAptabase must be used within AptabaseProvider. Did you forget to wrap your app in ?',
+ );
+ }
+
+ return ctx.client;
+}
+
+async function sendEvent(
+ appKey: string | undefined,
+ eventName: string,
+ props?: Record,
+): Promise {
+ if (!appKey) return Promise.resolve();
+
+ const body = JSON.stringify({
+ timestamp: new Date().toISOString(),
+ sessionId: 'CHANGE-THIS',
+ eventName: eventName,
+ systemProps: {
+ isDebug: true,
+ locale: 'en',
+ appVersion: '',
+ sdkVersion: 'aptabase-nextjs@0.0.1',
+ },
+ props: props,
+ });
+
+ try {
+ const response = await fetch('http://localhost:3000/api/v0/event', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'App-Key': appKey,
+ },
+ credentials: 'omit',
+ body,
+ });
+
+ if (response.status >= 300) {
+ const responseBody = await response.text();
+ console.warn(`Failed to send event "${eventName}": ${response.status} ${responseBody}`);
+ }
+ } catch (e) {
+ console.warn(`Failed to send event "${eventName}": ${e}`);
+ }
+}
diff --git a/packages/nextjs/src/global.d.ts b/packages/nextjs/src/global.d.ts
new file mode 100644
index 0000000..ecaf0f1
--- /dev/null
+++ b/packages/nextjs/src/global.d.ts
@@ -0,0 +1,5 @@
+type AptabaseState = {
+ appKey?: string;
+};
+
+declare var __APTABASE__: AptabaseState;
diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts
new file mode 100644
index 0000000..d3df734
--- /dev/null
+++ b/packages/nextjs/src/index.ts
@@ -0,0 +1,5 @@
+export * from './client';
+
+export function init(appKey: string) {
+ globalThis.__APTABASE__ = { appKey };
+}
diff --git a/packages/nextjs/src/server.ts b/packages/nextjs/src/server.ts
new file mode 100644
index 0000000..1bbb563
--- /dev/null
+++ b/packages/nextjs/src/server.ts
@@ -0,0 +1,39 @@
+import { headers } from 'next/headers';
+
+export async function trackEvent(eventName: string, props?: Record): Promise {
+ const appKey = globalThis.__APTABASE__.appKey;
+ if (!appKey) return Promise.resolve();
+
+ const body = JSON.stringify({
+ timestamp: new Date().toISOString(),
+ sessionId: 'CHANGE-THIS',
+ eventName: eventName,
+ systemProps: {
+ isDebug: true,
+ locale: 'en',
+ appVersion: '',
+ sdkVersion: 'aptabase-nextjs@0.0.1',
+ },
+ props: props,
+ });
+
+ try {
+ const response = await fetch('http://localhost:3000/api/v0/event', {
+ method: 'POST',
+ headers: {
+ 'User-Agent': headers().get('User-Agent') ?? '',
+ 'Content-Type': 'application/json',
+ 'App-Key': appKey,
+ },
+ credentials: 'omit',
+ body,
+ });
+
+ if (response.status >= 300) {
+ const responseBody = await response.text();
+ console.warn(`Failed to send event "${eventName}": ${response.status} ${responseBody}`);
+ }
+ } catch (e) {
+ console.warn(`Failed to send event "${eventName}": ${e}`);
+ }
+}
diff --git a/packages/nextjs/src/types.ts b/packages/nextjs/src/types.ts
new file mode 100644
index 0000000..e8835a2
--- /dev/null
+++ b/packages/nextjs/src/types.ts
@@ -0,0 +1,4 @@
+export type AptabaseOptions = {
+ host?: string;
+ appVersion?: string;
+};
diff --git a/packages/nextjs/tsconfig.json b/packages/nextjs/tsconfig.json
new file mode 100644
index 0000000..00f5a87
--- /dev/null
+++ b/packages/nextjs/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "esModuleInterop": true,
+ "lib": ["esnext", "dom"],
+ "module": "esnext",
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "noEmitOnError": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitReturns": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "outDir": "dist",
+ "sourceMap": true,
+ "strict": true,
+ "target": "es2020",
+ "types": ["node"],
+ },
+ "include": ["src/*", "src/global.d.ts"],
+ "exclude": ["node_modules"]
+}
\ No newline at end of file
diff --git a/packages/web/package.json b/packages/web/package.json
index dd7e481..2da99ec 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -1,7 +1,6 @@
{
"name": "@aptabase/web",
"version": "0.1.3",
- "private": false,
"type": "module",
"description": "JavaScript SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
"main": "./dist/index.js",
@@ -17,7 +16,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/aptabase/aptabase-js.git",
- "directory": "packages/js"
+ "directory": "packages/web"
},
"bugs": {
"url": "https://github.com/aptabase/aptabase-js/issues"
diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json
index 28c5fae..0f249c6 100644
--- a/packages/web/tsconfig.json
+++ b/packages/web/tsconfig.json
@@ -2,7 +2,6 @@
"compilerOptions": {
"target": "ES5",
"strict": true,
- "allowJs": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {