refactor api and sdk

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-10-12 12:16:33 +02:00
parent 5b9a01c665
commit 8a2417de5a
18 changed files with 298 additions and 292 deletions

View File

@@ -1,7 +1,7 @@
import { v4 as uuid } from 'uuid'
import {
EventPayload,
MixanErrorResponse,
MixanIssuesResponse,
MixanResponse,
ProfilePayload,
} from '@mixan/types'
@@ -12,6 +12,9 @@ type MixanOptions = {
batchInterval?: number
maxBatchSize?: number
verbose?: boolean
saveProfileId: (profileId: string) => void,
getProfileId: () => string | null,
removeProfileId: () => void,
}
class Fetcher {
@@ -25,7 +28,11 @@ class Fetcher {
this.logger = options.verbose ? console.log : () => {}
}
post(path: string, data: Record<string, any>) {
post(
path: string,
data: Record<string, any>,
options: FetchRequestInit = {}
) {
const url = `${this.url}${path}`
this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2))
return fetch(url, {
@@ -35,30 +42,19 @@ class Fetcher {
},
method: 'POST',
body: JSON.stringify(data),
...options,
})
.then(async (res) => {
const response = await res.json<
MixanIssuesResponse | MixanErrorResponse | MixanResponse<unknown>
MixanErrorResponse | MixanResponse<unknown>
>()
if ('status' in response && response.status === 'ok') {
return response
}
if ('code' in response) {
this.logger(`Mixan error: [${response.code}] ${response.message}`)
if('status' in response && response.status === 'error') {
this.logger(`Mixan request failed: ${url}`, JSON.stringify(response, null, 2))
return null
}
if ('issues' in response) {
this.logger(`Mixan issues:`)
response.issues.forEach((issue) => {
this.logger(` - ${issue.message} (${issue.value})`)
})
return null
}
return null
return response
})
.catch(() => {
return null
@@ -99,7 +95,7 @@ class Batcher<T extends any> {
return
}
if (this.queue.length > this.maxBatchSize) {
if (this.queue.length >= this.maxBatchSize) {
this.send()
return
}
@@ -116,16 +112,21 @@ class Batcher<T extends any> {
export class Mixan {
private fetch: Fetcher
private eventBatcher: Batcher<EventPayload>
private profile: ProfilePayload | null = null
private profileId?: string
private options: MixanOptions
private logger: (...args: any[]) => void
constructor(options: MixanOptions) {
this.logger = options.verbose ? console.log : () => {}
this.options = options
this.fetch = new Fetcher(options)
this.setAnonymousUser()
this.eventBatcher = new Batcher(options, (queue) => {
this.fetch.post(
'/events',
queue.map((item) => ({
...item,
externalId: item.externalId || this.profile?.id,
profileId: item.profileId || this.profileId || null,
}))
)
})
@@ -140,18 +141,34 @@ export class Mixan {
name,
properties,
time: this.timestamp(),
externalId: this.profile?.id || null,
profileId: this.profileId || null,
})
}
private setAnonymousUser() {
const profileId = this.options.getProfileId()
if(profileId) {
this.profileId = profileId
this.logger('Use existing ID', this.profileId);
} else {
this.profileId = uuid()
this.logger('Create new ID', this.profileId);
this.options.saveProfileId(this.profileId)
this.fetch.post('/profiles', {
id: this.profileId,
properties: {},
})
}
}
async setUser(profile: ProfilePayload) {
this.profile = profile
await this.fetch.post('/profiles', profile)
await this.fetch.post(`/profiles/${this.profileId}`, profile, {
method: 'PUT'
})
}
async setUserProperty(name: string, value: any) {
await this.fetch.post('/profiles', {
...this.profile,
await this.fetch.post(`/profiles/${this.profileId}`, {
properties: {
[name]: value,
},
@@ -159,33 +176,41 @@ export class Mixan {
}
async increment(name: string, value: number = 1) {
if (!this.profile) {
if (!this.profileId) {
return
}
await this.fetch.post('/profiles/increment', {
id: this.profile.id,
await this.fetch.post(`/profiles/${this.profileId}/increment`, {
name,
value,
}, {
method: 'PUT'
})
}
async decrement(name: string, value: number = 1) {
if (!this.profile) {
if (!this.profileId) {
return
}
await this.fetch.post('/profiles/decrement', {
id: this.profile.id,
await this.fetch.post(`/profiles/${this.profileId}/decrement`, {
name,
value,
}, {
method: 'PUT'
})
}
screenView(route: string, properties?: Record<string, any>) {
this.event('screen_view', {
async screenView(route: string, properties?: Record<string, any>) {
await this.event('screen_view', {
...(properties || {}),
route,
})
}
clear() {
this.eventBatcher.flush()
this.options.removeProfileId()
this.profileId = undefined
}
}

View File

@@ -4,9 +4,11 @@
"type": "module",
"module": "index.ts",
"dependencies": {
"@mixan/types": "workspace:*"
"@mixan/types": "workspace:*",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/uuid": "^9.0.5",
"bun-types": "latest",
"typescript": "^5.0.0"
}

View File

@@ -3,7 +3,7 @@ export type MixanJson = Record<string, any>
export type EventPayload = {
name: string
time: string
externalId: string | null
profileId: string | null
properties: MixanJson
}
@@ -12,8 +12,8 @@ export type ProfilePayload = {
last_name?: string
email?: string
avatar?: string
id: string
properties: MixanJson
id?: string
properties?: MixanJson
}
export type ProfileIncrementPayload = {
@@ -59,13 +59,11 @@ export type MixanIssue = {
value: any
}
export type MixanIssuesResponse = {
issues: Array<MixanIssue>,
}
export type MixanErrorResponse = {
code: string
status: 'error'
code: number
message: string
issues: Array<MixanIssue>
}
export type MixanResponse<T> = {