-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Started analytics * convert track to async function * Base track custom event * do not export * Refactor into a class * times are strings * implement id generation * Work on cacheing * add tdk versions * retrying logic * auto retry submit events * storage test * Add README * Add analytics manager to treasure context * add uuid types * fix variable name * auto set smart account address * Track custom event does not take smart account address * move types to own file * Add example for react * type fix * Tracking on login in the react example * Fix react example * Added changeset * README update * minor fix * move types to devDeps * rename xApiKey to apiKey * make AnalyticsManager a singleton * Update param and use singleton in react * fix warning * fix types * auto remove events from retrying if older than 7 days
- Loading branch information
1 parent
6eb6c21
commit 399d230
Showing
21 changed files
with
826 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@treasure-dev/tdk-react": minor | ||
"@treasure-dev/tdk-core": minor | ||
--- | ||
|
||
Added support for analytics |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import pjson from "../../package.json"; | ||
import { DEFAULT_TDK_DARKMATTER_BASE_URI } from "../constants"; | ||
import { | ||
addCachedEvent, | ||
clearCachedEvents, | ||
getCachedEvents, | ||
removeOldEvents, | ||
} from "./storage"; | ||
import type { AnalyticsPayload, AppInfo, TrackableEvent } from "./types"; | ||
import { getEventId, getServerTime } from "./utils"; | ||
|
||
export class AnalyticsManager { | ||
static _instance: AnalyticsManager; | ||
|
||
initialized = false; | ||
|
||
apiUri!: string; | ||
|
||
apiKey!: string; | ||
|
||
app!: AppInfo; | ||
|
||
private constructor() {} | ||
|
||
public static get instance(): AnalyticsManager { | ||
if (!AnalyticsManager._instance) { | ||
AnalyticsManager._instance = new AnalyticsManager(); | ||
} | ||
|
||
return AnalyticsManager._instance; | ||
} | ||
|
||
public init({ | ||
apiUri = DEFAULT_TDK_DARKMATTER_BASE_URI, | ||
apiKey, | ||
app, | ||
}: { apiUri?: string; apiKey: string; app: AppInfo }) { | ||
this.apiUri = apiUri; | ||
this.apiKey = apiKey; | ||
this.app = app; | ||
this.initialized = true; | ||
|
||
setInterval( | ||
() => { | ||
this.retryAllCachedEvents(); | ||
}, | ||
1000 * 60 * 5, | ||
); | ||
} | ||
|
||
/** | ||
* Submits an array of payloads to the Analytics API. | ||
* | ||
* @param {AnalyticsPayload[]} payload - The payloads to submit. | ||
* @returns {Promise<void>} - A promise that resolves when the payloads have been submitted. | ||
*/ | ||
async submitPayload(payload: AnalyticsPayload[]): Promise<void> { | ||
if (!this.initialized) { | ||
throw new Error("AnalyticsManager is not initialized"); | ||
} | ||
const response = await fetch(`${this.apiUri}/ingress/events`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
"X-API-Key": this.apiKey, | ||
}, | ||
body: JSON.stringify(payload), | ||
}); | ||
if (!response.ok) { | ||
throw new Error(`Failed to track custom event: ${response.status}`); | ||
} | ||
} | ||
|
||
/** | ||
* Tracks a custom event. | ||
* | ||
* @param {TrackableEvent} event - The event to track. | ||
* @param {boolean} cacheOnFailure - Whether to cache the event on failure. | ||
* @returns {Promise<string>} - A promise that resolves with the newly created event's unique ID. | ||
*/ | ||
async trackCustomEvent( | ||
event: TrackableEvent, | ||
cacheOnFailure = true, | ||
): Promise<string> { | ||
const serverTime = await getServerTime(this.apiUri); | ||
const localTime = `${Date.now()}`; | ||
const eventId = getEventId(); | ||
const payload: AnalyticsPayload = { | ||
...event, | ||
id: eventId, | ||
time_server: serverTime, | ||
time_local: localTime, | ||
app: this.app, | ||
tdk_flavour: "tdk-js", | ||
tdk_version: pjson.version, | ||
}; | ||
|
||
try { | ||
await this.submitPayload([payload]); | ||
return eventId; | ||
} catch (err) { | ||
console.error("Error tracking custom event:", err); | ||
if (cacheOnFailure) { | ||
addCachedEvent(payload); | ||
} | ||
throw err; | ||
} | ||
} | ||
|
||
async retryAllCachedEvents() { | ||
const cachedEvents = getCachedEvents(); | ||
if (cachedEvents.length === 0) { | ||
return; | ||
} | ||
try { | ||
await this.submitPayload(cachedEvents); | ||
clearCachedEvents(); | ||
} catch (err) { | ||
console.error("Error retrying cached events:", err); | ||
removeOldEvents(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# TDK Core Analytics | ||
|
||
Module for interacting with Treasure’s Data Platform (codename Darkmatter) — a powerful, scalable and real-time streaming analytics service that allows developers to collect data from games. | ||
|
||
## Installation | ||
|
||
```bash | ||
pnpm add @treasure-dev/tdk-core | ||
``` | ||
|
||
## Usage | ||
|
||
```typescript | ||
import { AnalyticsManager } from "@treasure-dev/tdk-core"; | ||
|
||
AnalyticsManager.instance.init({ | ||
apiUri: "{DARKMATTER_API_BASE_URI}", | ||
xApiKey: "YOUR_X_API_KEY", | ||
app: { | ||
app_identifier: "YOUR_APP_IDENTIFIER", | ||
app_version: "YOUR_APP_VERSION", | ||
app_environment: 0, // 0 for dev, 1 for prod | ||
}, | ||
}); | ||
|
||
// Track a custom event | ||
await AnalyticsManager.instance.trackCustomEvent({ | ||
smart_account: "YOUR_SMART_ACCOUNT_ADDRESS", // And/or `user_id` | ||
cartridge_tag: "YOUR_CARTRIDGE_TAG", | ||
name: "YOUR_EVENT_NAME", | ||
properties: { | ||
// Add any additional properties here | ||
}, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { | ||
addCachedEvent, | ||
clearCachedEvents, | ||
getCachedEvents, | ||
removeOldEvents, | ||
} from "./storage"; | ||
import type { AnalyticsPayload } from "./types"; | ||
|
||
describe("analytics storage", () => { | ||
it("adds and gets cached events", () => { | ||
const payload: AnalyticsPayload = { | ||
id: "test-id", | ||
cartridge_tag: "test-cartridge-tag", | ||
name: "test-name", | ||
op: "upsert", | ||
properties: { | ||
test: "test-value", | ||
}, | ||
time_server: "test-server-time", | ||
time_local: "test-local-time", | ||
app: { | ||
app_identifier: "test-app-name", | ||
app_version: "test-app-version", | ||
app_environment: 0, | ||
}, | ||
smart_account: "test-smart-account", | ||
tdk_flavour: "tdk-js", | ||
tdk_version: "test-tdk-version", | ||
}; | ||
addCachedEvent(payload); | ||
expect(getCachedEvents()).toEqual([payload]); | ||
clearCachedEvents(); | ||
expect(getCachedEvents()).toEqual([]); | ||
}); | ||
|
||
it("removes old events", () => { | ||
const payload: AnalyticsPayload = { | ||
id: "test-id", | ||
cartridge_tag: "test-cartridge-tag", | ||
name: "test-name", | ||
op: "upsert", | ||
properties: { | ||
test: "test-value", | ||
}, | ||
time_server: `${Date.now() - 1000 * 60 * 60 * 24 * 14}`, // 14 days ago | ||
time_local: `${Date.now() - 1000 * 60 * 60 * 24 * 14}`, | ||
app: { | ||
app_identifier: "test-app-name", | ||
app_version: "test-app-version", | ||
app_environment: 0, | ||
}, | ||
smart_account: "test-smart-account", | ||
tdk_flavour: "tdk-js", | ||
tdk_version: "test-tdk-version", | ||
}; | ||
addCachedEvent(payload); | ||
expect(getCachedEvents()).toEqual([payload]); | ||
removeOldEvents(); | ||
expect(getCachedEvents()).toEqual([]); | ||
}); | ||
}); |
Oops, something went wrong.