@streamlayer/feature-gamification 0.39.0 → 0.40.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.
@@ -34,7 +34,7 @@ export declare class GamificationBackground {
34
34
  /** opened question statistics */
35
35
  openedQuestion: ReturnType<typeof detail>;
36
36
  /** last active question in feed */
37
- activeQuestionId: ApiStore<GetApiResponseType<typeof queries.$activeQuestion>>;
37
+ activeQuestionId: ReturnType<typeof queries.$activeQuestion>;
38
38
  feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
39
39
  /** moderation id */
40
40
  moderationId: ReadableAtom<string | undefined>;
@@ -47,6 +47,7 @@ export declare class GamificationBackground {
47
47
  private notifications;
48
48
  private log;
49
49
  private transport;
50
+ private cancels;
50
51
  constructor(instance: StreamLayerContext);
51
52
  /**
52
53
  * Get id for notifications and link with current session
package/lib/background.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ApiStore, SingleStore, createSingleStore } from '@streamlayer/sdk-web-interfaces';
2
2
  import { createLogger } from '@streamlayer/sdk-web-logger';
3
+ import { QuestionStatus } from '@streamlayer/sdk-web-types';
3
4
  import '@streamlayer/sdk-web-core/store';
4
5
  import * as queries from './queries';
5
6
  import { detail } from './detail';
@@ -41,6 +42,7 @@ export class GamificationBackground {
41
42
  notifications;
42
43
  log;
43
44
  transport;
45
+ cancels = new Set();
44
46
  constructor(instance) {
45
47
  this.transport = instance.transport;
46
48
  this.log = createLogger('gamification-background');
@@ -53,9 +55,9 @@ export class GamificationBackground {
53
55
  this.notifications = instance.notifications;
54
56
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
55
57
  this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, instance.transport), 'gamification:feedList');
56
- this.activeQuestionId = new ApiStore(queries.$activeQuestion(this.slStreamId, instance.transport), 'gamification:activeQuestionId');
58
+ this.activeQuestionId = queries.$activeQuestion(this.slStreamId, instance.transport);
57
59
  this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
58
- this.openedQuestionId.listen((item) => {
60
+ this.cancels.add(this.openedQuestionId.listen((item) => {
59
61
  this.log.debug({ item }, 'received question');
60
62
  if (item?.questionId) {
61
63
  this.questionSubscription = queries.questionSubscription(item.questionId, instance.transport);
@@ -72,16 +74,22 @@ export class GamificationBackground {
72
74
  this.questionSubscription = undefined;
73
75
  }
74
76
  }
75
- });
77
+ }));
76
78
  this.feedSubscription = queries.feedSubscription(this.slStreamId, instance.transport);
77
- this.feedSubscription.addListener('feed-subscription-active-question', (response) => {
78
- const $activeQuestionId = this.activeQuestionId.getStore();
79
- if ($activeQuestionId) {
80
- $activeQuestionId.mutate(response.data?.attributes);
79
+ this.cancels.add(this.feedSubscription.addListener('feed-subscription-active-question', (response) => {
80
+ const activeQuestionId = this.activeQuestionId.get().data?.question?.id;
81
+ const question = response.data?.attributes?.question;
82
+ if (!question) {
83
+ return;
81
84
  }
82
- });
85
+ // skip update question, avoid race condition
86
+ if (activeQuestionId && question.status === QuestionStatus.RESOLVED && question.id !== activeQuestionId) {
87
+ return;
88
+ }
89
+ this.activeQuestionId.mutate(response.data?.attributes);
90
+ }));
83
91
  // refresh moderation if question empty, it`s mean that moderation was changed
84
- this.feedSubscription.addListener('moderation update', (response) => {
92
+ this.cancels.add(this.feedSubscription.addListener('moderation update', (response) => {
85
93
  window.requestAnimationFrame(() => {
86
94
  if (response.data?.attributes?.question === undefined) {
87
95
  if (response.data?.attributes?.moderation) {
@@ -89,17 +97,26 @@ export class GamificationBackground {
89
97
  }
90
98
  }
91
99
  });
92
- });
100
+ }));
93
101
  this.feedSubscription.connect();
94
102
  /**
95
103
  * invalidate active question on interactiveAllowed change
96
104
  * close question if interactiveAllowed changed to disallowed
97
105
  * open question if interactiveAllowed changed to allowed
98
106
  */
99
- this.interactiveAllowed.listen(() => {
107
+ this.cancels.add(this.interactiveAllowed.listen(() => {
100
108
  window.requestAnimationFrame(() => {
101
109
  this.activeQuestionId.invalidate();
102
110
  });
111
+ }));
112
+ instance.sdk.onMount(() => {
113
+ return () => {
114
+ this.activeQuestionId.off();
115
+ for (const cancel of this.cancels) {
116
+ cancel();
117
+ this.cancels.delete(cancel);
118
+ }
119
+ };
103
120
  });
104
121
  }
105
122
  /**
package/lib/detail.js CHANGED
@@ -38,6 +38,9 @@ export const detail = (transport, $openedQuestionId, $feedList) => {
38
38
  };
39
39
  $extendedStore.mutate({
40
40
  ...question,
41
+ options: currentQuestion?.options,
42
+ subject: currentQuestion?.subject,
43
+ appearance: currentQuestion?.appearance,
41
44
  answers: mergeQuestionAnswers(currentQuestion?.answers, question?.answers),
42
45
  });
43
46
  };
@@ -0,0 +1,5 @@
1
+ import type { Transport } from '@streamlayer/sdk-web-api';
2
+ import { ReadableAtom } from 'nanostores';
3
+ import { LeaderboardItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
4
+ import { Gamification } from '.';
5
+ export declare const friendSummary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification['friends'], friendId: string, transport: Transport) => Promise<LeaderboardItem | undefined>;
@@ -0,0 +1,18 @@
1
+ import { createUserSummaryFetch } from './queries/leaderboard';
2
+ export const friendSummary = async ($eventId, $userId, $friends, friendId, transport) => {
3
+ const fetch = createUserSummaryFetch(transport);
4
+ const eventId = $eventId.get();
5
+ const userId = $userId.get();
6
+ const usersIds = $friends
7
+ .getStore()
8
+ .get()
9
+ .data?.map((friend) => friend.slId) || [];
10
+ const request = {
11
+ eventId: eventId,
12
+ userId: friendId,
13
+ usersIds: [...usersIds, userId],
14
+ };
15
+ const res = await fetch(request);
16
+ const summary = res.data?.attributes?.summary;
17
+ return summary;
18
+ };
@@ -10,6 +10,7 @@ import { deepLink } from './deepLink';
10
10
  import { OnboardingStatus } from './onboarding';
11
11
  import { LeaderboardItem } from './queries/leaderboard';
12
12
  import { GamificationBackground } from './background';
13
+ import { summary } from './userSummary';
13
14
  /**
14
15
  * Gamification (Games) Overlay
15
16
  * Includes:
@@ -21,7 +22,7 @@ import { GamificationBackground } from './background';
21
22
  */
22
23
  export declare class Gamification extends AbstractFeature<'games', PlainMessage<GamesOverlaySettings>> {
23
24
  /** user statistics (leaderboard panel) */
24
- userSummary: ApiStore<GetApiResponseType<typeof queries.$userSummary>>;
25
+ userSummary: ReturnType<typeof summary>;
25
26
  /** feed list (pick history) */
26
27
  feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
27
28
  /** friends list */
@@ -54,17 +55,18 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
54
55
  /** Browser cache */
55
56
  private storage;
56
57
  private submitAnswerTimeout;
58
+ private cancels;
57
59
  constructor(config: FeatureProps, source: FeatureSource, instance: StreamLayerContext);
58
60
  get isInteractiveAllowed(): boolean;
59
61
  checkInteractiveFlag: () => void;
60
62
  connect: () => void;
61
63
  disconnect: () => void;
62
64
  submitAnswer: (questionId: string, answerId: string) => Promise<void>;
63
- openQuestion: (questionId: string, question?: FeedItem & {
65
+ openQuestion: (questionId?: string, question?: FeedItem & {
64
66
  openedFrom?: 'list' | 'notification';
65
- }) => void;
67
+ }) => void | (() => void);
66
68
  closeQuestion: (questionId?: string) => void;
67
- openUser: (userId: string) => void;
69
+ openUser: (friendId: string) => Promise<void>;
68
70
  closeUser: () => void;
69
71
  /**
70
72
  * Show in-app notification for active question
@@ -12,6 +12,8 @@ import { OnboardingStatus, onboarding } from './onboarding';
12
12
  import { GamificationBackground, InteractiveAllowed } from './background';
13
13
  import { ERROR } from './constants';
14
14
  import { $questionByUser } from './queries';
15
+ import { summary } from './userSummary';
16
+ import { friendSummary } from './friendSummary';
15
17
  const InteractiveQuestionTypes = new Set([QuestionType.POLL, QuestionType.PREDICTION, QuestionType.TRIVIA]);
16
18
  /**
17
19
  * Gamification (Games) Overlay
@@ -54,6 +56,7 @@ export class Gamification extends AbstractFeature {
54
56
  /** Browser cache */
55
57
  storage;
56
58
  submitAnswerTimeout;
59
+ cancels = new Set();
57
60
  constructor(config, source, instance) {
58
61
  super(config, source);
59
62
  this.background = new GamificationBackground(instance);
@@ -61,7 +64,6 @@ export class Gamification extends AbstractFeature {
61
64
  this.activeQuestionId = this.background.activeQuestionId;
62
65
  this.openedQuestionId = this.background.openedQuestionId;
63
66
  this.storage = new GamificationStorage();
64
- this.userSummary = new ApiStore(queries.$userSummary(this.background.slStreamId, this.background.userId, instance.transport), 'gamification:userSummary');
65
67
  this.feedList = this.background.feedList;
66
68
  this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
67
69
  this.currentUserId = this.background.userId;
@@ -74,24 +76,48 @@ export class Gamification extends AbstractFeature {
74
76
  this.openFeature = () => instance.sdk.openFeature(FeatureType.GAMES);
75
77
  this.openedQuestion = this.background.openedQuestion;
76
78
  this.deepLink = deepLink(this.transport, this.background.slStreamId, instance.stores.providerStreamId.getStore(), this.background.userId);
79
+ this.userSummary = summary(this.background.slStreamId, this.background.userId, this.friends, this.transport);
77
80
  this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends);
78
81
  this.connect();
79
82
  // refresh leaderboard on user summary update after earning points
80
- this.userSummary.listen((userSummary) => {
81
- if (this.leaderboardList.$store.lc !== 0 && userSummary?.data?.summary) {
83
+ this.cancels.add(this.userSummary.$store.listen((userSummary, prevValue) => {
84
+ if (prevValue?.summary && userSummary?.summary && !userSummary.fromLeaderboard) {
82
85
  this.leaderboardList.invalidate(); // verified, it's necessary
83
86
  }
84
- });
87
+ }));
88
+ this.cancels.add(this.leaderboardList.$store.subscribe((leaderboard) => {
89
+ const userSummary = { ...(this.userSummary.$store.get() || {}) };
90
+ const userId = userSummary?.summary?.userId;
91
+ if (leaderboard.data.length && userId) {
92
+ const userRank = leaderboard.data.find((item) => item.userId === userId)?.rank;
93
+ if (userRank !== undefined) {
94
+ if (userSummary?.summary) {
95
+ userSummary.fromLeaderboard = true;
96
+ userSummary.summary.friendsRank = userRank;
97
+ // @ts-ignore
98
+ this.userSummary.$store.set(userSummary);
99
+ }
100
+ }
101
+ }
102
+ }));
85
103
  /**
86
104
  * listen for active question and show in-app notification
87
105
  */
88
- this.background.activeQuestionId.listen(this.showInApp);
106
+ this.cancels.add(this.background.activeQuestionId.listen(this.showInApp));
89
107
  /**
90
108
  * listen for onboarding status, moderation onboarding changes and opt-in settings
91
109
  */
92
- this.onboardingStatus.$store.listen(this.checkInteractiveFlag);
93
- this.background.moderation.getStore().listen(this.checkInteractiveFlag);
94
- this.settings.subscribe(this.checkInteractiveFlag);
110
+ this.cancels.add(this.onboardingStatus.$store.listen(this.checkInteractiveFlag));
111
+ this.cancels.add(this.background.moderation.getStore().listen(this.checkInteractiveFlag));
112
+ this.cancels.add(this.settings.subscribe(this.checkInteractiveFlag));
113
+ instance.sdk.onMount(() => {
114
+ return () => {
115
+ for (const cancel of this.cancels) {
116
+ cancel();
117
+ this.cancels.delete(cancel);
118
+ }
119
+ };
120
+ });
95
121
  }
96
122
  get isInteractiveAllowed() {
97
123
  return this.background.interactiveAllowed.get() === InteractiveAllowed.ALLOWED;
@@ -105,7 +131,7 @@ export class Gamification extends AbstractFeature {
105
131
  this.background.interactiveAllowed.set(allowed ? InteractiveAllowed.ALLOWED : InteractiveAllowed.DISALLOWED);
106
132
  };
107
133
  connect = () => {
108
- this.background.feedSubscription.addListener('feed-subscription-prediction-close', async (response) => {
134
+ this.cancels.add(this.background.feedSubscription.addListener('feed-subscription-prediction-close', async (response) => {
109
135
  if (!this.isInteractiveAllowed) {
110
136
  return;
111
137
  }
@@ -193,18 +219,23 @@ export class Gamification extends AbstractFeature {
193
219
  }
194
220
  this.userSummary.invalidate(); // verified, it's necessary
195
221
  }
196
- });
222
+ }));
197
223
  // update feed list on question update received from subscription
198
224
  // add new question to the top of the list
199
- this.background.feedSubscription.addListener('feed-subscription-questions-list', (response) => {
225
+ this.cancels.add(this.background.feedSubscription.addListener('feed-subscription-questions-list', (response) => {
200
226
  const feedList = [...(this.feedList.getStore().value?.data || [])];
201
227
  const feedItem = response.data?.attributes?.feedItem;
202
228
  const questionIndex = feedList.findIndex((item) => item.id === feedItem?.id);
203
- if (!feedItem) {
229
+ if (!feedItem?.attributes) {
230
+ return;
231
+ }
232
+ // skip questions with status other than active or resolved
233
+ if (feedItem.attributes.status !== QuestionStatus.ACTIVE &&
234
+ feedItem.attributes.status !== QuestionStatus.RESOLVED) {
204
235
  return;
205
236
  }
206
237
  if (questionIndex !== -1) {
207
- if (feedItem.attributes?.attributes.case === 'question' &&
238
+ if (feedItem.attributes.attributes.case === 'question' &&
208
239
  feedList[questionIndex].attributes?.attributes.case === 'question') {
209
240
  const prev = feedList[questionIndex];
210
241
  if (prev.attributes) {
@@ -235,7 +266,24 @@ export class Gamification extends AbstractFeature {
235
266
  }
236
267
  }
237
268
  else {
238
- feedList[questionIndex] = feedItem;
269
+ const prev = feedList[questionIndex];
270
+ if (prev.attributes && feedItem.attributes) {
271
+ feedList[questionIndex] = {
272
+ ...feedList[questionIndex],
273
+ attributes: {
274
+ ...prev.attributes,
275
+ attributes: {
276
+ ...prev.attributes.attributes,
277
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
278
+ // @ts-ignore
279
+ value: {
280
+ ...prev.attributes.attributes.value,
281
+ ...feedItem.attributes.attributes.value,
282
+ },
283
+ },
284
+ },
285
+ };
286
+ }
239
287
  }
240
288
  }
241
289
  if (questionIndex === -1) {
@@ -249,7 +297,7 @@ export class Gamification extends AbstractFeature {
249
297
  });
250
298
  }
251
299
  this.feedList.getStore().mutate(feedList);
252
- });
300
+ }));
253
301
  };
254
302
  // not used
255
303
  disconnect = () => {
@@ -295,6 +343,9 @@ export class Gamification extends AbstractFeature {
295
343
  this.feedList.getStore().mutate([...feedList]);
296
344
  extendedQuestion.answers[votedAnswerIdx].correct = correctAnswer?.id === answerId;
297
345
  extendedQuestion.answers[votedAnswerIdx].youVoted = true;
346
+ extendedQuestion.answers.forEach((answer) => {
347
+ answer.percentageDecimal = 0;
348
+ });
298
349
  if (correctAnswer?.id === answerId) {
299
350
  extendedQuestion.answers[votedAnswerIdx].pointsEarned =
300
351
  extendedQuestion.status === QuestionStatus.RESOLVED ? 0 : correctAnswer.points;
@@ -324,6 +375,9 @@ export class Gamification extends AbstractFeature {
324
375
  }
325
376
  };
326
377
  openQuestion = (questionId, question) => {
378
+ if (!questionId) {
379
+ return () => { };
380
+ }
327
381
  this.notifications.close(this.background.getCurrentSessionId({
328
382
  prefix: 'notification',
329
383
  entity: questionId,
@@ -346,9 +400,34 @@ export class Gamification extends AbstractFeature {
346
400
  closeQuestion = (questionId) => {
347
401
  return this.background.closeQuestion(questionId);
348
402
  };
349
- openUser = (userId) => {
350
- const user = this.leaderboardList.$store.get().data?.find((item) => item.userId === userId);
351
- this.openedUser.set(user);
403
+ openUser = async (friendId) => {
404
+ const user = this.leaderboardList.$store.get().data?.find((item) => item.userId === friendId);
405
+ if (!user) {
406
+ this.openedUser.set(user);
407
+ return;
408
+ }
409
+ if (user.summaryLoaded) {
410
+ this.openedUser.set(user);
411
+ return;
412
+ }
413
+ const userCopy = { ...user };
414
+ try {
415
+ const friendDetail = await friendSummary(this.background.slStreamId, this.background.userId, this.friends, friendId, this.transport);
416
+ if (friendDetail?.inTop !== undefined) {
417
+ this.leaderboardList.$store.setKey('data', this.leaderboardList.$store.get().data?.map((item) => {
418
+ if (item.userId === friendId) {
419
+ item.inTop = friendDetail.inTop;
420
+ }
421
+ return item;
422
+ }));
423
+ userCopy.inTop = friendDetail.inTop;
424
+ }
425
+ }
426
+ catch (err) {
427
+ console.error(err);
428
+ }
429
+ // @ts-ignore
430
+ this.openedUser.set(userCopy);
352
431
  };
353
432
  closeUser = () => {
354
433
  this.openedUser.set(undefined);
@@ -371,8 +450,8 @@ export class Gamification extends AbstractFeature {
371
450
  if (this.isInteractiveAllowed) {
372
451
  this.notifications.add({
373
452
  type: NotificationType.QUESTION,
374
- action: () => question.data?.question && this.openQuestion(question.data.question.id, question.data.feedItem),
375
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
453
+ action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
454
+ close: () => this.closeQuestion(question.data?.question?.id),
376
455
  autoHideDuration: 1000 * 60,
377
456
  id: this.background.getCurrentSessionId({
378
457
  prefix: 'notification',
@@ -394,7 +473,7 @@ export class Gamification extends AbstractFeature {
394
473
  const instantView = {
395
474
  heading: question.data.question.notification.title,
396
475
  body: question.data.question.notification.body,
397
- imageMode: optionsValue.imageMode,
476
+ imageMode: optionsValue?.imageMode,
398
477
  image: optionsValue?.image,
399
478
  video: {
400
479
  id: optionsValue?.video?.id || '',
@@ -408,8 +487,8 @@ export class Gamification extends AbstractFeature {
408
487
  };
409
488
  this.notifications.add({
410
489
  type: NotificationType.QUESTION,
411
- action: () => question.data?.question && this.openQuestion(question.data.question.id, question.data.feedItem),
412
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
490
+ action: () => this.openQuestion(question?.data?.question?.id, question?.data?.feedItem),
491
+ close: () => this.closeQuestion(question?.data?.question?.id),
413
492
  autoHideDuration: 1000 * 120,
414
493
  emitEvent: true,
415
494
  id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
@@ -433,8 +512,8 @@ export class Gamification extends AbstractFeature {
433
512
  };
434
513
  this.notifications.add({
435
514
  type: NotificationType.QUESTION,
436
- action: () => question.data?.question && this.openQuestion(question.data.question.id, question.data.feedItem),
437
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
515
+ action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
516
+ close: () => this.closeQuestion(question.data?.question?.id),
438
517
  autoHideDuration: 1000 * 120,
439
518
  emitEvent: true,
440
519
  id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
@@ -6,7 +6,9 @@ type LeaderboardOptions = {
6
6
  pageSize?: number;
7
7
  };
8
8
  type LeaderboardStore = {
9
- data: LeaderboardItem[];
9
+ data: Array<LeaderboardItem & {
10
+ summaryLoaded?: boolean;
11
+ }>;
10
12
  loading?: boolean;
11
13
  key: number;
12
14
  hasMore: boolean;
@@ -34,19 +34,20 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
34
34
  const request = {
35
35
  eventId: eventId,
36
36
  usersIds: friendsIds,
37
- pagination: { page: 0, pageSize: options?.pageSize || defaultOptions.pageSize },
38
37
  };
39
38
  const newData = await fetch(request);
40
39
  $store.set({
41
- data: newData.data.map((item) => item.attributes),
42
- hasMore: true,
40
+ data: newData.data.map((item, i) => ({
41
+ ...item.attributes,
42
+ rank: i + 1,
43
+ })),
44
+ hasMore: false,
43
45
  key: Date.now(),
44
46
  loading: false,
45
47
  });
46
48
  if (newData.meta) {
47
49
  maxPage = Math.round(newData.meta.count / newData.meta.pageSize);
48
50
  }
49
- $pagination.set(request.pagination);
50
51
  }
51
52
  };
52
53
  const invalidate = () => {
@@ -65,33 +66,32 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
65
66
  onMount($store, () => {
66
67
  const cancelRefetchListener = $eventId.listen(refetch);
67
68
  const cancelRefetchByFriendsListener = $friends.listen(refetch);
68
- const cancelPaginationListener = $pagination.listen(async (pagination) => {
69
- const eventId = $eventId.get();
70
- if (pagination.page > 0 && eventId) {
71
- if (pagination.page < maxPage) {
72
- $store.setKey('loading', true);
73
- const request = {
74
- eventId: eventId,
75
- pagination,
76
- };
77
- const newData = await fetch(request);
78
- const prevData = $store.get().data || [];
79
- $store.set({
80
- data: [...prevData, ...newData.data.map((item) => item.attributes)],
81
- key: $store.get().key,
82
- loading: false,
83
- hasMore: true,
84
- });
85
- }
86
- else {
87
- $store.setKey('hasMore', false);
88
- }
89
- }
90
- });
69
+ // const cancelPaginationListener = $pagination.listen(async (pagination) => {
70
+ // const eventId = $eventId.get()
71
+ // if (pagination.page > 0 && eventId) {
72
+ // if (pagination.page < maxPage) {
73
+ // $store.setKey('loading', true)
74
+ // const request = {
75
+ // eventId: eventId as unknown as bigint,
76
+ // pagination,
77
+ // }
78
+ // const newData = await fetch(request)
79
+ // const prevData = $store.get().data || []
80
+ // $store.set({
81
+ // data: [...prevData, ...newData.data.map((item) => item.attributes as LeaderboardItem)],
82
+ // key: $store.get().key,
83
+ // loading: false,
84
+ // hasMore: true,
85
+ // })
86
+ // } else {
87
+ // $store.setKey('hasMore', false)
88
+ // }
89
+ // }
90
+ // })
91
91
  return () => {
92
92
  cancelRefetchListener();
93
93
  cancelRefetchByFriendsListener();
94
- cancelPaginationListener();
94
+ // cancelPaginationListener()
95
95
  };
96
96
  });
97
97
  return { $store, fetchMore, invalidate };
package/lib/onboarding.js CHANGED
@@ -154,6 +154,7 @@ export const onboarding = (service, background, transport, notifications) => {
154
154
  action: 'onboardingPassed',
155
155
  payload: {},
156
156
  });
157
+ service.openFeature();
157
158
  const notificationId = background.getCurrentSessionId({ prefix: 'onboarding' });
158
159
  notifications.close(notificationId);
159
160
  }
@@ -1,8 +1,9 @@
1
1
  import type { Transport } from '@streamlayer/sdk-web-api';
2
2
  import { ReadableAtom } from 'nanostores';
3
- import { ListRequest } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
3
+ import { ListRequest, SummaryRequest } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
4
4
  import { PartialMessage } from '@bufbuild/protobuf';
5
5
  export { LeaderboardItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
6
6
  export declare const $userSummary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").LeaderboardSummaryItem | undefined, any>;
7
7
  export declare const $leaderboardList: ($eventId: ReadableAtom<string | undefined>, _: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").ListResponse_ListResponseData[], any>;
8
8
  export declare const createLeaderboardListFetch: (transport: Transport) => (params: PartialMessage<ListRequest>) => Promise<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").ListResponse>;
9
+ export declare const createUserSummaryFetch: (transport: Transport) => (params: PartialMessage<SummaryRequest>) => Promise<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").SummaryResponse>;
@@ -34,3 +34,7 @@ export const createLeaderboardListFetch = (transport) => {
34
34
  const { client } = transport.createPromiseClient(Leaderboard, { method: 'list' });
35
35
  return (params) => client.list(params);
36
36
  };
37
+ export const createUserSummaryFetch = (transport) => {
38
+ const { client } = transport.createPromiseClient(Leaderboard, { method: 'summary' });
39
+ return (params) => client.summary(params);
40
+ };
@@ -0,0 +1,10 @@
1
+ import type { Transport } from '@streamlayer/sdk-web-api';
2
+ import { ReadableAtom } from 'nanostores';
3
+ import { LeaderboardSummaryItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
4
+ import { Gamification } from '.';
5
+ export declare const summary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification['friends'], transport: Transport) => {
6
+ $store: import("nanostores").MapStore<(LeaderboardSummaryItem & {
7
+ fromLeaderboard?: boolean | undefined;
8
+ }) | undefined>;
9
+ invalidate: () => void;
10
+ };
@@ -0,0 +1,45 @@
1
+ import { createMapStore } from '@streamlayer/sdk-web-interfaces';
2
+ import { onMount } from 'nanostores';
3
+ import { createUserSummaryFetch } from './queries/leaderboard';
4
+ export const summary = ($eventId, $userId, $friends, transport) => {
5
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6
+ // @ts-ignore
7
+ const $store = createMapStore(undefined);
8
+ const fetch = createUserSummaryFetch(transport);
9
+ const refetch = async () => {
10
+ const eventId = $eventId.get();
11
+ const userId = $userId.get();
12
+ const usersIds = $friends
13
+ .getStore()
14
+ .get()
15
+ .data?.map((friend) => friend.slId);
16
+ if (!usersIds) {
17
+ return;
18
+ }
19
+ const request = {
20
+ eventId: eventId,
21
+ userId: userId,
22
+ usersIds: [...usersIds, userId],
23
+ };
24
+ const res = await fetch(request);
25
+ const prevData = $store.get()?.summary?.friendsRank;
26
+ if (res.data?.attributes?.summary?.friendsRank && prevData !== undefined) {
27
+ res.data.attributes.summary.friendsRank = prevData;
28
+ }
29
+ $store.set(res.data?.attributes);
30
+ };
31
+ const invalidate = () => {
32
+ void refetch();
33
+ };
34
+ onMount($store, () => {
35
+ const cancelRefetchListener = $eventId.listen(refetch);
36
+ const cancelRefetchByFriendsListener = $friends.listen(refetch);
37
+ const cancelRefetchByUserListener = $userId.listen(refetch);
38
+ return () => {
39
+ cancelRefetchListener();
40
+ cancelRefetchByFriendsListener();
41
+ cancelRefetchByUserListener();
42
+ };
43
+ });
44
+ return { $store, invalidate };
45
+ };
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^1.7.2",
6
6
  "@fastify/deepmerge": "^1.3.0",
7
7
  "@streamlayer/sl-eslib": "^5.83.1",
8
8
  "nanostores": "^0.10.0",
9
- "@streamlayer/sdk-web-api": "^0.24.0",
10
- "@streamlayer/sdk-web-core": "^0.22.0",
9
+ "@streamlayer/sdk-web-api": "^0.24.2",
10
+ "@streamlayer/sdk-web-core": "^0.22.2",
11
11
  "@streamlayer/sdk-web-interfaces": "^0.21.0",
12
12
  "@streamlayer/sdk-web-logger": "^0.5.18",
13
- "@streamlayer/sdk-web-notifications": "^0.15.0",
13
+ "@streamlayer/sdk-web-notifications": "^0.15.1",
14
14
  "@streamlayer/sdk-web-storage": "^0.4.5",
15
15
  "@streamlayer/sdk-web-types": "^0.23.0"
16
16
  },