add native and web sdks

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-11-06 08:52:58 +01:00
parent ce26b24c1b
commit 4ddafcad07
33 changed files with 812 additions and 68 deletions

View File

@@ -11,10 +11,12 @@ export interface NewMixanOptions {
batchInterval?: number;
maxBatchSize?: number;
sessionTimeout?: number;
session?: boolean;
verbose?: boolean;
saveProfileId: (profiId: string) => void;
getProfileId: () => string | null;
removeProfileId: () => void;
setItem: (key: string, profileId: string) => void;
getItem: (key: string) => string | null;
removeItem: (key: string) => void;
trackIp?: boolean;
}
export type MixanOptions = Required<NewMixanOptions>;
@@ -39,7 +41,6 @@ class Fetcher {
const url = `${this.url}${path}`;
this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2));
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return fetch(url, {
headers: {
['mixan-client-id']: this.clientId,
@@ -137,19 +138,23 @@ export class Mixan {
private options: MixanOptions;
private logger: (...args: any[]) => void;
private globalProperties: Record<string, unknown> = {};
private lastEventAt?: string;
private lastScreenViewAt?: string;
private lastEventAt: string;
private promiseIp: Promise<string | null>;
constructor(options: NewMixanOptions) {
this.logger = options.verbose ? console.log : () => {};
this.options = {
sessionTimeout: 1000 * 60 * 30,
session: true,
verbose: false,
batchInterval: 10000,
maxBatchSize: 10,
trackIp: false,
...options,
};
this.lastEventAt =
this.options.getItem('@mixan:lastEventAt') ?? '1970-01-01';
this.fetch = new Fetcher(this.options);
this.eventBatcher = new Batcher(this.options, (queue) => {
this.fetch.post(
@@ -164,13 +169,26 @@ export class Mixan {
}))
);
});
this.promiseIp = this.options.trackIp
? fetch('https://api.ipify.org')
.then((res) => res.text())
.catch(() => null)
: Promise.resolve(null);
}
timestamp() {
return new Date().toISOString();
async ip() {
return this.promiseIp;
}
init() {
timestamp(modify = 0) {
return new Date(Date.now() + modify).toISOString();
}
init(properties?: Record<string, unknown>) {
if (properties) {
this.setGlobalProperties(properties);
}
this.logger('Mixan: Init');
this.setAnonymousUser();
}
@@ -178,14 +196,15 @@ export class Mixan {
event(name: string, properties: Record<string, unknown> = {}) {
const now = new Date();
const isSessionStart =
now.getTime() - new Date(this.lastEventAt ?? '1970-01-01').getTime() >
this.options.sessionTimeout;
this.options.session &&
now.getTime() - new Date(this.lastEventAt).getTime() >
this.options.sessionTimeout;
if (isSessionStart) {
this.logger('Mixan: Session start');
this.eventBatcher.add({
name: 'session_start',
time: this.timestamp(),
time: this.timestamp(-10),
properties: {},
profileId: this.profileId ?? null,
});
@@ -199,19 +218,28 @@ export class Mixan {
profileId: this.profileId ?? null,
});
this.lastEventAt = this.timestamp();
this.options.setItem('@mixan:lastEventAt', this.lastEventAt);
}
private async setAnonymousUser(retryCount = 0) {
const profileId = this.options.getProfileId();
const profileId = this.options.getItem('@mixan:profileId');
if (profileId) {
this.profileId = profileId;
await this.setUser({
properties: this.globalProperties,
});
this.logger('Mixan: Use existing profile', this.profileId);
} else {
const res = await this.fetch.post<undefined, { id: string }>('/profiles');
const res = await this.fetch.post<ProfilePayload, { id: string }>(
'/profiles',
{
properties: this.globalProperties,
}
);
if (res) {
this.profileId = res.id;
this.options.saveProfileId(res.id);
this.options.setItem('@mixan:profileId', res.id);
this.logger('Mixan: Create new profile', this.profileId);
} else if (retryCount < 2) {
setTimeout(() => {
@@ -249,8 +277,16 @@ export class Mixan {
}
setGlobalProperties(properties: Record<string, unknown>) {
if (typeof properties !== 'object') {
return this.logger(
'Mixan: Set global properties failed, properties must be an object'
);
}
this.logger('Mixan: Set global properties', properties);
this.globalProperties = properties ?? {};
this.globalProperties = {
...this.globalProperties,
...properties,
};
}
async increment(name: string, value = 1) {
@@ -291,34 +327,16 @@ export class Mixan {
);
}
screenView(route: string, _properties?: Record<string, unknown>) {
const properties = _properties ?? {};
const now = new Date();
if (this.lastScreenViewAt) {
const last = new Date(this.lastScreenViewAt);
const diff = now.getTime() - last.getTime();
this.logger(`Mixan: Screen view duration: ${diff}ms`);
properties.duration = diff;
}
this.lastScreenViewAt = now.toISOString();
this.event('screen_view', {
...properties,
route,
});
}
flush() {
this.logger('Mixan: Flushing events queue');
this.eventBatcher.send();
this.lastScreenViewAt = undefined;
}
clear() {
this.logger('Mixan: Clear, send remaining events and remove profileId');
this.eventBatcher.send();
this.options.removeProfileId();
this.options.removeItem('@mixan:profileId');
this.options.removeItem('@mixan:session');
this.profileId = undefined;
this.setAnonymousUser();
}

View File

@@ -3,6 +3,7 @@
"version": "0.0.1",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
"lint": "eslint .",
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
"typecheck": "tsc --noEmit"
@@ -13,6 +14,7 @@
"devDependencies": {
"@mixan/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*",
"@mixan/tsconfig": "workspace:*",
"eslint": "^8.48.0",
"prettier": "^3.0.3",
"tsup": "^7.2.0",

View File

@@ -1,21 +1,6 @@
{
"extends": "@mixan/tsconfig/sdk.json",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"outDir": "dist",
"allowImportingTsExtensions": false,
"noEmit": false
"outDir": "dist"
}
}

View File

@@ -1,10 +1,7 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['index.ts'],
format: ['cjs', 'esm'], // Build for commonJS and ESmodules
dts: true, // Generate declaration file (.d.ts)
splitting: false,
sourcemap: true,
clean: true,
});
import config from '@mixan/tsconfig/tsup.config.json' assert {
type: 'json'
}
export default defineConfig(config as any);