@streamlayer/sdk-web-analytics 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/lib/analytics.d.ts +48 -0
- package/lib/analytics.js +121 -0
- package/lib/constants.d.ts +45 -0
- package/lib/constants.js +46 -0
- package/lib/heartbeat.d.ts +13 -0
- package/lib/heartbeat.js +59 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +11 -0
- package/lib/interactions.d.ts +7 -0
- package/lib/interactions.js +42 -0
- package/lib/invitation.d.ts +10 -0
- package/lib/invitation.js +41 -0
- package/lib/notifications.d.ts +7 -0
- package/lib/notifications.js +49 -0
- package/lib/polls.d.ts +25 -0
- package/lib/polls.js +93 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MapStore, StreamLayerContext, OnMountCb } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { Logger } from '@streamlayer/sdk-web-logger';
|
|
3
|
+
import { GamesMessage, NotificationMessage, SendInvitationsMessage, EventOpenedMessage, SendInteractionsMessage, CommonData } from '@streamlayer/sl-eslib/analytics/v2/streaming/streaming_pb';
|
|
4
|
+
import { PlainMessage } from '@bufbuild/protobuf';
|
|
5
|
+
import { InvitationAnalytics } from './invitation';
|
|
6
|
+
import { InteractionsAnalytics } from './interactions';
|
|
7
|
+
import { NotificationsAnalytics } from './notifications';
|
|
8
|
+
import { PollsAnalytics } from './polls';
|
|
9
|
+
import '@streamlayer/sdk-web-features';
|
|
10
|
+
type AnalyticsMessages = {
|
|
11
|
+
games?: PlainMessage<GamesMessage>;
|
|
12
|
+
notification?: PlainMessage<NotificationMessage>;
|
|
13
|
+
invitation?: PlainMessage<SendInvitationsMessage>;
|
|
14
|
+
eventOpened?: PlainMessage<EventOpenedMessage>;
|
|
15
|
+
interactions?: PlainMessage<SendInteractionsMessage>;
|
|
16
|
+
common: PlainMessage<CommonData>;
|
|
17
|
+
};
|
|
18
|
+
export declare const logger: Logger;
|
|
19
|
+
/**
|
|
20
|
+
* analytics events
|
|
21
|
+
* 1. start (on start we should send heartbeats, generate session id)
|
|
22
|
+
* 2. overlay open (on open we should update store, create overlay session id)
|
|
23
|
+
* 3. overlay close (on close we should update store, remove overlay session id)
|
|
24
|
+
* 4. switch event
|
|
25
|
+
* 5. on update screen orientation
|
|
26
|
+
* 6. on open question we should update topicId, topicType (???parentTopicId, ???topicSubType)
|
|
27
|
+
* 7. on close question we should clear topicId, topicType
|
|
28
|
+
*/
|
|
29
|
+
export declare class Analytics {
|
|
30
|
+
readonly commonStore: MapStore<AnalyticsMessages['common']>;
|
|
31
|
+
readonly notifications: NotificationsAnalytics;
|
|
32
|
+
readonly polls: PollsAnalytics;
|
|
33
|
+
readonly invitation: InvitationAnalytics;
|
|
34
|
+
readonly interactions: InteractionsAnalytics;
|
|
35
|
+
private heartbeat;
|
|
36
|
+
private analyticsClient;
|
|
37
|
+
private listeners;
|
|
38
|
+
private listenersCancels;
|
|
39
|
+
private connected;
|
|
40
|
+
constructor(instance: StreamLayerContext);
|
|
41
|
+
connect: () => void;
|
|
42
|
+
disconnect: () => void;
|
|
43
|
+
write: <T extends "invitation" | "notification" | "interactions" | "games" | "eventOpened" = "invitation" | "notification" | "interactions" | "games" | "eventOpened">(key: T, value: AnalyticsMessages[T]) => void;
|
|
44
|
+
writeCommon: <T extends "category" | "eventId" | "kind" | "screenOrientation" | "sessionId" | "overlaySessionId" | "topicId" | "topicType" | "parentTopicId" | "parentTopicType" | "participantsCount" | "routeMap" | "trackTimestamp" | "topicSubType" | "country" = "category" | "eventId" | "kind" | "screenOrientation" | "sessionId" | "overlaySessionId" | "topicId" | "topicType" | "parentTopicId" | "parentTopicType" | "participantsCount" | "routeMap" | "trackTimestamp" | "topicSubType" | "country">(key: T, value: PlainMessage<CommonData>[T]) => void;
|
|
45
|
+
onConnect: (cb: OnMountCb) => void;
|
|
46
|
+
private connectToSDK;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/lib/analytics.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createMapStore, MapStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { FeatureType } from '@streamlayer/sdk-web-types';
|
|
3
|
+
import { createLogger } from '@streamlayer/sdk-web-logger';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
import { ScreenOrientation } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
6
|
+
import { AnalyticsService } from '@streamlayer/sl-eslib/analytics/v2/streaming/streaming_connect';
|
|
7
|
+
import { InvitationAnalytics } from './invitation';
|
|
8
|
+
import { InteractionsAnalytics } from './interactions';
|
|
9
|
+
import { NotificationsAnalytics } from './notifications';
|
|
10
|
+
import { PollsAnalytics } from './polls';
|
|
11
|
+
import { heartbeat } from './heartbeat';
|
|
12
|
+
import { CATEGORY_TYPE_MAP } from './constants';
|
|
13
|
+
import '@streamlayer/sdk-web-features';
|
|
14
|
+
export const logger = createLogger('analytics');
|
|
15
|
+
// analytics events
|
|
16
|
+
/**
|
|
17
|
+
* analytics events
|
|
18
|
+
* 1. start (on start we should send heartbeats, generate session id)
|
|
19
|
+
* 2. overlay open (on open we should update store, create overlay session id)
|
|
20
|
+
* 3. overlay close (on close we should update store, remove overlay session id)
|
|
21
|
+
* 4. switch event
|
|
22
|
+
* 5. on update screen orientation
|
|
23
|
+
* 6. on open question we should update topicId, topicType (???parentTopicId, ???topicSubType)
|
|
24
|
+
* 7. on close question we should clear topicId, topicType
|
|
25
|
+
*/
|
|
26
|
+
export class Analytics {
|
|
27
|
+
commonStore;
|
|
28
|
+
notifications;
|
|
29
|
+
polls;
|
|
30
|
+
invitation;
|
|
31
|
+
interactions;
|
|
32
|
+
heartbeat;
|
|
33
|
+
analyticsClient;
|
|
34
|
+
listeners;
|
|
35
|
+
listenersCancels;
|
|
36
|
+
connected = false;
|
|
37
|
+
constructor(instance) {
|
|
38
|
+
this.listeners = new Set();
|
|
39
|
+
this.listenersCancels = new Set();
|
|
40
|
+
this.commonStore = new MapStore(createMapStore({}), 'common-analytics');
|
|
41
|
+
this.connectToSDK(instance);
|
|
42
|
+
this.heartbeat = heartbeat({ transport: instance.analyticsTransport, $commonStore: this.commonStore });
|
|
43
|
+
this.notifications = new NotificationsAnalytics(this);
|
|
44
|
+
this.polls = new PollsAnalytics(this);
|
|
45
|
+
this.invitation = new InvitationAnalytics(this);
|
|
46
|
+
this.interactions = new InteractionsAnalytics(this);
|
|
47
|
+
const { client } = instance.analyticsTransport.createPromiseClient(AnalyticsService, {
|
|
48
|
+
method: 'send',
|
|
49
|
+
});
|
|
50
|
+
this.analyticsClient = client;
|
|
51
|
+
}
|
|
52
|
+
connect = () => {
|
|
53
|
+
this.writeCommon('sessionId', nanoid());
|
|
54
|
+
this.heartbeat.enable();
|
|
55
|
+
this.listeners.forEach((cb) => this.listenersCancels.add(cb()));
|
|
56
|
+
this.connected = true;
|
|
57
|
+
logger.debug('analytics connected');
|
|
58
|
+
};
|
|
59
|
+
disconnect = () => {
|
|
60
|
+
this.writeCommon('sessionId', '');
|
|
61
|
+
this.writeCommon('overlaySessionId', '');
|
|
62
|
+
this.heartbeat.unmount();
|
|
63
|
+
this.listenersCancels.forEach((cancel) => cancel());
|
|
64
|
+
this.connected = false;
|
|
65
|
+
logger.debug('analytics disconnected');
|
|
66
|
+
};
|
|
67
|
+
write = (key, value) => {
|
|
68
|
+
if (value) {
|
|
69
|
+
this.analyticsClient
|
|
70
|
+
.unary({ message: { case: key, value } })
|
|
71
|
+
.then(() => {
|
|
72
|
+
logger.trace('send analytics success', key, value);
|
|
73
|
+
})
|
|
74
|
+
.catch((e) => {
|
|
75
|
+
logger.error(e, 'send analytics failed');
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
writeCommon = (key, value) => {
|
|
80
|
+
this.commonStore.setValue(key, value);
|
|
81
|
+
};
|
|
82
|
+
// calls the cb when connected/reconnected, callbacks return a cancel function to be called when disconnected
|
|
83
|
+
onConnect = (cb) => {
|
|
84
|
+
this.listeners.add(cb);
|
|
85
|
+
// if already connected, call the cb
|
|
86
|
+
if (this.connected) {
|
|
87
|
+
this.listenersCancels.add(cb());
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
connectToSDK = (instance) => {
|
|
91
|
+
this.onConnect(() => instance.stores.slStreamId.getAtomStore().subscribe((eventId) => {
|
|
92
|
+
this.writeCommon('eventId', eventId);
|
|
93
|
+
}));
|
|
94
|
+
this.onConnect(() => instance.sdk.getActiveFeature().subscribe((feature) => {
|
|
95
|
+
if (feature) {
|
|
96
|
+
this.writeCommon('category', CATEGORY_TYPE_MAP[feature]);
|
|
97
|
+
}
|
|
98
|
+
if (feature !== FeatureType.UNSET) {
|
|
99
|
+
this.writeCommon('overlaySessionId', nanoid());
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.writeCommon('overlaySessionId', '');
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
this.onConnect(() => {
|
|
106
|
+
const mediaQuery = window.matchMedia('(orientation: portrait)');
|
|
107
|
+
const handleOrientationChange = (e) => {
|
|
108
|
+
this.writeCommon('screenOrientation', e.matches ? ScreenOrientation.PORTRAIT : ScreenOrientation.LANDSCAPE);
|
|
109
|
+
};
|
|
110
|
+
mediaQuery.addEventListener('change', handleOrientationChange);
|
|
111
|
+
return () => {
|
|
112
|
+
mediaQuery.removeEventListener('change', handleOrientationChange);
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
this.onConnect(() => instance.deepLink.$store.subscribe((deepLink) => {
|
|
116
|
+
if (deepLink?.handled) {
|
|
117
|
+
this.invitation.accepted();
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TopicType, Category } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
2
|
+
import { NotificationKind } from '@streamlayer/sl-eslib/analytics/v1/notifications/notifications_pb';
|
|
3
|
+
export declare const AllowedCases: {
|
|
4
|
+
games: string;
|
|
5
|
+
notification: string;
|
|
6
|
+
invitation: string;
|
|
7
|
+
eventOpened: string;
|
|
8
|
+
interactions: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const TOPIC_TYPE_MAP: {
|
|
11
|
+
0: TopicType;
|
|
12
|
+
4: TopicType;
|
|
13
|
+
1: TopicType;
|
|
14
|
+
3: TopicType;
|
|
15
|
+
6: TopicType;
|
|
16
|
+
2: TopicType;
|
|
17
|
+
5: TopicType;
|
|
18
|
+
};
|
|
19
|
+
export declare const QUESTION_NOTIFICATION_KIND_MAP: {
|
|
20
|
+
0: NotificationKind;
|
|
21
|
+
4: NotificationKind;
|
|
22
|
+
1: NotificationKind;
|
|
23
|
+
3: NotificationKind;
|
|
24
|
+
6: NotificationKind;
|
|
25
|
+
2: NotificationKind;
|
|
26
|
+
5: NotificationKind;
|
|
27
|
+
};
|
|
28
|
+
export declare const CATEGORY_TYPE_MAP: {
|
|
29
|
+
0: Category;
|
|
30
|
+
1: Category;
|
|
31
|
+
2: Category;
|
|
32
|
+
3: Category;
|
|
33
|
+
4: Category;
|
|
34
|
+
5: Category;
|
|
35
|
+
6: Category;
|
|
36
|
+
7: Category;
|
|
37
|
+
8: Category;
|
|
38
|
+
9: Category;
|
|
39
|
+
10: Category;
|
|
40
|
+
11: Category;
|
|
41
|
+
12: Category;
|
|
42
|
+
13: Category;
|
|
43
|
+
14: Category;
|
|
44
|
+
15: Category;
|
|
45
|
+
};
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { QuestionType, FeatureType } from '@streamlayer/sdk-web-types';
|
|
2
|
+
import { TopicType, Category } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
3
|
+
import { NotificationKind } from '@streamlayer/sl-eslib/analytics/v1/notifications/notifications_pb';
|
|
4
|
+
export const AllowedCases = {
|
|
5
|
+
games: 'games',
|
|
6
|
+
notification: 'notification',
|
|
7
|
+
invitation: 'invitation',
|
|
8
|
+
eventOpened: 'eventOpened',
|
|
9
|
+
interactions: 'interactions',
|
|
10
|
+
};
|
|
11
|
+
export const TOPIC_TYPE_MAP = {
|
|
12
|
+
[QuestionType.UNSET]: TopicType.UNSET,
|
|
13
|
+
[QuestionType.FACTOID]: TopicType.POLLS_INSIGHT,
|
|
14
|
+
[QuestionType.POLL]: TopicType.POLLS_POLL,
|
|
15
|
+
[QuestionType.PREDICTION]: TopicType.POLLS_PREDICTION,
|
|
16
|
+
[QuestionType.PROMOTION]: TopicType.POLLS_PROMOTION,
|
|
17
|
+
[QuestionType.TRIVIA]: TopicType.POLLS_TRIVIA,
|
|
18
|
+
[QuestionType.TWEET]: TopicType.POLLS_TWITTER,
|
|
19
|
+
};
|
|
20
|
+
export const QUESTION_NOTIFICATION_KIND_MAP = {
|
|
21
|
+
[QuestionType.UNSET]: NotificationKind.UNSET,
|
|
22
|
+
[QuestionType.FACTOID]: NotificationKind.POLLS,
|
|
23
|
+
[QuestionType.POLL]: NotificationKind.POLLS,
|
|
24
|
+
[QuestionType.PREDICTION]: NotificationKind.POLLS,
|
|
25
|
+
[QuestionType.PROMOTION]: NotificationKind.POLLS,
|
|
26
|
+
[QuestionType.TRIVIA]: NotificationKind.POLLS,
|
|
27
|
+
[QuestionType.TWEET]: NotificationKind.POLLS,
|
|
28
|
+
};
|
|
29
|
+
export const CATEGORY_TYPE_MAP = {
|
|
30
|
+
[FeatureType.UNSET]: Category.UNSET,
|
|
31
|
+
[FeatureType.INPLAY]: Category.INPLAY,
|
|
32
|
+
[FeatureType.MESSAGING]: Category.MESSAGING,
|
|
33
|
+
[FeatureType.STATISTICS]: Category.STATS,
|
|
34
|
+
[FeatureType.BETTING]: Category.BETTING,
|
|
35
|
+
[FeatureType.TWITTER]: Category.TWITTER,
|
|
36
|
+
[FeatureType.MERCHANDISE]: Category.MERCHANDISE,
|
|
37
|
+
[FeatureType.CONTESTS]: Category.CONTESTS,
|
|
38
|
+
[FeatureType.VOICE_CHAT]: Category.CALLING,
|
|
39
|
+
[FeatureType.TICKETS]: Category.TICKETS,
|
|
40
|
+
[FeatureType.WHOIS_WATCHING]: Category.WHOISWATCHING,
|
|
41
|
+
[FeatureType.PROFILE]: Category.PROFILE,
|
|
42
|
+
[FeatureType.GAMES]: Category.POLLS, // ??
|
|
43
|
+
[FeatureType.GOLF_STATISTICS]: Category.STATS, // ??
|
|
44
|
+
[FeatureType.HIGHLIGHTS]: Category.HIGHLIGHTS,
|
|
45
|
+
[FeatureType.PUBLIC_CHAT]: Category.PUBLIC_CHAT,
|
|
46
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Transport } from '@streamlayer/sdk-web-api';
|
|
2
|
+
import { Analytics } from './analytics';
|
|
3
|
+
type IHeartbeat = {
|
|
4
|
+
transport: Transport;
|
|
5
|
+
$commonStore: Analytics['commonStore'];
|
|
6
|
+
};
|
|
7
|
+
export declare const heartbeat: ({ transport, $commonStore }: IHeartbeat) => {
|
|
8
|
+
enable: () => void;
|
|
9
|
+
disable: () => void;
|
|
10
|
+
mount: () => void;
|
|
11
|
+
unmount: () => void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
package/lib/heartbeat.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createSingleStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { AnalyticsService } from '@streamlayer/sl-eslib/analytics/v2/streaming/streaming_connect';
|
|
3
|
+
import { Kind } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
4
|
+
import { logger } from './analytics';
|
|
5
|
+
export const heartbeat = ({ transport, $commonStore }) => {
|
|
6
|
+
let cancelFn = undefined;
|
|
7
|
+
const enabled = createSingleStore(false);
|
|
8
|
+
const refetchInterval = 5000;
|
|
9
|
+
const { client, queryKey } = transport.createPromiseClient(AnalyticsService, {
|
|
10
|
+
method: 'send',
|
|
11
|
+
params: [enabled],
|
|
12
|
+
});
|
|
13
|
+
const $api = transport.nanoquery.createFetcherStore(queryKey, {
|
|
14
|
+
fetcher: async () => {
|
|
15
|
+
const common = $commonStore.getStore().get();
|
|
16
|
+
try {
|
|
17
|
+
await client.unary({
|
|
18
|
+
message: {
|
|
19
|
+
case: 'heartbeat',
|
|
20
|
+
value: {
|
|
21
|
+
common: {
|
|
22
|
+
...common,
|
|
23
|
+
kind: Kind.SESSION_HEARTBEAT,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
logger.error(e, 'send heartbeat failed');
|
|
31
|
+
}
|
|
32
|
+
return Date.now();
|
|
33
|
+
},
|
|
34
|
+
refetchInterval,
|
|
35
|
+
});
|
|
36
|
+
const mount = () => {
|
|
37
|
+
logger.debug('mounted heartbeat');
|
|
38
|
+
cancelFn = $api.subscribe(() => { });
|
|
39
|
+
};
|
|
40
|
+
const enable = () => {
|
|
41
|
+
logger.debug('enabled heartbeat');
|
|
42
|
+
enabled.set(true);
|
|
43
|
+
if ($api.lc === 0) {
|
|
44
|
+
mount();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const disable = () => {
|
|
48
|
+
logger.debug('disabled heartbeat');
|
|
49
|
+
enabled.set(false);
|
|
50
|
+
};
|
|
51
|
+
const unmount = () => {
|
|
52
|
+
logger.debug('unmounted heartbeat');
|
|
53
|
+
$api.off();
|
|
54
|
+
cancelFn?.();
|
|
55
|
+
cancelFn = undefined;
|
|
56
|
+
disable();
|
|
57
|
+
};
|
|
58
|
+
return { enable, disable, mount, unmount };
|
|
59
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import type { DeepLinkContext } from '@streamlayer/sdk-web-core';
|
|
3
|
+
import { Transport } from '@streamlayer/sdk-web-api';
|
|
4
|
+
import { Analytics } from './analytics';
|
|
5
|
+
declare module '@streamlayer/sdk-web-interfaces' {
|
|
6
|
+
interface StreamLayerContext {
|
|
7
|
+
analytics: Analytics;
|
|
8
|
+
deepLink: DeepLinkContext;
|
|
9
|
+
analyticsTransport: Transport;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export declare const analytics: (instance: StreamLayerContext, opts: unknown, done: Function) => void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Analytics } from './analytics';
|
|
2
|
+
export const analytics = (instance, opts, done) => {
|
|
3
|
+
instance.analytics = new Analytics(instance);
|
|
4
|
+
instance.sdk.onMount(() => {
|
|
5
|
+
instance.analytics.connect();
|
|
6
|
+
return () => {
|
|
7
|
+
instance.analytics.disconnect();
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
done();
|
|
11
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { InteractionActionType } from '@streamlayer/sl-eslib/analytics/v1/interactions/interactions_pb';
|
|
3
|
+
import { Kind } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
4
|
+
export class InteractionsAnalytics {
|
|
5
|
+
analytics;
|
|
6
|
+
constructor(analytics) {
|
|
7
|
+
this.analytics = analytics;
|
|
8
|
+
this.analytics.onConnect(() => {
|
|
9
|
+
const listener = (event) => {
|
|
10
|
+
if (event.slEventBus?.type === 'interactions') {
|
|
11
|
+
switch (event.slEventBus.action) {
|
|
12
|
+
case 'tap':
|
|
13
|
+
this.tap();
|
|
14
|
+
break;
|
|
15
|
+
case 'scroll':
|
|
16
|
+
this.scroll();
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return eventBus.listen(listener);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
tap = () => {
|
|
25
|
+
this.analytics.write('interactions', {
|
|
26
|
+
common: {
|
|
27
|
+
...this.analytics.commonStore.getValues(),
|
|
28
|
+
kind: Kind.INTERACTIONS,
|
|
29
|
+
},
|
|
30
|
+
actionType: InteractionActionType.TAP,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
scroll = () => {
|
|
34
|
+
this.analytics.write('interactions', {
|
|
35
|
+
common: {
|
|
36
|
+
...this.analytics.commonStore.getValues(),
|
|
37
|
+
kind: Kind.INTERACTIONS,
|
|
38
|
+
},
|
|
39
|
+
actionType: InteractionActionType.SCROLL,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InvitationFrom } from '@streamlayer/sl-eslib/analytics/v1/invitations/invitations_pb';
|
|
2
|
+
import { Analytics } from './analytics';
|
|
3
|
+
export declare class InvitationAnalytics {
|
|
4
|
+
private analytics;
|
|
5
|
+
constructor(analytics: Analytics);
|
|
6
|
+
accepted: () => void;
|
|
7
|
+
sent: ({ from }: {
|
|
8
|
+
from?: InvitationFrom | undefined;
|
|
9
|
+
}) => void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { Kind } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
3
|
+
import { InvitationCategory, InvitationFrom } from '@streamlayer/sl-eslib/analytics/v1/invitations/invitations_pb';
|
|
4
|
+
export class InvitationAnalytics {
|
|
5
|
+
analytics;
|
|
6
|
+
constructor(analytics) {
|
|
7
|
+
this.analytics = analytics;
|
|
8
|
+
this.analytics.onConnect(() => {
|
|
9
|
+
const listener = (event) => {
|
|
10
|
+
if (event.slEventBus?.type === 'invitation') {
|
|
11
|
+
switch (event.slEventBus.action) {
|
|
12
|
+
case 'sent':
|
|
13
|
+
this.sent(event.slEventBus.payload);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return eventBus.listen(listener);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
accepted = () => {
|
|
22
|
+
this.analytics.write('invitation', {
|
|
23
|
+
common: {
|
|
24
|
+
...this.analytics.commonStore.getValues(),
|
|
25
|
+
kind: Kind.INVITATION_ACCEPTED,
|
|
26
|
+
},
|
|
27
|
+
category: InvitationCategory.GAMES,
|
|
28
|
+
from: InvitationFrom.UNSET,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
sent = ({ from }) => {
|
|
32
|
+
this.analytics.write('invitation', {
|
|
33
|
+
common: {
|
|
34
|
+
...this.analytics.commonStore.getValues(),
|
|
35
|
+
kind: Kind.INVITATION_SENT,
|
|
36
|
+
},
|
|
37
|
+
category: InvitationCategory.GAMES,
|
|
38
|
+
from: from ? from : InvitationFrom.UNSET,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { Kind, TopicType } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
3
|
+
import { NotificationKind, NotificationType } from '@streamlayer/sl-eslib/analytics/v1/notifications/notifications_pb';
|
|
4
|
+
import { TOPIC_TYPE_MAP } from './constants';
|
|
5
|
+
export class NotificationsAnalytics {
|
|
6
|
+
analytics;
|
|
7
|
+
constructor(analytics) {
|
|
8
|
+
this.analytics = analytics;
|
|
9
|
+
this.analytics.onConnect(() => {
|
|
10
|
+
const listener = (event) => {
|
|
11
|
+
if (event.slEventBus?.type === 'notification') {
|
|
12
|
+
switch (event.slEventBus.action) {
|
|
13
|
+
case 'rendered':
|
|
14
|
+
this.received(event.slEventBus.payload);
|
|
15
|
+
break;
|
|
16
|
+
case 'opened':
|
|
17
|
+
this.opened(event.slEventBus.payload);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return eventBus.listen(listener);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
received = ({ questionId, questionType }) => {
|
|
26
|
+
this.analytics.write('notification', {
|
|
27
|
+
common: {
|
|
28
|
+
...this.analytics.commonStore.getValues(),
|
|
29
|
+
kind: Kind.NOTIFICATION_RECEIVED,
|
|
30
|
+
topicId: questionId,
|
|
31
|
+
topicType: questionType ? TOPIC_TYPE_MAP[questionType] : TopicType.UNSET,
|
|
32
|
+
},
|
|
33
|
+
notificationKind: NotificationKind.POLLS,
|
|
34
|
+
notificationType: NotificationType.IN_APP,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
opened = ({ questionId, questionType }) => {
|
|
38
|
+
this.analytics.write('notification', {
|
|
39
|
+
common: {
|
|
40
|
+
...this.analytics.commonStore.getValues(),
|
|
41
|
+
kind: Kind.NOTIFICATION_OPENED,
|
|
42
|
+
topicId: questionId,
|
|
43
|
+
topicType: questionType ? TOPIC_TYPE_MAP[questionType] : TopicType.UNSET,
|
|
44
|
+
},
|
|
45
|
+
notificationKind: NotificationKind.POLLS,
|
|
46
|
+
notificationType: NotificationType.IN_APP,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
}
|
package/lib/polls.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { QuestionType } from '@streamlayer/sdk-web-types';
|
|
2
|
+
import { Analytics } from './analytics';
|
|
3
|
+
/**
|
|
4
|
+
* PollsAnalytics is a class that is used to send analytics events related to polls.
|
|
5
|
+
* It is used to send events when a poll is received, opened, voted, onboarding passed, or navigated.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PollsAnalytics {
|
|
8
|
+
private analytics;
|
|
9
|
+
constructor(analytics: Analytics);
|
|
10
|
+
received: ({ questionId, questionType }: {
|
|
11
|
+
questionId?: string | undefined;
|
|
12
|
+
questionType?: QuestionType | undefined;
|
|
13
|
+
}) => void;
|
|
14
|
+
opened: ({ questionId, questionType, questionOpenedFrom, }: {
|
|
15
|
+
questionId?: string | undefined;
|
|
16
|
+
questionType?: QuestionType | undefined;
|
|
17
|
+
questionOpenedFrom?: "notification" | "list" | undefined;
|
|
18
|
+
}) => void;
|
|
19
|
+
voted: ({ questionId, questionType }: {
|
|
20
|
+
questionId?: string | undefined;
|
|
21
|
+
questionType?: QuestionType | undefined;
|
|
22
|
+
}) => void;
|
|
23
|
+
onboardingPassed: () => void;
|
|
24
|
+
navigated: () => void;
|
|
25
|
+
}
|
package/lib/polls.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { Kind, PollOpenedFrom, TopicType } from '@streamlayer/sl-eslib/analytics/v1/common/analytics.common_pb';
|
|
3
|
+
import { TOPIC_TYPE_MAP } from './constants';
|
|
4
|
+
/**
|
|
5
|
+
* PollsAnalytics is a class that is used to send analytics events related to polls.
|
|
6
|
+
* It is used to send events when a poll is received, opened, voted, onboarding passed, or navigated.
|
|
7
|
+
*/
|
|
8
|
+
export class PollsAnalytics {
|
|
9
|
+
analytics;
|
|
10
|
+
constructor(analytics) {
|
|
11
|
+
this.analytics = analytics;
|
|
12
|
+
this.analytics.onConnect(() => {
|
|
13
|
+
const listener = (event) => {
|
|
14
|
+
if (event.slEventBus?.type === 'poll') {
|
|
15
|
+
switch (event.slEventBus.action) {
|
|
16
|
+
case 'voted':
|
|
17
|
+
this.voted(event.slEventBus.payload);
|
|
18
|
+
break;
|
|
19
|
+
case 'navigated':
|
|
20
|
+
this.navigated();
|
|
21
|
+
break;
|
|
22
|
+
case 'onboardingPassed':
|
|
23
|
+
this.onboardingPassed();
|
|
24
|
+
break;
|
|
25
|
+
case 'received':
|
|
26
|
+
this.received(event.slEventBus.payload);
|
|
27
|
+
break;
|
|
28
|
+
case 'opened':
|
|
29
|
+
this.opened(event.slEventBus.payload);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return eventBus.listen(listener);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// event when a poll is received from subscription or active question request
|
|
38
|
+
received = ({ questionId, questionType }) => {
|
|
39
|
+
this.analytics.write('games', {
|
|
40
|
+
common: {
|
|
41
|
+
...this.analytics.commonStore.getValues(),
|
|
42
|
+
kind: Kind.POLLS_RECEIVED,
|
|
43
|
+
topicId: questionId,
|
|
44
|
+
topicType: questionType ? TOPIC_TYPE_MAP[questionType] : TopicType.UNSET,
|
|
45
|
+
},
|
|
46
|
+
pollOpenedFrom: PollOpenedFrom.UNSET,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
// poll is opened from notification or list
|
|
50
|
+
opened = ({ questionId, questionType, questionOpenedFrom, }) => {
|
|
51
|
+
this.analytics.write('games', {
|
|
52
|
+
common: {
|
|
53
|
+
...this.analytics.commonStore.getValues(),
|
|
54
|
+
kind: Kind.POLLS_OPENED,
|
|
55
|
+
topicId: questionId,
|
|
56
|
+
topicType: questionType ? TOPIC_TYPE_MAP[questionType] : TopicType.UNSET,
|
|
57
|
+
},
|
|
58
|
+
pollOpenedFrom: questionOpenedFrom === 'list' ? PollOpenedFrom.PICK_HISTORY : PollOpenedFrom.IN_APP,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
// poll is voted
|
|
62
|
+
voted = ({ questionId, questionType }) => {
|
|
63
|
+
this.analytics.write('games', {
|
|
64
|
+
common: {
|
|
65
|
+
...this.analytics.commonStore.getValues(),
|
|
66
|
+
kind: Kind.POLLS_VOTE,
|
|
67
|
+
topicId: questionId,
|
|
68
|
+
topicType: questionType ? TOPIC_TYPE_MAP[questionType] : TopicType.UNSET,
|
|
69
|
+
},
|
|
70
|
+
pollOpenedFrom: PollOpenedFrom.UNSET,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
// onboarding passed, user is now able to vote
|
|
74
|
+
onboardingPassed = () => {
|
|
75
|
+
this.analytics.write('games', {
|
|
76
|
+
common: {
|
|
77
|
+
...this.analytics.commonStore.getValues(),
|
|
78
|
+
kind: Kind.GAMES_JOINED,
|
|
79
|
+
},
|
|
80
|
+
pollOpenedFrom: PollOpenedFrom.UNSET,
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
// user clicked on link in poll and navigated to another page
|
|
84
|
+
navigated = () => {
|
|
85
|
+
this.analytics.write('games', {
|
|
86
|
+
common: {
|
|
87
|
+
...this.analytics.commonStore.getValues(),
|
|
88
|
+
kind: Kind.POLLS_NAVIGATED,
|
|
89
|
+
},
|
|
90
|
+
pollOpenedFrom: PollOpenedFrom.UNSET,
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@streamlayer/sdk-web-analytics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"typings": "./lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/",
|
|
9
|
+
"package.json"
|
|
10
|
+
],
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@bufbuild/protobuf": "^1.7.2",
|
|
13
|
+
"@connectrpc/connect": "^1.3.0",
|
|
14
|
+
"nanoid": "3.3.7",
|
|
15
|
+
"@streamlayer/sl-eslib": "^5.83.1",
|
|
16
|
+
"@streamlayer/sdk-web-api": "^0.24.0",
|
|
17
|
+
"@streamlayer/sdk-web-interfaces": "^0.21.0",
|
|
18
|
+
"@streamlayer/sdk-web-logger": "^0.5.18",
|
|
19
|
+
"@streamlayer/sdk-web-features": "^0.11.24",
|
|
20
|
+
"@streamlayer/sdk-web-core": "^0.22.0",
|
|
21
|
+
"@streamlayer/feature-gamification": "^0.39.0"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"module": "./lib/index.js",
|
|
26
|
+
"require": "./lib/index.js",
|
|
27
|
+
"types": "./lib/index.d.ts"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|