sdk: remove uuid dependency and get profile id from api
This commit is contained in:
@@ -1,22 +1,22 @@
|
|||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
import {
|
import {
|
||||||
EventPayload,
|
EventPayload,
|
||||||
MixanErrorResponse,
|
MixanErrorResponse,
|
||||||
MixanResponse,
|
|
||||||
ProfilePayload,
|
ProfilePayload,
|
||||||
} from '@mixan/types'
|
} from '@mixan/types'
|
||||||
|
|
||||||
type MixanOptions = {
|
type NewMixanOptions = {
|
||||||
url: string
|
url: string
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
batchInterval?: number
|
batchInterval?: number
|
||||||
maxBatchSize?: number
|
maxBatchSize?: number
|
||||||
|
sessionTimeout?: number
|
||||||
verbose?: boolean
|
verbose?: boolean
|
||||||
saveProfileId: (profileId: string) => void,
|
saveProfileId: (profiId: string) => void
|
||||||
getProfileId: () => string | null,
|
getProfileId: () => (string | null)
|
||||||
removeProfileId: () => void,
|
removeProfileId: () => void
|
||||||
}
|
}
|
||||||
|
type MixanOptions = Required<NewMixanOptions>
|
||||||
|
|
||||||
class Fetcher {
|
class Fetcher {
|
||||||
private url: string
|
private url: string
|
||||||
@@ -31,11 +31,11 @@ class Fetcher {
|
|||||||
this.logger = options.verbose ? console.log : () => {}
|
this.logger = options.verbose ? console.log : () => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
post(
|
post<Response extends unknown>(
|
||||||
path: string,
|
path: string,
|
||||||
data: Record<string, any>,
|
data: Record<string, any> = {},
|
||||||
options: FetchRequestInit = {}
|
options: FetchRequestInit = {}
|
||||||
) {
|
): Promise<Response | null> {
|
||||||
const url = `${this.url}${path}`
|
const url = `${this.url}${path}`
|
||||||
this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2))
|
this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2))
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
@@ -50,17 +50,27 @@ class Fetcher {
|
|||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
const response = await res.json<
|
const response = await res.json<
|
||||||
MixanErrorResponse | MixanResponse<unknown>
|
MixanErrorResponse | Response
|
||||||
>()
|
>()
|
||||||
|
|
||||||
if('status' in response && response.status === 'error') {
|
if(!response) {
|
||||||
this.logger(`Mixan request failed: [${options.method || 'POST'}] ${url}`, JSON.stringify(response, null, 2))
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
if (typeof response === 'object' && 'status' in response && response.status === 'error') {
|
||||||
|
this.logger(
|
||||||
|
`Mixan request failed: [${options.method || 'POST'}] ${url}`,
|
||||||
|
JSON.stringify(response, null, 2)
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return response as Response
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
this.logger(
|
||||||
|
`Mixan request failed: [${options.method || 'POST'}] ${url}`
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,19 +80,13 @@ class Batcher<T extends any> {
|
|||||||
queue: T[] = []
|
queue: T[] = []
|
||||||
timer?: Timer
|
timer?: Timer
|
||||||
callback: (queue: T[]) => void
|
callback: (queue: T[]) => void
|
||||||
maxBatchSize = 10
|
maxBatchSize: number
|
||||||
batchInterval = 10000
|
batchInterval: number
|
||||||
|
|
||||||
constructor(options: MixanOptions, callback: (queue: T[]) => void) {
|
constructor(options: MixanOptions, callback: (queue: T[]) => void) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
this.maxBatchSize = options.maxBatchSize
|
||||||
if (options.maxBatchSize) {
|
this.batchInterval = options.batchInterval
|
||||||
this.maxBatchSize = options.maxBatchSize
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.batchInterval) {
|
|
||||||
this.batchInterval = options.batchInterval
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(payload: T) {
|
add(payload: T) {
|
||||||
@@ -108,11 +112,11 @@ class Batcher<T extends any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send() {
|
send() {
|
||||||
if(this.timer) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer)
|
clearTimeout(this.timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.queue.length > 0) {
|
if (this.queue.length > 0) {
|
||||||
this.callback(this.queue)
|
this.callback(this.queue)
|
||||||
this.queue = []
|
this.queue = []
|
||||||
}
|
}
|
||||||
@@ -126,18 +130,29 @@ export class Mixan {
|
|||||||
private options: MixanOptions
|
private options: MixanOptions
|
||||||
private logger: (...args: any[]) => void
|
private logger: (...args: any[]) => void
|
||||||
private globalProperties: Record<string, any> = {}
|
private globalProperties: Record<string, any> = {}
|
||||||
|
private lastEventAt?: string
|
||||||
private lastScreenViewAt?: string
|
private lastScreenViewAt?: string
|
||||||
|
|
||||||
constructor(options: MixanOptions) {
|
constructor(options: NewMixanOptions) {
|
||||||
this.logger = options.verbose ? console.log : () => {}
|
this.logger = options.verbose ? console.log : () => {}
|
||||||
this.options = options
|
this.options = {
|
||||||
this.fetch = new Fetcher(options)
|
sessionTimeout: 1000 * 60 * 30,
|
||||||
this.setAnonymousUser()
|
verbose: false,
|
||||||
this.eventBatcher = new Batcher(options, (queue) => {
|
batchInterval: 10000,
|
||||||
|
maxBatchSize: 10,
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetch = new Fetcher(this.options)
|
||||||
|
this.eventBatcher = new Batcher(this.options, (queue) => {
|
||||||
this.fetch.post(
|
this.fetch.post(
|
||||||
'/events',
|
'/events',
|
||||||
queue.map((item) => ({
|
queue.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
|
properties: {
|
||||||
|
...this.globalProperties,
|
||||||
|
...item.properties,
|
||||||
|
},
|
||||||
profileId: item.profileId || this.profileId || null,
|
profileId: item.profileId || this.profileId || null,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
@@ -148,50 +163,74 @@ export class Mixan {
|
|||||||
return new Date().toISOString()
|
return new Date().toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
event(name: string, properties: Record<string, any>) {
|
init() {
|
||||||
|
this.logger('Mixan: Init')
|
||||||
|
this.setAnonymousUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
event(name: string, properties: Record<string, any> = {}) {
|
||||||
|
const now = new Date()
|
||||||
|
const isSessionStart =
|
||||||
|
now.getTime() - new Date(this.lastEventAt ?? '1970-01-01').getTime() >
|
||||||
|
this.options.sessionTimeout
|
||||||
|
|
||||||
|
if (isSessionStart) {
|
||||||
|
this.logger('Mixan: Session start')
|
||||||
|
this.eventBatcher.add({
|
||||||
|
name: 'session_start',
|
||||||
|
time: this.timestamp(),
|
||||||
|
properties: {},
|
||||||
|
profileId: this.profileId || null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.logger('Mixan: Queue event', name)
|
this.logger('Mixan: Queue event', name)
|
||||||
this.eventBatcher.add({
|
this.eventBatcher.add({
|
||||||
name,
|
name,
|
||||||
properties: {
|
properties,
|
||||||
...this.globalProperties,
|
|
||||||
...properties,
|
|
||||||
},
|
|
||||||
time: this.timestamp(),
|
time: this.timestamp(),
|
||||||
profileId: this.profileId || null,
|
profileId: this.profileId || null,
|
||||||
})
|
})
|
||||||
|
this.lastEventAt = this.timestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
private setAnonymousUser() {
|
private async setAnonymousUser(retryCount: number = 0) {
|
||||||
const profileId = this.options.getProfileId()
|
const profileId = this.options.getProfileId()
|
||||||
if(profileId) {
|
if (profileId) {
|
||||||
this.profileId = profileId
|
this.profileId = profileId
|
||||||
this.logger('Mixan: Use existing ID', this.profileId);
|
this.logger('Mixan: Use existing profile', this.profileId)
|
||||||
} else {
|
} else {
|
||||||
this.profileId = uuid()
|
const res = await this.fetch.post<{id: string}>('/profiles')
|
||||||
this.logger('Mixan: Create new ID', this.profileId);
|
|
||||||
this.options.saveProfileId(this.profileId)
|
if(res) {
|
||||||
this.fetch.post('/profiles', {
|
this.profileId = res.id
|
||||||
id: this.profileId,
|
this.options.saveProfileId(res.id)
|
||||||
properties: {},
|
this.logger('Mixan: Create new profile', this.profileId)
|
||||||
})
|
} else if(retryCount < 2) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setAnonymousUser(retryCount + 1)
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
this.logger('Mixan: Failed to create new profile')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUser(profile: ProfilePayload) {
|
async setUser(profile: ProfilePayload) {
|
||||||
if(!this.profileId) {
|
if (!this.profileId) {
|
||||||
return this.logger('Mixan: Set user failed, no profileId');
|
return this.logger('Mixan: Set user failed, no profileId')
|
||||||
}
|
}
|
||||||
this.logger('Mixan: Set user', profile);
|
this.logger('Mixan: Set user', profile)
|
||||||
await this.fetch.post(`/profiles/${this.profileId}`, profile, {
|
await this.fetch.post(`/profiles/${this.profileId}`, profile, {
|
||||||
method: 'PUT'
|
method: 'PUT',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserProperty(name: string, value: any) {
|
async setUserProperty(name: string, value: any) {
|
||||||
if(!this.profileId) {
|
if (!this.profileId) {
|
||||||
return this.logger('Mixan: Set user property, no profileId');
|
return this.logger('Mixan: Set user property, no profileId')
|
||||||
}
|
}
|
||||||
this.logger('Mixan: Set user property', name, value);
|
this.logger('Mixan: Set user property', name, value)
|
||||||
await this.fetch.post(`/profiles/${this.profileId}`, {
|
await this.fetch.post(`/profiles/${this.profileId}`, {
|
||||||
properties: {
|
properties: {
|
||||||
[name]: value,
|
[name]: value,
|
||||||
@@ -200,45 +239,53 @@ export class Mixan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setGlobalProperties(properties: Record<string, any>) {
|
async setGlobalProperties(properties: Record<string, any>) {
|
||||||
this.logger('Mixan: Set global properties', properties);
|
this.logger('Mixan: Set global properties', properties)
|
||||||
this.globalProperties = properties ?? {}
|
this.globalProperties = properties ?? {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async increment(name: string, value: number = 1) {
|
async increment(name: string, value: number = 1) {
|
||||||
if (!this.profileId) {
|
if (!this.profileId) {
|
||||||
this.logger('Mixan: Increment failed, no profileId');
|
this.logger('Mixan: Increment failed, no profileId')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger('Mixan: Increment user property', name, value);
|
this.logger('Mixan: Increment user property', name, value)
|
||||||
await this.fetch.post(`/profiles/${this.profileId}/increment`, {
|
await this.fetch.post(
|
||||||
name,
|
`/profiles/${this.profileId}/increment`,
|
||||||
value,
|
{
|
||||||
}, {
|
name,
|
||||||
method: 'PUT'
|
value,
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async decrement(name: string, value: number = 1) {
|
async decrement(name: string, value: number = 1) {
|
||||||
if (!this.profileId) {
|
if (!this.profileId) {
|
||||||
this.logger('Mixan: Decrement failed, no profileId');
|
this.logger('Mixan: Decrement failed, no profileId')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger('Mixan: Decrement user property', name, value);
|
this.logger('Mixan: Decrement user property', name, value)
|
||||||
await this.fetch.post(`/profiles/${this.profileId}/decrement`, {
|
await this.fetch.post(
|
||||||
name,
|
`/profiles/${this.profileId}/decrement`,
|
||||||
value,
|
{
|
||||||
}, {
|
name,
|
||||||
method: 'PUT'
|
value,
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenView(route: string, _properties?: Record<string, any>) {
|
async screenView(route: string, _properties?: Record<string, any>) {
|
||||||
const properties = _properties ?? {}
|
const properties = _properties ?? {}
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
||||||
if(this.lastScreenViewAt) {
|
if (this.lastScreenViewAt) {
|
||||||
const last = new Date(this.lastScreenViewAt)
|
const last = new Date(this.lastScreenViewAt)
|
||||||
const diff = now.getTime() - last.getTime()
|
const diff = now.getTime() - last.getTime()
|
||||||
this.logger(`Mixan: Screen view duration: ${diff}ms`)
|
this.logger(`Mixan: Screen view duration: ${diff}ms`)
|
||||||
@@ -259,7 +306,7 @@ export class Mixan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.logger('Mixan: Clear, send remaining events and remove profileId');
|
this.logger('Mixan: Clear, send remaining events and remove profileId')
|
||||||
this.eventBatcher.send()
|
this.eventBatcher.send()
|
||||||
this.options.removeProfileId()
|
this.options.removeProfileId()
|
||||||
this.profileId = undefined
|
this.profileId = undefined
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mixan/types": "workspace:*",
|
"@mixan/types": "workspace:*"
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/uuid": "^9.0.5",
|
"@types/uuid": "^9.0.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user