@sky-mavis/tanto-widget
Version:
Tanto Widget
328 lines (324 loc) • 9.28 kB
JavaScript
'use strict';
var uuid = require('uuid');
var index = require('./constants/index.cjs');
var analytic$1 = require('./types/analytic.cjs');
var index$1 = require('./utils/index.cjs');
class AnalyticStorage {
static instance;
static MA_CONFIG = '__TANTO_MA_CONFIG';
static MA_DATA = '__TANTO_MA_DATA';
constructor() {}
static getInstance() {
if (!AnalyticStorage.instance) {
AnalyticStorage.instance = new AnalyticStorage();
}
return AnalyticStorage.instance;
}
setConfig(data) {
try {
sessionStorage.setItem(AnalyticStorage.MA_CONFIG, JSON.stringify(data));
} catch (error) {
console.debug('Failed to set config in session storage:', error);
}
}
getConfig() {
try {
const data = sessionStorage.getItem(AnalyticStorage.MA_CONFIG);
if (!data) {
return this.getDefaultConfig();
}
return JSON.parse(data);
} catch (error) {
console.debug('Failed to get config from session storage:', error);
return this.getDefaultConfig();
}
}
setData(data) {
try {
sessionStorage.setItem(AnalyticStorage.MA_DATA, JSON.stringify(data));
} catch (error) {
console.debug('Failed to set data in session storage:', error);
}
}
getData() {
try {
const data = sessionStorage.getItem(AnalyticStorage.MA_DATA);
if (!data) {
return {};
}
return JSON.parse(data);
} catch (error) {
console.debug('Failed to get data from session storage:', error);
return {};
}
}
clear() {
try {
sessionStorage.removeItem(AnalyticStorage.MA_CONFIG);
sessionStorage.removeItem(AnalyticStorage.MA_DATA);
} catch (error) {
console.debug('Failed to clear storage:', error);
}
}
getDefaultConfig() {
return {
sessionId: uuid.v4()
};
}
}
class PlatformDataCollector {
static instance;
userAgent;
constructor() {
this.userAgent = index$1.getUserAgent();
}
static getInstance() {
if (!PlatformDataCollector.instance) {
PlatformDataCollector.instance = new PlatformDataCollector();
}
return PlatformDataCollector.instance;
}
getPlatformData() {
return {
build_version: [this.userAgent?.browser.name, this.userAgent?.browser.version].filter(Boolean).join(' - ') || 'Unknown',
device_name: [this.userAgent?.device.vendor, this.userAgent?.device.model].filter(Boolean).join(' - ') || 'Unknown',
platform_name: this.userAgent?.os.name || 'Unknown',
platform_version: this.userAgent?.os.version || 'Unknown'
};
}
}
class Analytic {
static INTERNAL_EVENTS = ['heartbeat', 'identify'];
static BATCH_SIZE = 20;
static HEARTBEAT_INTERVAL = 2000;
static DEVICE_FINGERPRINT_KEY = '__TANTO_MA_DFP';
static FIRST_PARTY_DOMAINS = ['skymavis.com', 'skymavis.one', 'roninchain.com', 'axieinfinity.com'];
intervalId = null;
apiKey;
events;
storage;
platformDataCollector;
constructor(apiKey) {
if (!index$1.isClient()) {
return;
}
this.apiKey = apiKey;
this.events = [];
this.storage = AnalyticStorage.getInstance();
this.platformDataCollector = PlatformDataCollector.getInstance();
if (!localStorage.getItem(Analytic.DEVICE_FINGERPRINT_KEY)) {
localStorage.setItem(Analytic.DEVICE_FINGERPRINT_KEY, uuid.v4());
}
}
isFirstPartyDomain() {
if (!index$1.isClient()) {
return false;
}
if (Analytic.FIRST_PARTY_DOMAINS.includes(window.location.hostname)) {
return true;
}
return Analytic.FIRST_PARTY_DOMAINS.some(domain => window.location.hostname.endsWith(`.${domain}`));
}
updateSession(options) {
if (this.isFirstPartyDomain()) {
return;
}
const {
sessionId: currentSessionId,
userAddress: currentUserAddress
} = this.storage.getConfig();
const data = this.storage.getData();
const shouldUseCurrentSessionId = !options?.force && !!currentSessionId;
const sessionId = shouldUseCurrentSessionId ? currentSessionId : options?.sessionId || uuid.v4();
this.storage.setConfig({
userAddress: currentUserAddress || options?.userAddress,
sessionTimeout: options?.sessionTimeout,
sessionId
});
if (options?.resetLastEventRef) {
this.storage.setData({
...data,
lastEvent: undefined
});
}
if (!shouldUseCurrentSessionId) {
this.handleNewSession(options);
}
this.startHeartbeat();
}
handleNewSession(options) {
try {
if (!this.validate()) {
return;
}
this.resetSession();
this.trackEvents([{
type: analytic$1.AnalyticEventType.IDENTIFY,
data: {
event: 'identify',
...this.platformDataCollector.getPlatformData(),
user_properties: {
...(options?.commonProperties ?? {}),
build_version: '0.0.1',
app_origin: index$1.isClient() ? window.location.origin : undefined
},
...this.storage.getData(),
device_id: options?.deviceId || localStorage.getItem(Analytic.DEVICE_FINGERPRINT_KEY) || undefined
}
}], {
force: true
});
if (options?.sessionTimeout) {
this.storage.setData({
sessionCreatedAt: Date.now()
});
}
} catch (error) {
console.debug('Failed to handle new session:', error);
}
}
revoke() {
this.storage.clear();
this.stopHeartbeat();
}
startHeartbeat() {
if (!this.intervalId) {
this.intervalId = setInterval(() => {
this.sendEvent('heartbeat');
}, Analytic.HEARTBEAT_INTERVAL);
}
}
stopHeartbeat() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
async sendEvent(eventName, data = {}) {
try {
if (!this.validate()) return;
await this.trackEvents([{
type: analytic$1.AnalyticEventType.TRACK,
data: {
action_properties: {
...data,
build_version: '0.0.1',
app_origin: index$1.isClient() ? window.location.origin : undefined
},
action: eventName,
event: eventName
}
}], {
force: eventName !== 'heartbeat'
});
} catch (error) {
console.debug('Failed to send event:', error);
}
}
sendScreen(screen, data = {}) {
try {
if (!this.validate()) return;
this.trackEvents([{
type: analytic$1.AnalyticEventType.SCREEN,
data: {
screen,
screen_properties: {
...data,
build_version: '0.0.1',
app_origin: index$1.isClient() ? window.location.origin : undefined
}
}
}]);
} catch (error) {
console.debug('Failed to send screen:', error);
}
}
getBaseData() {
const data = this.storage.getData();
const config = this.storage.getConfig();
return {
uuid: uuid.v4(),
ref: data.lastEvent,
timestamp: new Date().toISOString().slice(0, 19).replace('T', ' '),
session_id: config.sessionId,
user_id: config.userAddress
};
}
async trackEvents(eventsData, options) {
if (this.isFirstPartyDomain()) {
return;
}
try {
const {
force = true
} = options || {};
const data = this.storage.getData();
const events = eventsData.map(event => ({
type: event.type,
data: {
...this.getBaseData(),
...this.platformDataCollector.getPlatformData(),
...event.data,
device_id: localStorage.getItem(Analytic.DEVICE_FINGERPRINT_KEY)
}
}));
this.events.push(...events);
if (force || this.events.length >= Analytic.BATCH_SIZE) {
await this.send(this.events);
this.events.length = 0;
}
const lastEvent = eventsData[eventsData.length - 1]?.data.event;
if (lastEvent && !Analytic.INTERNAL_EVENTS.includes(lastEvent)) {
this.storage.setData({
...data,
lastEvent
});
}
} catch (error) {
console.debug('Failed to track events:', error);
}
}
async send(events) {
return fetch('https://x.skymavis.com/track', {
method: 'POST',
headers: [['Authorization', `Basic ${btoa(`${this.apiKey}:`)}`], ['Content-Type', 'application/json']],
body: JSON.stringify({
events
})
});
}
resetSession() {
const config = this.storage.getConfig();
const data = this.storage.getData();
const {
sessionTimeout
} = config;
const {
sessionCreatedAt = 0
} = data;
if (sessionTimeout) {
const sessionExpiredAt = sessionCreatedAt + sessionTimeout * 1000;
const now = Date.now();
if (now >= sessionExpiredAt) {
this.storage.setData({
...data,
sessionCreatedAt: now
});
this.storage.setConfig({
...config,
sessionId: uuid.v4()
});
}
} else if (sessionCreatedAt) {
this.storage.setData({
...data,
sessionCreatedAt: undefined
});
}
}
validate() {
return Boolean(this.apiKey);
}
}
const analytic = new Analytic(index.ANALYTIC_PUBLIC_KEY);
exports.analytic = analytic;