@streamlayer/feature-gamification 1.14.1 → 1.15.1

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.
@@ -6,6 +6,7 @@ import { ReadableAtom, WritableAtom } from 'nanostores';
6
6
  import * as queries from './queries';
7
7
  import { detail } from './detail';
8
8
  import { advertisement } from './advertisement';
9
+ import { GamificationStorage } from './storage';
9
10
  export declare enum InteractiveAllowed {
10
11
  ALLOWED = "allowed",
11
12
  DISALLOWED = "disallowed"
@@ -47,6 +48,7 @@ export declare class GamificationBackground {
47
48
  /** subscription to opened question (vote percentage) */
48
49
  questionSubscription?: ReturnType<typeof queries.questionSubscription>;
49
50
  advertisement: ReturnType<typeof advertisement>;
51
+ storage: GamificationStorage;
50
52
  private notifications;
51
53
  private log;
52
54
  private transport;
package/lib/background.js CHANGED
@@ -5,6 +5,7 @@ import '@streamlayer/sdk-web-core/store';
5
5
  import * as queries from './queries';
6
6
  import { detail } from './detail';
7
7
  import { advertisement } from './advertisement';
8
+ import { GamificationStorage } from './storage';
8
9
  export var InteractiveAllowed;
9
10
  (function (InteractiveAllowed) {
10
11
  InteractiveAllowed["ALLOWED"] = "allowed";
@@ -42,6 +43,7 @@ export class GamificationBackground {
42
43
  /** subscription to opened question (vote percentage) */
43
44
  questionSubscription;
44
45
  advertisement;
46
+ storage;
45
47
  notifications;
46
48
  log;
47
49
  transport;
@@ -49,6 +51,7 @@ export class GamificationBackground {
49
51
  constructor(instance) {
50
52
  this.transport = instance.transport;
51
53
  this.log = createLogger('gamification-background');
54
+ this.storage = new GamificationStorage();
52
55
  this.slStreamId = instance.stores.slStreamId.getAtomStore();
53
56
  this.organizationId = instance.stores.organizationSettings.getAtomStore();
54
57
  this.userId = instance.stores.user.getAtomStore();
@@ -58,15 +61,34 @@ export class GamificationBackground {
58
61
  this.notifications = instance.notifications;
59
62
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
60
63
  this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, instance.transport), 'gamification:feedList');
61
- this.betPack = new ApiStore(queries.$betPack(this.slStreamId, instance.transport), 'gamification:betPack');
64
+ this.betPack = new ApiStore(queries.$betPack(this.slStreamId, this.userId, this.organizationId, this.storage, instance.transport), 'gamification:betPack');
62
65
  this.activeQuestionId = queries.$activeQuestion(this.slStreamId, instance.transport);
63
66
  this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
64
67
  this.cancels.add(this.openedQuestionId.listen((item) => {
65
68
  this.log.debug({ item }, 'received question');
66
69
  if (item?.questionId) {
67
70
  this.questionSubscription = queries.questionSubscription(item.questionId, instance.transport);
68
- this.questionSubscription.addListener('feed-subscription-opened-question', (response) => {
69
- this.openedQuestion.updateExtendedQuestion(response.data?.attributes?.question);
71
+ this.questionSubscription.addListener('feed-subscription-opened-question', async (response) => {
72
+ const question = response.data?.attributes?.question;
73
+ this.openedQuestion.updateExtendedQuestion(question);
74
+ if (question?.type === QuestionType.PREDICTION) {
75
+ const betPackData = this.betPack.getValues().data || {};
76
+ const betPackItem = betPackData?.[question.id];
77
+ if (betPackItem || Object.keys(betPackData).length < 5) {
78
+ const data = queries.$questionByUser(question.id, this.transport);
79
+ const cancel = data.subscribe(() => { });
80
+ await data.get().promise;
81
+ const extendedQuestion = await queries.questionByUser(question.id, this.transport);
82
+ cancel();
83
+ window.requestAnimationFrame(() => {
84
+ data.invalidate();
85
+ });
86
+ this.betPack.getStore().mutate({
87
+ ...betPackData,
88
+ [question.id]: extendedQuestion,
89
+ });
90
+ }
91
+ }
70
92
  });
71
93
  this.questionSubscription.connect();
72
94
  }
@@ -4,7 +4,7 @@ export const friendSummary = async ($eventId, $userId, $friends, friendId, trans
4
4
  const eventId = $eventId.get();
5
5
  const userId = $userId.get();
6
6
  const usersIds = $friends
7
- .getStore()
7
+ ?.getStore()
8
8
  .get()
9
9
  .data?.map((friend) => friend.slId) || [];
10
10
  const request = {
@@ -28,11 +28,11 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
28
28
  /** list (pack) of predefined questions */
29
29
  betPack: ApiStore<GetApiResponseType<typeof queries.$betPack>>;
30
30
  /** friends list */
31
- friends: ApiStore<GetApiResponseType<typeof queries.$friends>>;
31
+ friends?: ApiStore<GetApiResponseType<typeof queries.$friends>>;
32
32
  /** pinned leaderboard id */
33
33
  leaderboardId: WritableAtom<string | undefined>;
34
34
  /** leaderboard list */
35
- leaderboardList: ReturnType<typeof leaderboard>;
35
+ leaderboardList?: ReturnType<typeof leaderboard>;
36
36
  deepLink: ReturnType<typeof deepLink>;
37
37
  /** onboarding status */
38
38
  onboardingStatus: {
@@ -44,7 +44,7 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
44
44
  /** current user id */
45
45
  currentUserId: GamificationBackground['userId'];
46
46
  /** pinned leaderboard id */
47
- openedUser: WritableAtom<LeaderboardItem | undefined>;
47
+ openedUser?: WritableAtom<LeaderboardItem | undefined>;
48
48
  closeFeature: (destroy?: boolean) => void;
49
49
  openFeature: () => void;
50
50
  feedSubscription: GamificationBackground['feedSubscription'];
@@ -5,7 +5,6 @@ import { NotificationType } from '@streamlayer/sdk-web-notifications';
5
5
  import '@streamlayer/sdk-web-core/store';
6
6
  import * as queries from './queries';
7
7
  import * as actions from './queries/actions';
8
- import { GamificationStorage } from './storage';
9
8
  import { leaderboard } from './leaderboard';
10
9
  import { deepLink } from './deepLink';
11
10
  import { OnboardingStatus, onboarding } from './onboarding';
@@ -71,15 +70,15 @@ export class Gamification extends AbstractFeature {
71
70
  this.feedSubscription = this.background.feedSubscription;
72
71
  this.activeQuestionId = this.background.activeQuestionId;
73
72
  this.openedQuestionId = this.background.openedQuestionId;
74
- this.storage = new GamificationStorage();
73
+ this.storage = this.background.storage;
75
74
  this.feedList = this.background.feedList;
76
75
  this.betPack = this.background.betPack;
77
- this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
78
76
  this.currentUserId = this.background.userId;
79
- this.openedUser = createSingleStore(undefined);
80
77
  this.onboardingProcessed = createSingleStore(!instance.sdk.withAuth);
81
78
  this.leaderboardId = new SingleStore(createSingleStore(this.settings.getValue('pinnedLeaderboardId')), 'pinnedLeaderboardId').getStore();
82
- this.onboardingStatus = onboarding(this, this.background, instance.transport, instance.notifications);
79
+ this.onboardingStatus = onboarding(this, this.background, instance.transport, instance.notifications, {
80
+ skipOnboarding: instance.sdk.options.get().skipOnboarding,
81
+ });
83
82
  this.notifications = instance.notifications;
84
83
  this.transport = instance.transport;
85
84
  this.closeFeature = (destroy = true) => instance.sdk.closeFeature(destroy);
@@ -87,14 +86,18 @@ export class Gamification extends AbstractFeature {
87
86
  this.openedQuestion = this.background.openedQuestion;
88
87
  this.deepLink = deepLink(this.transport, this.background.slStreamId, instance.stores.providerStreamId.getStore(), this.background.userId);
89
88
  this.userSummary = summary(this.background.slStreamId, this.background.userId, this.friends, this.transport);
90
- this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends);
89
+ if (!instance.sdk.options.get().hideFriends) {
90
+ this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
91
+ this.openedUser = createSingleStore(undefined);
92
+ const leaderboardList = (this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends));
93
+ // refresh leaderboard on user summary update after earning points
94
+ this.cancels.add(this.userSummary.$store.listen((userSummary, prevValue) => {
95
+ if (prevValue?.summary && userSummary?.summary) {
96
+ leaderboardList.invalidate(); // verified, it's necessary
97
+ }
98
+ }));
99
+ }
91
100
  this.connect();
92
- // refresh leaderboard on user summary update after earning points
93
- this.cancels.add(this.userSummary.$store.listen((userSummary, prevValue) => {
94
- if (prevValue?.summary && userSummary?.summary) {
95
- this.leaderboardList.invalidate(); // verified, it's necessary
96
- }
97
- }));
98
101
  /**
99
102
  * listen for onboarding status, moderation onboarding changes and opt-in settings
100
103
  */
@@ -333,15 +336,6 @@ export class Gamification extends AbstractFeature {
333
336
  const betPackList = { ...this.betPack.getValues().data };
334
337
  const question = betPackList?.[questionId];
335
338
  if (question) {
336
- question.answers = betPackList[questionId].answers.map((answer) => {
337
- if (answer.id === answerId) {
338
- return {
339
- ...answer,
340
- youVoted: true,
341
- };
342
- }
343
- return answer;
344
- });
345
339
  eventBus.emit('poll', {
346
340
  action: 'voted',
347
341
  payload: {
@@ -349,10 +343,6 @@ export class Gamification extends AbstractFeature {
349
343
  questionType: question.type,
350
344
  },
351
345
  });
352
- this.betPack.getStore().mutate({
353
- ...betPackList,
354
- [questionId]: question,
355
- });
356
346
  }
357
347
  };
358
348
  submitAnswer = async (questionId, answerId) => {
@@ -444,14 +434,22 @@ export class Gamification extends AbstractFeature {
444
434
  const feedList = this.feedList.getStore().value?.data || [];
445
435
  questionType = feedList.find((item) => item.id === questionId)?.attributes?.type;
446
436
  }
447
- eventBus.emit('poll', {
448
- action: 'opened',
449
- payload: {
450
- questionId,
451
- questionType,
452
- questionOpenedFrom: question?.openedFrom,
453
- },
454
- });
437
+ const flags = {
438
+ eventId: this.background.slStreamId.get() || '',
439
+ userId: this.background.userId.get() || '',
440
+ organizationId: this.background.organizationId.get() || '',
441
+ };
442
+ if (!this.storage.isQuestionOpened(flags, questionId)) {
443
+ eventBus.emit('poll', {
444
+ action: 'opened',
445
+ payload: {
446
+ questionId,
447
+ questionType,
448
+ questionOpenedFrom: question?.openedFrom,
449
+ },
450
+ });
451
+ this.storage.saveQuestionOpened(flags, questionId);
452
+ }
455
453
  this.openFeature();
456
454
  return this.background.openQuestion(questionId, question);
457
455
  };
@@ -475,20 +473,23 @@ export class Gamification extends AbstractFeature {
475
473
  return this.background.closeQuestion(questionId);
476
474
  };
477
475
  openUser = async (friendId) => {
478
- const user = this.leaderboardList.$store.get().data?.find((item) => item.userId === friendId);
476
+ if (!this.leaderboardList) {
477
+ return;
478
+ }
479
+ const user = this.leaderboardList?.$store.get().data?.find((item) => item.userId === friendId);
479
480
  if (!user) {
480
- this.openedUser.set(user);
481
+ this.openedUser?.set(user);
481
482
  return;
482
483
  }
483
484
  if (user.summaryLoaded) {
484
- this.openedUser.set(user);
485
+ this.openedUser?.set(user);
485
486
  return;
486
487
  }
487
488
  const userCopy = { ...user };
488
489
  try {
489
490
  const friendDetail = await friendSummary(this.background.slStreamId, this.background.userId, this.friends, friendId, this.transport);
490
491
  if (friendDetail?.inTop !== undefined) {
491
- this.leaderboardList.$store.setKey('data', this.leaderboardList.$store.get().data?.map((item) => {
492
+ this.leaderboardList?.$store.setKey('data', this.leaderboardList.$store.get().data?.map((item) => {
492
493
  if (item.userId === friendId) {
493
494
  item.inTop = friendDetail.inTop;
494
495
  }
@@ -504,7 +505,7 @@ export class Gamification extends AbstractFeature {
504
505
  this.openedUser.set(userCopy);
505
506
  };
506
507
  closeUser = () => {
507
- this.openedUser.set(undefined);
508
+ this.openedUser?.set(undefined);
508
509
  };
509
510
  /**
510
511
  * Show in-app notification for active question
@@ -21,7 +21,7 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
21
21
  const refetch = async () => {
22
22
  const eventId = $eventId.get();
23
23
  const friendsIds = $friends
24
- .getStore()
24
+ ?.getStore()
25
25
  .get()
26
26
  .data?.map((friend) => friend.slId) || [];
27
27
  if (eventId && friendsIds?.length) {
@@ -65,7 +65,7 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
65
65
  };
66
66
  onMount($store, () => {
67
67
  const cancelRefetchListener = $eventId.listen(refetch);
68
- const cancelRefetchByFriendsListener = $friends.listen(refetch);
68
+ const cancelRefetchByFriendsListener = $friends?.listen(refetch);
69
69
  // const cancelPaginationListener = $pagination.listen(async (pagination) => {
70
70
  // const eventId = $eventId.get()
71
71
  // if (pagination.page > 0 && eventId) {
@@ -90,7 +90,7 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
90
90
  // })
91
91
  return () => {
92
92
  cancelRefetchListener();
93
- cancelRefetchByFriendsListener();
93
+ cancelRefetchByFriendsListener?.();
94
94
  // cancelPaginationListener()
95
95
  };
96
96
  });
@@ -17,7 +17,9 @@ export declare enum OnboardingStatus {
17
17
  Disabled = "disabled",
18
18
  Unavailable = "unavailable"
19
19
  }
20
- export declare const onboarding: (service: Gamification, background: GamificationBackground, transport: Transport, notifications: Notifications) => {
20
+ export declare const onboarding: (service: Gamification, background: GamificationBackground, transport: Transport, notifications: Notifications, options?: {
21
+ skipOnboarding?: boolean;
22
+ }) => {
21
23
  $store: import("nanostores").WritableAtom<OnboardingStatus>;
22
24
  submitInplay: () => Promise<void>;
23
25
  };
package/lib/onboarding.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { createSingleStore, eventBus } from '@streamlayer/sdk-web-interfaces';
2
2
  import { NotificationType } from '@streamlayer/sdk-web-notifications';
3
3
  import { QuestionType } from '@streamlayer/sdk-web-types';
4
- import { GamificationStorage } from './storage';
5
4
  import { submitInplay as submitInplayApi } from './queries/actions';
6
5
  /**
7
6
  * Required: in-app should be displayed and questions not available
@@ -52,22 +51,32 @@ const showOnboardingInApp = (service, background, notifications, storage) => {
52
51
  * check onboarding status, sync with browser cache
53
52
  * retrieve onboarding settings from api
54
53
  */
55
- const onboardingProcess = ($store, background, service, notifications, storage, listeners) => {
54
+ const onboardingProcess = ($store, background, service, notifications, storage, listeners, transport, options) => {
56
55
  try {
57
56
  const userId = background.userId.get();
58
- if (!userId) {
57
+ const organizationId = background.organizationId.get();
58
+ const eventId = background.slStreamId.get();
59
+ if (!userId || !organizationId || !eventId) {
59
60
  return;
60
61
  }
61
62
  const onboardingStatus = storage.getOnboardingStatus({
62
63
  userId,
63
- organizationId: background.organizationId.get() || '',
64
- eventId: background.slStreamId.get() || '',
64
+ organizationId,
65
+ eventId,
65
66
  });
66
- if (onboardingStatus === OnboardingStatus.Completed) {
67
+ if (onboardingStatus === OnboardingStatus.Completed || options?.skipOnboarding) {
67
68
  $store.set(OnboardingStatus.Completed);
68
69
  for (const cancel of listeners) {
69
70
  cancel();
70
71
  }
72
+ if (options?.skipOnboarding && onboardingStatus !== OnboardingStatus.Completed) {
73
+ storage.saveOnboardingStatus({
74
+ organizationId,
75
+ userId,
76
+ eventId,
77
+ }, OnboardingStatus.Completed);
78
+ void submitInplayApi(transport, background.slStreamId.get() || '');
79
+ }
71
80
  return;
72
81
  }
73
82
  const moderation = background.moderation.getStore().value?.data;
@@ -100,10 +109,10 @@ const onboardingProcess = ($store, background, service, notifications, storage,
100
109
  console.error(error);
101
110
  }
102
111
  };
103
- export const onboarding = (service, background, transport, notifications) => {
112
+ export const onboarding = (service, background, transport, notifications, options) => {
104
113
  // show onboarding each time when user open new sdk session
105
- let onboardingShowed = false;
106
- const storage = new GamificationStorage();
114
+ let onboardingShowed = !!options?.skipOnboarding;
115
+ const storage = background.storage;
107
116
  const $store = createSingleStore(OnboardingStatus.Unset);
108
117
  $store.subscribe((onboardingStatus) => {
109
118
  if (onboardingStatus === OnboardingStatus.Unset) {
@@ -128,24 +137,24 @@ export const onboarding = (service, background, transport, notifications) => {
128
137
  });
129
138
  });
130
139
  const listeners = [];
131
- onboardingProcess($store, background, service, notifications, storage, listeners);
132
140
  if ($store.get() !== OnboardingStatus.Completed) {
133
141
  listeners.push(background.userId.listen((userId) => {
134
142
  if (userId) {
135
- onboardingProcess($store, background, service, notifications, storage, listeners);
143
+ onboardingProcess($store, background, service, notifications, storage, listeners, transport, options);
136
144
  }
137
145
  }));
138
146
  listeners.push(background.moderation.listen((value) => {
139
147
  if (value.data) {
140
- onboardingProcess($store, background, service, notifications, storage, listeners);
148
+ onboardingProcess($store, background, service, notifications, storage, listeners, transport, options);
141
149
  }
142
150
  }));
143
151
  listeners.push(service.featureSettings.listen((value) => {
144
152
  if (value) {
145
- onboardingProcess($store, background, service, notifications, storage, listeners);
153
+ onboardingProcess($store, background, service, notifications, storage, listeners, transport, options);
146
154
  }
147
155
  }));
148
156
  }
157
+ onboardingProcess($store, background, service, notifications, storage, listeners, transport, options);
149
158
  const submitInplay = async () => {
150
159
  const eventId = background.slStreamId.get();
151
160
  if (eventId) {
@@ -3,6 +3,7 @@ import { QuestionStatus, QuestionType } from '@streamlayer/sdk-web-types';
3
3
  import { ReadableAtom } from 'nanostores';
4
4
  import type { SubscriptionRequest, SubscriptionResponse, VotingSubscriptionRequest, VotingSubscriptionResponse, QuestionSubscriptionRequest, QuestionSubscriptionResponse } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb';
5
5
  import { InteractiveAllowed } from '../background';
6
+ import { GamificationStorage } from '../storage';
6
7
  export declare const $activeQuestion: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedQuestion | undefined, any>;
7
8
  export declare const getFeedItem: (questionId: string, transport: Transport) => Promise<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem | undefined>;
8
9
  export declare const feedSubscription: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("packages/sdk-web-api/lib/grpc/subscription").ServerStreamSubscription<import("@bufbuild/protobuf").ServiceType, import("@bufbuild/protobuf").Message<import("@bufbuild/protobuf").AnyMessage>, import("@bufbuild/protobuf").Message<import("@bufbuild/protobuf").AnyMessage>, never, never> | import("packages/sdk-web-api/lib/grpc/subscription").ServerStreamSubscription<{
@@ -434,4 +435,4 @@ export declare const $activePromotionId: ($slStreamId: ReadableAtom<string | und
434
435
  export { $userSummary, $leaderboardList } from './leaderboard';
435
436
  export { $friends } from './friends';
436
437
  export { $moderation } from './moderation';
437
- export declare const $betPack: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<Record<string, import("@bufbuild/protobuf").PlainMessage<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion>> | null, any>;
438
+ export declare const $betPack: ($slStreamId: ReadableAtom<string | undefined>, $slUserId: ReadableAtom<string | undefined>, $slOrganizationId: ReadableAtom<string | undefined>, storage: GamificationStorage, transport: Transport) => import("@nanostores/query").FetcherStore<Record<string, import("@bufbuild/protobuf").PlainMessage<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion>> | null, any>;
@@ -1,4 +1,5 @@
1
1
  import { QuestionStatus, QuestionType } from '@streamlayer/sdk-web-types';
2
+ import { eventBus } from '@streamlayer/sdk-web-interfaces';
2
3
  import { atom } from 'nanostores';
3
4
  import { Feed } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_connect';
4
5
  import { InteractiveAllowed } from '../background';
@@ -170,14 +171,14 @@ export const $activePromotionId = ($slStreamId, transport) => {
170
171
  export { $userSummary, $leaderboardList } from './leaderboard';
171
172
  export { $friends } from './friends';
172
173
  export { $moderation } from './moderation';
173
- export const $betPack = ($slStreamId, transport) => {
174
+ export const $betPack = ($slStreamId, $slUserId, $slOrganizationId, storage, transport) => {
174
175
  const { client, queryKey } = transport.createPromiseClient(Feed, {
175
176
  method: 'betPack',
176
- params: [$slStreamId],
177
+ params: [$slStreamId, $slUserId, $slOrganizationId],
177
178
  });
178
179
  return transport.nanoquery.createFetcherStore(queryKey, {
179
- fetcher: async (_, __, slStreamId) => {
180
- if (!slStreamId) {
180
+ fetcher: async (_, __, slStreamId, userId, organizationId) => {
181
+ if (!slStreamId || !userId || !organizationId) {
181
182
  return null;
182
183
  }
183
184
  const res = await client.betPack({
@@ -193,7 +194,28 @@ export const $betPack = ($slStreamId, transport) => {
193
194
  if (!res.data || !res.data.length) {
194
195
  return null;
195
196
  }
196
- return res.data?.reduce((acc, item) => item?.attributes?.question?.id ? { ...acc, [item.attributes.question.id]: item.attributes.question } : acc, {});
197
+ return res.data?.reduce((acc, item) => {
198
+ const question = item?.attributes?.question;
199
+ if (!question?.id) {
200
+ return acc;
201
+ }
202
+ const flags = {
203
+ eventId: slStreamId,
204
+ userId: userId,
205
+ organizationId: organizationId,
206
+ };
207
+ if (!storage.isBetPackQuestionReceived(flags, question.id)) {
208
+ eventBus.emit('poll', {
209
+ action: 'received',
210
+ payload: {
211
+ questionId: question.id,
212
+ questionType: question.type,
213
+ },
214
+ });
215
+ storage.saveBetPackQuestionReceived(flags, question.id);
216
+ }
217
+ return { ...acc, [question.id]: question };
218
+ }, {});
197
219
  },
198
220
  dedupeTime: 0,
199
221
  refetchInterval: 0,
package/lib/storage.d.ts CHANGED
@@ -8,6 +8,10 @@ type UserProps = {
8
8
  export declare class GamificationStorage extends Storage {
9
9
  private sessionStorage;
10
10
  constructor();
11
+ saveBetPackQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => void;
12
+ isBetPackQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => boolean;
13
+ saveQuestionOpened: ({ userId, eventId, organizationId }: UserProps, questionId: string) => void;
14
+ isQuestionOpened: ({ userId, eventId, organizationId }: UserProps, questionId: string) => boolean;
11
15
  saveOnboardingStatus: ({ userId, eventId, organizationId }: UserProps, status: OnboardingStatus) => void;
12
16
  getOnboardingStatus: ({ userId, eventId, organizationId }: UserProps) => string | undefined;
13
17
  setOnboardingInstantOpen: ({ userId, eventId, organizationId }: UserProps) => void;
package/lib/storage.js CHANGED
@@ -3,6 +3,8 @@ var KEY_PREFIX;
3
3
  (function (KEY_PREFIX) {
4
4
  KEY_PREFIX["ONBOARDING"] = "onboarding";
5
5
  KEY_PREFIX["ONBOARDING_IO"] = "onboarding_io";
6
+ KEY_PREFIX["BET_PACK_ITEM_RECEIVED"] = "bp-item-received";
7
+ KEY_PREFIX["QUESTION_OPENED"] = "q-opened";
6
8
  })(KEY_PREFIX || (KEY_PREFIX = {}));
7
9
  class GamificationSessionStorage extends Storage {
8
10
  constructor() {
@@ -21,6 +23,18 @@ export class GamificationStorage extends Storage {
21
23
  super('gamification');
22
24
  this.sessionStorage = new GamificationSessionStorage();
23
25
  }
26
+ saveBetPackQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
27
+ this.write(KEY_PREFIX.BET_PACK_ITEM_RECEIVED, organizationId, userId, eventId, questionId);
28
+ };
29
+ isBetPackQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
30
+ return !!this.read(KEY_PREFIX.BET_PACK_ITEM_RECEIVED, organizationId, userId, eventId, questionId);
31
+ };
32
+ saveQuestionOpened = ({ userId, eventId, organizationId }, questionId) => {
33
+ this.write(KEY_PREFIX.QUESTION_OPENED, organizationId, userId, eventId, questionId);
34
+ };
35
+ isQuestionOpened = ({ userId, eventId, organizationId }, questionId) => {
36
+ return !!this.read(KEY_PREFIX.QUESTION_OPENED, organizationId, userId, eventId, questionId);
37
+ };
24
38
  saveOnboardingStatus = ({ userId, eventId, organizationId }, status) => {
25
39
  this.write(KEY_PREFIX.ONBOARDING, organizationId, userId, eventId, status);
26
40
  };
@@ -2,7 +2,7 @@ import type { Transport } from '@streamlayer/sdk-web-api';
2
2
  import { ReadableAtom } from 'nanostores';
3
3
  import { LeaderboardSummaryItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
4
4
  import { Gamification } from '.';
5
- export declare const summary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification["friends"], transport: Transport) => {
5
+ export declare const summary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification["friends"] | undefined, transport: Transport) => {
6
6
  $store: import("nanostores").MapStore<LeaderboardSummaryItem | undefined>;
7
7
  invalidate: () => void;
8
8
  };
@@ -10,9 +10,11 @@ export const summary = ($eventId, $userId, $friends, transport) => {
10
10
  const eventId = $eventId.get();
11
11
  const userId = $userId.get();
12
12
  const usersIds = $friends
13
- .getStore()
14
- .get()
15
- .data?.map((friend) => friend.slId);
13
+ ? $friends
14
+ .getStore()
15
+ .get()
16
+ .data?.map((friend) => friend.slId)
17
+ : [];
16
18
  if (!usersIds) {
17
19
  return;
18
20
  }
@@ -28,12 +30,13 @@ export const summary = ($eventId, $userId, $friends, transport) => {
28
30
  void refetch();
29
31
  };
30
32
  onMount($store, () => {
33
+ invalidate();
31
34
  const cancelRefetchListener = $eventId.listen(refetch);
32
- const cancelRefetchByFriendsListener = $friends.listen(refetch);
35
+ const cancelRefetchByFriendsListener = $friends?.listen(refetch);
33
36
  const cancelRefetchByUserListener = $userId.listen(refetch);
34
37
  return () => {
35
38
  cancelRefetchListener();
36
- cancelRefetchByFriendsListener();
39
+ cancelRefetchByFriendsListener?.();
37
40
  cancelRefetchByUserListener();
38
41
  };
39
42
  });
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^1.10.0",
6
6
  "@fastify/deepmerge": "^2.0.0",
7
7
  "@streamlayer/sl-eslib": "^5.149.1",
8
8
  "nanostores": "^0.10.3",
9
- "@streamlayer/sdk-web-api": "^1.7.1",
10
- "@streamlayer/sdk-web-interfaces": "^1.4.10",
11
- "@streamlayer/sdk-web-core": "^1.10.1",
12
- "@streamlayer/sdk-web-logger": "^1.0.44",
13
- "@streamlayer/sdk-web-notifications": "^1.3.6",
14
- "@streamlayer/sdk-web-storage": "^1.0.44",
15
- "@streamlayer/sdk-web-types": "^1.10.1"
9
+ "@streamlayer/sdk-web-api": "^1.7.3",
10
+ "@streamlayer/sdk-web-core": "^1.11.1",
11
+ "@streamlayer/sdk-web-interfaces": "^1.4.12",
12
+ "@streamlayer/sdk-web-logger": "^1.0.46",
13
+ "@streamlayer/sdk-web-notifications": "^1.3.8",
14
+ "@streamlayer/sdk-web-storage": "^1.0.46",
15
+ "@streamlayer/sdk-web-types": "^1.10.3"
16
16
  },
17
17
  "devDependencies": {
18
18
  "tslib": "^2.7.0"