working version of appRouter

This commit is contained in:
goenning 2023-09-01 10:52:15 +01:00
parent 90317d71d3
commit f4edab87af
21 changed files with 249 additions and 93 deletions

View file

@ -1,4 +1,5 @@
{ {
"editor.tabSize": 2,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.tabCompletion": "on", "editor.tabCompletion": "on",
"[typescript]": { "[typescript]": {

View file

@ -1,4 +1,8 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = {} const nextConfig = {
experimental: {
instrumentationHook: true,
},
};
module.exports = nextConfig module.exports = nextConfig;

View file

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev -p 4000",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint"
@ -16,6 +16,6 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"@aptabase/next": "*" "@aptabase/nextjs": "*"
} }
} }

View file

@ -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 (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}

View file

@ -1,11 +1,11 @@
export default function RootLayout({ import { AptabaseProvider } from '@aptabase/nextjs';
children,
}: { export default function RootLayout({ children }: { children: React.ReactNode }) {
children: React.ReactNode
}) {
return ( return (
<html lang="en"> <html lang="en">
<body>{children}</body> <body>
<AptabaseProvider appKey="A-DEV-0000000000">{children}</AptabaseProvider>
</body>
</html> </html>
) );
} }

View file

@ -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 ( return (
<main> <main>
Hello from Next.js App Router: {getName()} Hello from Next.js App Router <br /> <Counter />
</main> </main>
) );
} }

View file

@ -0,0 +1,5 @@
import { init } from '@aptabase/nextjs';
export function register() {
init('A-DEV-0000000000');
}

28
package-lock.json generated
View file

@ -23,7 +23,7 @@
"examples/next-with-approuter": { "examples/next-with-approuter": {
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@aptabase/next": "*", "@aptabase/nextjs": "*",
"@types/node": "20.5.7", "@types/node": "20.5.7",
"@types/react": "18.2.21", "@types/react": "18.2.21",
"@types/react-dom": "18.2.7", "@types/react-dom": "18.2.7",
@ -33,8 +33,8 @@
"typescript": "5.2.2" "typescript": "5.2.2"
} }
}, },
"node_modules/@aptabase/next": { "node_modules/@aptabase/nextjs": {
"resolved": "packages/next", "resolved": "packages/nextjs",
"link": true "link": true
}, },
"node_modules/@aptabase/web": { "node_modules/@aptabase/web": {
@ -989,6 +989,7 @@
"packages/next": { "packages/next": {
"name": "@aptabase/next", "name": "@aptabase/next",
"version": "0.0.1", "version": "0.0.1",
"extraneous": true,
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "5.0.2", "@rollup/plugin-replace": "5.0.2",
@ -999,7 +1000,24 @@
"typescript": "5.2.2" "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", "version": "3.28.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
"integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
@ -1015,7 +1033,7 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"packages/next/node_modules/tslib": { "packages/nextjs/node_modules/tslib": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",

View file

@ -8,7 +8,7 @@
], ],
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"watch": "turbo run watch --parallel", "build-packages": "turbo run build --filter=./packages/*",
"format": "prettier --write \"**/*.{ts,tsx,md}\"" "format": "prettier --write \"**/*.{ts,tsx,md}\""
}, },
"devDependencies": { "devDependencies": {

View file

@ -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];

View file

@ -1,3 +0,0 @@
export function getName() {
return "next"
}

View file

@ -1,16 +0,0 @@
{
"compilerOptions": {
"target": "ES5",
"strict": true,
"allowJs": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"types": ["@types"]
},
"declaration": true,
"declarationDir": "./dist",
"rootDir": "./src"
},
"include": ["./"]
}

View file

@ -1,23 +1,23 @@
{ {
"name": "@aptabase/next", "name": "@aptabase/nextjs",
"version": "0.0.1", "version": "0.0.1",
"private": false,
"type": "module", "type": "module",
"description": "Next.js SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps", "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", "types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"require": "./dist/index.js", "types": "./dist/index.d.ts",
"import": "./dist/index.mjs", "default": "./dist/index.js"
"types": "./dist/index.d.ts" },
"./server": {
"types": "./dist/server.d.ts",
"default": "./dist/server.js"
} }
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/aptabase/aptabase-js.git", "url": "git+https://github.com/aptabase/aptabase-js.git",
"directory": "packages/js" "directory": "packages/nextjs"
}, },
"bugs": { "bugs": {
"url": "https://github.com/aptabase/aptabase-js/issues" "url": "https://github.com/aptabase/aptabase-js/issues"
@ -25,10 +25,7 @@
"homepage": "https://github.com/aptabase/aptabase-js", "homepage": "https://github.com/aptabase/aptabase-js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "rollup -c ./rollup.config.mjs", "build": "tsc"
"watch": "rollup -c ./rollup.config.mjs -w",
"prepublishOnly": "npm run build",
"pretest": "npm run build"
}, },
"files": [ "files": [
"README.md", "README.md",
@ -43,5 +40,9 @@
"@rollup/plugin-terser": "0.4.3", "@rollup/plugin-terser": "0.4.3",
"tslib": "2.6.2", "tslib": "2.6.2",
"typescript": "5.2.2" "typescript": "5.2.2"
},
"peerDependencies": {
"react": "^18.0.0",
"next": "^13.0.0"
} }
} }

View file

@ -0,0 +1,82 @@
'use client';
import { createContext, useContext } from 'react';
import { AptabaseOptions } from './types';
type TrackEventFn = (eventName: string, props?: Record<string, string | number | boolean>) => Promise<void>;
type ContextProps = {
appKey?: string;
client?: AptabaseClient;
} & AptabaseOptions;
export type AptabaseClient = {
trackEvent: TrackEventFn;
};
const AptabaseContext = createContext<ContextProps>({});
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 <AptabaseContext.Provider value={{ appKey, ...options, client }}>{children}</AptabaseContext.Provider>;
}
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 <AptabaseProvider>?',
);
}
return ctx.client;
}
async function sendEvent(
appKey: string | undefined,
eventName: string,
props?: Record<string, string | number | boolean>,
): Promise<void> {
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}`);
}
}

5
packages/nextjs/src/global.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
type AptabaseState = {
appKey?: string;
};
declare var __APTABASE__: AptabaseState;

View file

@ -0,0 +1,5 @@
export * from './client';
export function init(appKey: string) {
globalThis.__APTABASE__ = { appKey };
}

View file

@ -0,0 +1,39 @@
import { headers } from 'next/headers';
export async function trackEvent(eventName: string, props?: Record<string, string | number | boolean>): Promise<void> {
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}`);
}
}

View file

@ -0,0 +1,4 @@
export type AptabaseOptions = {
host?: string;
appVersion?: string;
};

View file

@ -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"]
}

View file

@ -1,7 +1,6 @@
{ {
"name": "@aptabase/web", "name": "@aptabase/web",
"version": "0.1.3", "version": "0.1.3",
"private": false,
"type": "module", "type": "module",
"description": "JavaScript SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps", "description": "JavaScript SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
"main": "./dist/index.js", "main": "./dist/index.js",
@ -17,7 +16,7 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/aptabase/aptabase-js.git", "url": "git+https://github.com/aptabase/aptabase-js.git",
"directory": "packages/js" "directory": "packages/web"
}, },
"bugs": { "bugs": {
"url": "https://github.com/aptabase/aptabase-js/issues" "url": "https://github.com/aptabase/aptabase-js/issues"

View file

@ -2,7 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "ES5", "target": "ES5",
"strict": true, "strict": true,
"allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {