@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 ADDED
@@ -0,0 +1,3 @@
1
+ # sdk-web-analytics
2
+
3
+ This library was generated with [Nx](https://nx.dev).
@@ -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 {};
@@ -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
+ };
@@ -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 {};
@@ -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,7 @@
1
+ import { Analytics } from './analytics';
2
+ export declare class InteractionsAnalytics {
3
+ private analytics;
4
+ constructor(analytics: Analytics);
5
+ tap: () => void;
6
+ scroll: () => void;
7
+ }
@@ -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,7 @@
1
+ import { Analytics } from './analytics';
2
+ export declare class NotificationsAnalytics {
3
+ private analytics;
4
+ constructor(analytics: Analytics);
5
+ private received;
6
+ private opened;
7
+ }
@@ -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
+ }