rename mixan to OPENPANEL!
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user