rename mixan to OPENPANEL!

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-11 13:40:46 +01:00
parent 6d4f9010d4
commit e6c0bc2ec8
201 changed files with 1193 additions and 1047 deletions

View File

@@ -6,7 +6,7 @@
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: false,
transpilePackages: ['@mixan/sdk', '@mixan/web', '@mixan/nextjs'],
transpilePackages: ['@openpanel/sdk', '@openpanel/web', '@openpanel/nextjs'],
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
/**

View File

@@ -1,5 +1,5 @@
{
"name": "@mixan/test",
"name": "@openpanel/test",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -12,17 +12,17 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@mixan-test/nextjs": "workspace:@mixan/nextjs@*",
"@mixan-test/sdk": "workspace:@mixan/sdk@*",
"@mixan-test/web": "workspace:@mixan/web@*",
"@openpanel-test/nextjs": "workspace:@openpanel/nextjs@*",
"@openpanel-test/sdk": "workspace:@openpanel/sdk@*",
"@openpanel-test/web": "workspace:@openpanel/web@*",
"next": "~14.1.0",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@mixan/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*",
"@mixan/tsconfig": "workspace:*",
"@openpanel/eslint-config": "workspace:*",
"@openpanel/prettier-config": "workspace:*",
"@openpanel/tsconfig": "workspace:*",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/react-syntax-highlighter": "^15.5.9",
@@ -42,10 +42,10 @@
"eslintConfig": {
"root": true,
"extends": [
"@mixan/eslint-config/base",
"@mixan/eslint-config/nextjs",
"@mixan/eslint-config/react"
"@openpanel/eslint-config/base",
"@openpanel/eslint-config/nextjs",
"@openpanel/eslint-config/react"
]
},
"prettier": "@mixan/prettier-config"
"prettier": "@openpanel/prettier-config"
}

View File

@@ -1,2 +1,2 @@
"use strict";(()=>{function v(r){return Promise.all(Object.entries(r).map(async([t,e])=>[t,await e??""])).then(t=>Object.fromEntries(t))}function m(r){let t={"Content-Type":"application/json"};return{headers:t,async fetch(e,i,n){let s=`${r}${e}`,c,l=await v(t);return new Promise(o=>{let h=a=>{clearTimeout(c),fetch(s,{headers:l,method:"POST",body:JSON.stringify(i??{}),keepalive:!0,...n??{}}).then(async p=>{if(p.status!==200&&p.status!==202)return f(a,o);let g=await p.text();if(!g)return o(null);o(g)}).catch(()=>f(a,o))};function f(a,p){if(a>1)return p(null);c=setTimeout(()=>{h(a+1)},Math.pow(2,a)*500)}h(0)})}}}var d=class{options;api;state={properties:{}};constructor(t){this.options=t,this.api=m(t.url),this.api.headers["mixan-client-id"]=t.clientId,this.options.clientSecret&&(this.api.headers["mixan-client-secret"]=this.options.clientSecret)}init(t){this.state.properties=t??{}}setProfileId(t){this.state.profileId=t}setProfile(t){this.setProfileId(t.profileId),this.api.fetch("/profile",{...t,properties:{...this.state.properties,...t.properties}})}increment(t,e,i){let n=i?.profileId??this.state.profileId;if(!n)return console.log("No profile id");this.api.fetch("/profile/increment",{profileId:n,property:t,value:e})}decrement(t,e,i){let n=i?.profileId??this.state.profileId;if(!n)return console.log("No profile id");this.api.fetch("/profile/decrement",{profileId:n,property:t,value:e})}event(t,e){let i=e?.profileId??this.state.profileId;delete e?.profileId,this.api.fetch("/event",{name:t,properties:{...this.state.properties,...e??{}},timestamp:this.timestamp(),deviceId:this.getDeviceId(),profileId:i}).then(n=>{this.options.setDeviceId&&n&&this.options.setDeviceId(n)})}setGlobalProperties(t){this.state.properties={...this.state.properties,...t}}clear(){this.state.properties={},this.state.deviceId=void 0,this.options.removeDeviceId&&this.options.removeDeviceId()}timestamp(){return new Date().toISOString()}getDeviceId(){if(this.state.deviceId)return this.state.deviceId;this.options.getDeviceId&&(this.state.deviceId=this.options.getDeviceId()||void 0)}};function b(r){return r.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_",""))}var u=class extends d{lastPath="";constructor(t){super(t),this.isServer()||(this.setGlobalProperties({referrer:document.referrer}),this.options.trackOutgoingLinks&&this.trackOutgoingLinks(),this.options.trackScreenViews&&this.trackScreenViews(),this.options.trackAttributes&&this.trackAttributes())}isServer(){return typeof document>"u"}trackOutgoingLinks(){this.isServer()||document.addEventListener("click",t=>{let e=t.target,i=e.closest("a");if(i&&e){let n=i.getAttribute("href");n?.startsWith("http")&&super.event("link_out",{href:n,text:i.innerText||i.getAttribute("title")||e.getAttribute("alt")||e.getAttribute("title")})}})}trackScreenViews(){if(this.isServer())return;let t=history.pushState;history.pushState=function(...n){let s=t.apply(this,n);return window.dispatchEvent(new Event("pushstate")),window.dispatchEvent(new Event("locationchange")),s};let e=history.replaceState;history.replaceState=function(...n){let s=e.apply(this,n);return window.dispatchEvent(new Event("replacestate")),window.dispatchEvent(new Event("locationchange")),s},window.addEventListener("popstate",()=>window.dispatchEvent(new Event("locationchange"))),this.options.hash?window.addEventListener("hashchange",()=>this.screenView()):window.addEventListener("locationchange",()=>this.screenView()),setTimeout(()=>{this.screenView()},50)}trackAttributes(){this.isServer()||document.addEventListener("click",t=>{let e=t.target,i=e.closest("button"),n=e.closest("button"),s=i?.getAttribute("data-event")?i:n?.getAttribute("data-event")?n:null;if(s){let c={};for(let o of s.attributes)o.name.startsWith("data-")&&o.name!=="data-event"&&(c[b(o.name.replace(/^data-/,""))]=o.value);let l=s.getAttribute("data-event");l&&super.event(l,c)}})}screenView(t){if(this.isServer())return;let e=window.location.href;this.lastPath!==e&&(this.lastPath=e,super.event("screen_view",{...t??{},path:e,title:document.title}))}};(r=>{if(r.op&&"q"in r.op){let t=r.op.q||[],e=new u(t.shift()[1]);t.forEach(i=>{i[0]in e&&e[i[0]](...i.slice(1))}),r.op=(i,...n)=>{let s=e[i].bind(e);typeof s=="function"&&s(...n)}}})(window);})();
"use strict";(()=>{function v(r){return Promise.all(Object.entries(r).map(async([t,e])=>[t,await e??""])).then(t=>Object.fromEntries(t))}function m(r){let t={"Content-Type":"application/json"};return{headers:t,async fetch(e,i,n){let s=`${r}${e}`,c,l=await v(t);return new Promise(o=>{let h=a=>{clearTimeout(c),fetch(s,{headers:l,method:"POST",body:JSON.stringify(i??{}),keepalive:!0,...n??{}}).then(async p=>{if(p.status!==200&&p.status!==202)return f(a,o);let g=await p.text();if(!g)return o(null);o(g)}).catch(()=>f(a,o))};function f(a,p){if(a>1)return p(null);c=setTimeout(()=>{h(a+1)},Math.pow(2,a)*500)}h(0)})}}}var d=class{options;api;state={properties:{}};constructor(t){this.options=t,this.api=m(t.url),this.api.headers["openpanel-client-id"]=t.clientId,this.options.clientSecret&&(this.api.headers["openpanel-client-secret"]=this.options.clientSecret)}init(t){this.state.properties=t??{}}setProfileId(t){this.state.profileId=t}setProfile(t){this.setProfileId(t.profileId),this.api.fetch("/profile",{...t,properties:{...this.state.properties,...t.properties}})}increment(t,e,i){let n=i?.profileId??this.state.profileId;if(!n)return console.log("No profile id");this.api.fetch("/profile/increment",{profileId:n,property:t,value:e})}decrement(t,e,i){let n=i?.profileId??this.state.profileId;if(!n)return console.log("No profile id");this.api.fetch("/profile/decrement",{profileId:n,property:t,value:e})}event(t,e){let i=e?.profileId??this.state.profileId;delete e?.profileId,this.api.fetch("/event",{name:t,properties:{...this.state.properties,...e??{}},timestamp:this.timestamp(),deviceId:this.getDeviceId(),profileId:i}).then(n=>{this.options.setDeviceId&&n&&this.options.setDeviceId(n)})}setGlobalProperties(t){this.state.properties={...this.state.properties,...t}}clear(){this.state.properties={},this.state.deviceId=void 0,this.options.removeDeviceId&&this.options.removeDeviceId()}timestamp(){return new Date().toISOString()}getDeviceId(){if(this.state.deviceId)return this.state.deviceId;this.options.getDeviceId&&(this.state.deviceId=this.options.getDeviceId()||void 0)}};function b(r){return r.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_",""))}var u=class extends d{lastPath="";constructor(t){super(t),this.isServer()||(this.setGlobalProperties({referrer:document.referrer}),this.options.trackOutgoingLinks&&this.trackOutgoingLinks(),this.options.trackScreenViews&&this.trackScreenViews(),this.options.trackAttributes&&this.trackAttributes())}isServer(){return typeof document>"u"}trackOutgoingLinks(){this.isServer()||document.addEventListener("click",t=>{let e=t.target,i=e.closest("a");if(i&&e){let n=i.getAttribute("href");n?.startsWith("http")&&super.event("link_out",{href:n,text:i.innerText||i.getAttribute("title")||e.getAttribute("alt")||e.getAttribute("title")})}})}trackScreenViews(){if(this.isServer())return;let t=history.pushState;history.pushState=function(...n){let s=t.apply(this,n);return window.dispatchEvent(new Event("pushstate")),window.dispatchEvent(new Event("locationchange")),s};let e=history.replaceState;history.replaceState=function(...n){let s=e.apply(this,n);return window.dispatchEvent(new Event("replacestate")),window.dispatchEvent(new Event("locationchange")),s},window.addEventListener("popstate",()=>window.dispatchEvent(new Event("locationchange"))),this.options.hash?window.addEventListener("hashchange",()=>this.screenView()):window.addEventListener("locationchange",()=>this.screenView()),setTimeout(()=>{this.screenView()},50)}trackAttributes(){this.isServer()||document.addEventListener("click",t=>{let e=t.target,i=e.closest("button"),n=e.closest("button"),s=i?.getAttribute("data-event")?i:n?.getAttribute("data-event")?n:null;if(s){let c={};for(let o of s.attributes)o.name.startsWith("data-")&&o.name!=="data-event"&&(c[b(o.name.replace(/^data-/,""))]=o.value);let l=s.getAttribute("data-event");l&&super.event(l,c)}})}screenView(t){if(this.isServer())return;let e=window.location.href;this.lastPath!==e&&(this.lastPath=e,super.event("screen_view",{...t??{},path:e,title:document.title}))}};(r=>{if(r.op&&"q"in r.op){let t=r.op.q||[],e=new u(t.shift()[1]);t.forEach(i=>{i[0]in e&&e[i[0]](...i.slice(1))}),r.op=(i,...n)=>{let s=e[i].bind(e);typeof s=="function"&&s(...n)}}})(window);})();
//# sourceMappingURL=cdn.global.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,127 @@
// import { MixanWeb } from '@mixan-test/web';
import type { FastifyReply, FastifyRequest } from "fastify";
import icoToPng from "ico-to-png";
import sharp from "sharp";
// export const mixan = new MixanWeb({
// verbose: true,
// url: 'http://localhost:3000/api/sdk',
// clientId: '568b4ed1-5d00-4f27-88a7-b8959e6674bd',
// clientSecret: '1e362905-d352-44c4-9263-e037a2ad52fb',
// trackIp: true,
// });
import { createHash } from "@openpanel/common";
import { redis } from "@openpanel/redis";
// mixan.init({
// appVersion: '1.0.0',
// });
// mixan.trackOutgoingLinks();
interface GetFaviconParams {
url: string;
}
async function getImageBuffer(url: string) {
try {
const res = await fetch(url);
const contentType = res.headers.get("content-type");
if (!contentType?.includes("image")) {
return null;
}
if (!res.ok) {
return null;
}
if (contentType === "image/x-icon" || url.endsWith(".ico")) {
const arrayBuffer = await res.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
return await icoToPng(buffer, 30);
}
return await sharp(await res.arrayBuffer())
.resize(30, 30, {
fit: "cover",
})
.png()
.toBuffer();
} catch (e) {
console.log("Failed to get image from url", url);
console.log(e);
}
}
const imageExtensions = ["svg", "png", "jpg", "jpeg", "gif", "webp", "ico"];
export async function getFavicon(
request: FastifyRequest<{
Querystring: GetFaviconParams;
}>,
reply: FastifyReply,
) {
function sendBuffer(buffer: Buffer, cacheKey?: string) {
if (cacheKey) {
redis.set(`favicon:${cacheKey}`, buffer.toString("base64"));
}
reply.type("image/png");
console.log("buffer", buffer.byteLength);
return reply.send(buffer);
}
if (!request.query.url) {
return reply.status(404).send("Not found");
}
const url = decodeURIComponent(request.query.url);
// DIRECT IMAGE
if (imageExtensions.find((ext) => url.endsWith(ext))) {
const cacheKey = createHash(url, 32);
const cache = await redis.get(`favicon:${cacheKey}`);
if (cache) {
return sendBuffer(Buffer.from(cache, "base64"));
}
const buffer = await getImageBuffer(url);
if (buffer && buffer.byteLength > 0) {
return sendBuffer(buffer, cacheKey);
}
}
const { hostname, origin } = new URL(url);
const cache = await redis.get(`favicon:${hostname}`);
if (cache) {
return sendBuffer(Buffer.from(cache, "base64"));
}
// TRY FAVICON.ICO
const buffer = await getImageBuffer(`${origin}/favicon.ico`);
if (buffer && buffer.byteLength > 0) {
return sendBuffer(buffer, hostname);
}
// PARSE HTML
const res = await fetch(url).then((res) => res.text());
function findFavicon(res: string) {
const match = res.match(
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/,
);
if (!match) {
return null;
}
return match[0].match(/href="(.+?)"/)?.[1] ?? null;
}
const favicon = findFavicon(res);
if (favicon) {
const buffer = await getImageBuffer(favicon);
if (buffer && buffer.byteLength > 0) {
return sendBuffer(buffer, hostname);
}
}
return reply.status(404).send("Not found");
}
export async function clearFavicons(
request: FastifyRequest,
reply: FastifyReply,
) {
const keys = await redis.keys("favicon:*");
for (const key of keys) {
await redis.del(key);
}
return reply.status(404).send("OK");
}

View File

@@ -1,59 +1,113 @@
import {
OpenpanelProvider,
SetProfileId,
trackEvent,
} from '@mixan-test/nextjs';
import { Mixan as Openpanel } from '@mixan-test/sdk';
import { getClientIp, parseIp } from "@/utils/parseIp";
import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
import type { FastifyReply, FastifyRequest } from "fastify";
import { assocPath, pathOr } from "ramda";
const opServer = new Openpanel({
clientId: '4c9a28cb-73c3-429f-beaf-4b3fe91352ea',
clientSecret: '2701ada9-fcbf-414a-ac94-9511949ee44d',
url: 'https://api.openpanel.dev',
});
import { getProfileById, upsertProfile } from "@openpanel/db";
import type {
IncrementProfilePayload,
UpdateProfilePayload,
} from "@openpanel/sdk";
export default function Page() {
// Track event in server actions
async function create() {
'use server';
opServer.event('some-event', {
profileId: '1234',
});
export async function updateProfile(
request: FastifyRequest<{
Body: UpdateProfilePayload;
}>,
reply: FastifyReply,
) {
const { profileId, properties, ...rest } = request.body;
const projectId = request.projectId;
const ip = getClientIp(request)!;
const ua = request.headers["user-agent"]!;
const uaInfo = parseUserAgent(ua);
const geo = await parseIp(ip);
await upsertProfile({
id: profileId,
projectId,
properties: {
...(properties ?? {}),
...(ip ? geo : {}),
...(isUserAgentSet(ua) ? uaInfo : {}),
},
...rest,
});
reply.status(202).send(profileId);
}
export async function incrementProfileProperty(
request: FastifyRequest<{
Body: IncrementProfilePayload;
}>,
reply: FastifyReply,
) {
const { profileId, property, value } = request.body;
const projectId = request.projectId;
const profile = await getProfileById(profileId);
if (!profile) {
return reply.status(404).send("Not found");
}
return (
<div>
{/* In layout.tsx (app dir) or _app.tsx (pages) */}
<OpenpanelProvider
clientId="0acce97f-1126-4439-b7ee-5d384e2fc94b"
url="https://api.openpanel.dev"
trackScreenViews
trackAttributes
trackOutgoingLinks
/>
{/* Provide user id in React Server Components */}
<SetProfileId value="1234" />
<button
onClick={() =>
trackEvent('some-event', {
bar: 'bar',
foo: 'foo',
revenue: 1000,
})
}
>
Track event with method
</button>
<button
data-event="some-event"
data-bar="bar"
data-foo="foo"
data-revenue="1000"
>
Track event with attributes
</button>
</div>
const parsed = parseInt(
pathOr<string>("0", property.split("."), profile.properties),
10,
);
if (isNaN(parsed)) {
return reply.status(400).send("Not number");
}
profile.properties = assocPath(
property.split("."),
parsed + value,
profile.properties,
);
await upsertProfile({
id: profile.id,
projectId,
properties: profile.properties,
});
reply.status(202).send(profile.id);
}
export async function decrementProfileProperty(
request: FastifyRequest<{
Body: IncrementProfilePayload;
}>,
reply: FastifyReply,
) {
const { profileId, property, value } = request.body;
const projectId = request.projectId;
const profile = await getProfileById(profileId);
if (!profile) {
return reply.status(404).send("Not found");
}
const parsed = parseInt(
pathOr<string>("0", property.split("."), profile.properties),
10,
);
if (isNaN(parsed)) {
return reply.status(400).send("Not number");
}
profile.properties = assocPath(
property.split("."),
parsed - value,
profile.properties,
);
await upsertProfile({
id: profile.id,
projectId,
properties: profile.properties,
});
reply.status(202).send(profile.id);
}

View File

@@ -1,4 +1,4 @@
import { OpenpanelProvider } from '@mixan-test/nextjs';
import { OpenpanelProvider } from '@openpanel-test/nextjs';
export default function Layout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -1,15 +1,51 @@
import { OpenpanelProvider } from '@mixan-test/nextjs';
import type { AppProps } from 'next/app';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<OpenpanelProvider
clientId="0acce97f-1126-4439-b7ee-5d384e2fc94b"
url="http://localhost:3333"
trackScreenViews
/>
<Component {...pageProps} />
</>
);
{
"name": "@openpanel/api",
"version": "0.0.1",
"scripts": {
"dev": "dotenv -e ../../.env -c -v WATCH=1 tsup",
"testing": "API_PORT=3333 pnpm dev",
"start": "node dist/index.js",
"build": "rm -rf dist && tsup",
"lint": "eslint .",
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@fastify/cors": "^9.0.0",
"@fastify/websocket": "^8.3.1",
"@logtail/pino": "^0.4.19",
"@openpanel/common": "workspace:*",
"@openpanel/db": "workspace:*",
"@openpanel/queue": "workspace:*",
"@openpanel/redis": "workspace:*",
"fastify": "^4.25.2",
"ico-to-png": "^0.2.1",
"pino": "^8.17.2",
"pino-pretty": "^10.3.1",
"ramda": "^0.29.1",
"sharp": "^0.33.2",
"ua-parser-js": "^1.0.37",
"uuid": "^9.0.1"
},
"devDependencies": {
"@openpanel/eslint-config": "workspace:*",
"@openpanel/prettier-config": "workspace:*",
"@openpanel/sdk": "workspace:*",
"@openpanel/tsconfig": "workspace:*",
"@types/ramda": "^0.29.6",
"@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8",
"@types/ws": "^8.5.10",
"eslint": "^8.48.0",
"prettier": "^3.0.3",
"tsup": "^7.2.0",
"typescript": "^5.2.2"
},
"eslintConfig": {
"root": true,
"extends": [
"@openpanel/eslint-config/base"
]
},
"prettier": "@openpanel/prettier-config"
}

View File

@@ -5,7 +5,7 @@ import {
increment,
setProfile,
trackEvent,
} from '@mixan-test/nextjs';
} from '@openpanel-test/nextjs';
import Link from 'next/link';
export default function Test() {

View File

@@ -1,5 +1,5 @@
{
"extends": "@mixan/tsconfig/base.json",
"extends": "@openpanel/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {