@streamlayer/feature-gamification 1.25.4 → 1.26.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.
@@ -48,6 +48,7 @@ export declare class GamificationBackground {
48
48
  /** subscription to opened question (vote percentage) */
49
49
  questionSubscription?: ReturnType<typeof queries.questionSubscription>;
50
50
  advertisement?: ReturnType<typeof advertisement>;
51
+ withPickHistory: WritableAtom<boolean>;
51
52
  storage: GamificationStorage;
52
53
  private notifications;
53
54
  private log;
package/lib/background.js CHANGED
@@ -1,7 +1,8 @@
1
- import { ApiStore, SingleStore, createSingleStore } from '@streamlayer/sdk-web-interfaces';
1
+ import { ApiStore, SingleStore, createSingleStore, eventBus, } from '@streamlayer/sdk-web-interfaces';
2
2
  import { createLogger } from '@streamlayer/sdk-web-logger';
3
3
  import { QuestionStatus, QuestionType } from '@streamlayer/sdk-web-types';
4
4
  import '@streamlayer/sdk-web-core/store';
5
+ import { AdUnit, NotificationEnabled } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
5
6
  import * as queries from './queries';
6
7
  import { detail } from './detail';
7
8
  import { advertisement } from './advertisement';
@@ -43,6 +44,7 @@ export class GamificationBackground {
43
44
  /** subscription to opened question (vote percentage) */
44
45
  questionSubscription;
45
46
  advertisement;
47
+ withPickHistory;
46
48
  storage;
47
49
  notifications;
48
50
  log;
@@ -58,10 +60,11 @@ export class GamificationBackground {
58
60
  this.moderationId = new SingleStore(createSingleStore(undefined), 'moderationId').getStore();
59
61
  this.interactiveAllowed = new SingleStore(createSingleStore(InteractiveAllowed.DISALLOWED), 'interactiveAllowed').getStore();
60
62
  this.openedQuestionId = new SingleStore(createSingleStore(undefined), 'openedQuestionId').getStore();
63
+ this.withPickHistory = createSingleStore(false);
61
64
  this.notifications = instance.notifications;
62
65
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
63
66
  const onlyBetPack = !!instance.sdk.options.get().betPack;
64
- this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, this.userId, this.organizationId, this.storage, onlyBetPack, instance.transport), 'gamification:feedList');
67
+ this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, this.userId, this.organizationId, this.storage, onlyBetPack, this.withPickHistory, instance.transport), 'gamification:feedList');
65
68
  this.betPack = new ApiStore(queries.$betPack(this.slStreamId, this.userId, this.organizationId, this.storage, instance.transport), 'gamification:betPack');
66
69
  this.activeQuestionId = queries.$activeQuestion(this.slStreamId, onlyBetPack, instance.transport);
67
70
  this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
@@ -191,6 +194,21 @@ export class GamificationBackground {
191
194
  }));
192
195
  this.cancels.add(this.activeQuestionId.subscribe((item, prevItem) => {
193
196
  if (item.data?.feedItem && item.data?.feedItem?.id !== prevItem?.data?.feedItem?.id) {
197
+ const question = item.data.feedItem;
198
+ const flags = {
199
+ eventId: this.slStreamId.get(),
200
+ userId: this.userId.get(),
201
+ organizationId: this.organizationId.get(),
202
+ };
203
+ eventBus.emit('poll', {
204
+ action: 'received',
205
+ payload: {
206
+ isAd: question?.attributes?.adUnit != AdUnit.UNSET,
207
+ questionId: question.id,
208
+ questionType: question.attributes?.type,
209
+ },
210
+ });
211
+ this.storage.saveQuestionReceived(flags, question.id);
194
212
  instance.onQuestionActivate({
195
213
  stage: 'activate',
196
214
  id: item.data.feedItem.id,
@@ -198,7 +216,7 @@ export class GamificationBackground {
198
216
  prefix: 'notification',
199
217
  entity: item.data.feedItem.id,
200
218
  })),
201
- hasNotification: true,
219
+ hasNotification: item.data?.question?.notification?.enabled === NotificationEnabled.NOTIFICATION_ENABLED,
202
220
  type: item.data.feedItem.type,
203
221
  });
204
222
  }
@@ -210,7 +228,7 @@ export class GamificationBackground {
210
228
  prefix: 'notification',
211
229
  entity: prevItem.data.feedItem.id,
212
230
  })),
213
- hasNotification: true,
231
+ hasNotification: prevItem.data?.question?.notification?.enabled === NotificationEnabled.NOTIFICATION_ENABLED,
214
232
  type: prevItem.data.feedItem.type,
215
233
  });
216
234
  }
package/lib/detail.d.ts CHANGED
@@ -2,13 +2,17 @@ import type { Transport } from '@streamlayer/sdk-web-api';
2
2
  import { FeedItem, ExtendedQuestion } from '@streamlayer/sdk-web-types';
3
3
  import { ReadableAtom } from 'nanostores';
4
4
  import { type GamificationBackground } from './background';
5
+ export type DetailEvent = 'video-rendered' | `video-quartile-${25 | 50 | 75}` | 'muted' | 'unmuted' | 'replayed' | 'played' | 'ended' | 'banner-showed';
5
6
  export declare const detail: (transport: Transport, $openedQuestionId: ReadableAtom<{
6
7
  questionId: string;
7
8
  question?: FeedItem & {
8
9
  openedFrom?: "list" | "notification";
9
10
  };
10
11
  } | undefined>, $feedList: ReturnType<GamificationBackground["feedList"]["getStore"]>) => {
11
- $store: ReadableAtom<Omit<import("@streamlayer/sdk-web-types").RecursiveOmitHelper<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem, "$typeName">, "$typeName"> | undefined>;
12
+ $store: ReadableAtom<(Omit<import("@streamlayer/sdk-web-types").RecursiveOmitHelper<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem, "$typeName">, "$typeName"> & {
13
+ fireEvent: (event: DetailEvent) => void;
14
+ isEventFired: (event: DetailEvent) => boolean;
15
+ }) | undefined>;
12
16
  $extendedStore: import("@nanostores/query").FetcherStore<Omit<import("@streamlayer/sdk-web-types").RecursiveOmitHelper<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion, "$typeName">, "$typeName">, any>;
13
17
  updateExtendedQuestion: (question: ExtendedQuestion | undefined) => void;
14
18
  };
package/lib/detail.js CHANGED
@@ -1,6 +1,16 @@
1
1
  import { batched } from 'nanostores';
2
2
  import { $questionByUser } from './queries';
3
3
  export const detail = (transport, $openedQuestionId, $feedList) => {
4
+ const firedEvents = new Map();
5
+ const fireEvent = (id, event) => {
6
+ if (!firedEvents.has(id)) {
7
+ firedEvents.set(id, new Set());
8
+ }
9
+ firedEvents.get(id)?.add(event);
10
+ };
11
+ const isEventFired = (id, event) => {
12
+ return firedEvents.get(id)?.has(event) ?? false;
13
+ };
4
14
  const $store = batched([$openedQuestionId, $feedList], () => {
5
15
  const openedQuestion = $openedQuestionId.get();
6
16
  if (!openedQuestion) {
@@ -8,15 +18,25 @@ export const detail = (transport, $openedQuestionId, $feedList) => {
8
18
  }
9
19
  const question = $feedList.get().data?.find((item) => item.id === openedQuestion?.questionId);
10
20
  const openedFrom = openedQuestion?.question?.openedFrom;
21
+ const questionId = openedQuestion.questionId;
11
22
  if (question) {
12
23
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
13
24
  // @ts-ignore
14
25
  question.openedFrom = openedFrom;
15
- return question;
26
+ return Object.assign(question, {
27
+ fireEvent: (event) => fireEvent(questionId, event),
28
+ isEventFired: (event) => isEventFired(questionId, event),
29
+ });
30
+ }
31
+ if (openedQuestion.question) {
32
+ return Object.assign(openedQuestion.question, {
33
+ fireEvent: (event) => fireEvent(questionId, event),
34
+ isEventFired: (event) => isEventFired(questionId, event),
35
+ });
16
36
  }
17
- return openedQuestion.question;
37
+ return undefined;
18
38
  });
19
- const $storeQuestionId = batched($store, (item) => (item && item.type === 'question' ? item.id : undefined));
39
+ const $storeQuestionId = batched($store, (item) => item?.id);
20
40
  const $extendedStore = $questionByUser($storeQuestionId, transport);
21
41
  const updateExtendedQuestion = (question) => {
22
42
  const currentQuestion = $extendedStore.get().data;
@@ -3,6 +3,7 @@ import { type GamesOverlaySettings, FeedItem, PlainMessage } from '@streamlayer/
3
3
  import type { GetApiResponseType } from '@streamlayer/sdk-web-api';
4
4
  import '@streamlayer/sdk-web-core/store';
5
5
  import { WritableAtom } from 'nanostores';
6
+ import { UserAccountInfo } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
6
7
  import * as queries from './queries';
7
8
  import { leaderboard } from './leaderboard';
8
9
  import { deepLink } from './deepLink';
@@ -50,6 +51,7 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
50
51
  activeQuestionId: GamificationBackground['activeQuestionId'];
51
52
  openedQuestionId: GamificationBackground['openedQuestionId'];
52
53
  advertisement: GamificationBackground['advertisement'];
54
+ withPickHistory: GamificationBackground['withPickHistory'];
53
55
  onboardingProcessed: WritableAtom<boolean>;
54
56
  friendsTabEnabled: WritableAtom<boolean>;
55
57
  skipOnboarding: boolean;
@@ -78,6 +80,7 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
78
80
  openUser: (friendId: string) => Promise<void>;
79
81
  closeUser: () => void;
80
82
  openOnboarding: () => void;
83
+ sendWebhookEvent: (questionId: string, userAccountInfo: UserAccountInfo) => Promise<import("@streamlayer/sl-eslib/sdkSettings/organization/webhooks/organization.webhooks_pb").SendWebhookEventResponse>;
81
84
  /**
82
85
  * Show in-app notification for active question
83
86
  * for interactive questions we show notification only if interactiveAllowed
@@ -4,7 +4,7 @@ import { QuestionStatus, QuestionType, FeatureType, SilenceSetting, PickHistoryS
4
4
  import { NotificationType } from '@streamlayer/sdk-web-notifications';
5
5
  import '@streamlayer/sdk-web-core/store';
6
6
  import { onStart } from 'nanostores';
7
- import { AdUnit, FactoidMediaMode, NotificationUseContentTexts, } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
7
+ import { AdUnit, FactoidMediaMode, NotificationEnabled, NotificationUseContentTexts, } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
8
8
  import * as queries from './queries';
9
9
  import * as actions from './queries/actions';
10
10
  import { leaderboard } from './leaderboard';
@@ -53,6 +53,7 @@ export class Gamification extends AbstractFeature {
53
53
  activeQuestionId;
54
54
  openedQuestionId;
55
55
  advertisement;
56
+ withPickHistory;
56
57
  onboardingProcessed;
57
58
  friendsTabEnabled;
58
59
  skipOnboarding;
@@ -77,6 +78,7 @@ export class Gamification extends AbstractFeature {
77
78
  this.feedList = this.background.feedList;
78
79
  this.betPack = this.background.betPack;
79
80
  this.currentUserId = this.background.userId;
81
+ this.withPickHistory = this.background.withPickHistory;
80
82
  this.onboardingProcessed = createSingleStore(!instance.sdk.withAuth);
81
83
  this.leaderboardId = new SingleStore(createSingleStore(this.settings.getValue('pinnedLeaderboardId')), 'pinnedLeaderboardId').getStore();
82
84
  this.notifications = instance.notifications;
@@ -113,11 +115,6 @@ export class Gamification extends AbstractFeature {
113
115
  this.cancels.add(this.onboardingStatus.$store.listen(this.checkInteractiveFlag));
114
116
  this.cancels.add(this.background.moderation.getStore().listen(this.checkInteractiveFlag));
115
117
  this.cancels.add(this.settings.subscribe(this.checkInteractiveFlag));
116
- this.cancels.add(this.onboardingStatus.$store.listen((status, prevStatus) => {
117
- if (prevStatus === undefined || status !== OnboardingStatus.Unset) {
118
- this.background.activeQuestionId.invalidate();
119
- }
120
- }));
121
118
  this.background.activeQuestionId.listen(this.showInApp);
122
119
  this.friendsTabEnabled = createSingleStore(false);
123
120
  this.cancels.add(instance.sdk.options.subscribe((data) => {
@@ -351,17 +348,13 @@ export class Gamification extends AbstractFeature {
351
348
  }
352
349
  }
353
350
  if (questionIndex === -1) {
354
- feedList.unshift(feedItem);
355
- eventBus.emit('poll', {
356
- action: 'received',
357
- payload: {
358
- isAd: feedItem.attributes?.adUnit == AdUnit.UNSET,
359
- questionId: feedItem.id,
360
- questionType: feedItem.attributes?.type,
361
- },
362
- });
351
+ if (this.feedList.isEnabled()) {
352
+ feedList.unshift(feedItem);
353
+ }
354
+ }
355
+ if (this.feedList.isEnabled()) {
356
+ this.feedList.getStore().mutate(feedList);
363
357
  }
364
- this.feedList.getStore().mutate(feedList);
365
358
  }));
366
359
  };
367
360
  // not used
@@ -392,7 +385,7 @@ export class Gamification extends AbstractFeature {
392
385
  action: 'voted',
393
386
  payload: {
394
387
  questionId,
395
- isAd: question?.adUnit == AdUnit.UNSET,
388
+ isAd: question?.adUnit == AdUnit.STANDART || question?.promotion?.adUnit == AdUnit.STANDART,
396
389
  questionType: question.type,
397
390
  },
398
391
  });
@@ -414,19 +407,22 @@ export class Gamification extends AbstractFeature {
414
407
  const extendedQuestionBeforeVote = { ...(data.get().data || {}) };
415
408
  const updateQuestionAndFieldList = () => {
416
409
  const feedList = this.feedList.getValues().data;
417
- if (!feedList) {
418
- return;
419
- }
420
- const questionIndex = feedList.findIndex((item) => item.id === questionId);
421
- const poll = feedList[questionIndex];
422
- const question = poll?.attributes?.attributes.case === 'question' && poll.attributes.attributes.value;
410
+ const questionIndex = feedList?.findIndex((item) => item.id === questionId);
411
+ const poll = questionIndex && questionIndex >= 0 && feedList ? feedList[questionIndex] : undefined;
412
+ const question = (poll?.attributes?.attributes.case === 'question' && poll?.attributes?.attributes.value) ||
413
+ extendedQuestionBeforeVote;
414
+ const isAd = poll?.attributes?.adUnit == AdUnit.STANDART ||
415
+ poll?.attributes?.adPromotion?.adUnit == AdUnit.STANDART ||
416
+ extendedQuestionBeforeVote?.adUnit == AdUnit.STANDART ||
417
+ extendedQuestionBeforeVote?.promotion?.adUnit == AdUnit.STANDART;
418
+ const questionType = poll?.attributes?.type || extendedQuestionBeforeVote?.type;
423
419
  if (question) {
424
420
  eventBus.emit('poll', {
425
421
  action: 'voted',
426
422
  payload: {
427
- isAd: poll.attributes?.adUnit == AdUnit.UNSET,
423
+ isAd,
428
424
  questionId,
429
- questionType: question.questionType,
425
+ questionType,
430
426
  },
431
427
  });
432
428
  const extendedQuestion = data.get().data;
@@ -436,18 +432,20 @@ export class Gamification extends AbstractFeature {
436
432
  const votedAnswerIdx = extendedQuestion.answers.findIndex((answer) => answer.id === answerId);
437
433
  const votedAnswer = extendedQuestion.answers[votedAnswerIdx];
438
434
  const percentsEqual = extendedQuestion.answers.every(({ percentageDecimal }, index) => percentageDecimal === extendedQuestionBeforeVote?.answers[index].percentageDecimal);
439
- // @ts-ignore
440
- feedList[questionIndex].attributes.attributes.value.answerId = answerId;
441
- // @ts-ignore
442
- feedList[questionIndex].attributes.attributes.value.openForVoting = false;
443
- // @ts-ignore
444
- feedList[questionIndex].attributes.attributes.value.text = votedAnswer?.text || '';
445
- if (correctAnswer) {
435
+ if (feedList && questionIndex && questionIndex >= 0) {
446
436
  // @ts-ignore
447
- feedList[questionIndex].attributes.attributes.value.status =
448
- correctAnswer.id === answerId ? PickHistoryStatus.WON : PickHistoryStatus.LOST;
437
+ feedList[questionIndex].attributes.attributes.value.answerId = answerId;
438
+ // @ts-ignore
439
+ feedList[questionIndex].attributes.attributes.value.openForVoting = false;
440
+ // @ts-ignore
441
+ feedList[questionIndex].attributes.attributes.value.text = votedAnswer?.text || '';
442
+ if (correctAnswer) {
443
+ // @ts-ignore
444
+ feedList[questionIndex].attributes.attributes.value.status =
445
+ correctAnswer.id === answerId ? PickHistoryStatus.WON : PickHistoryStatus.LOST;
446
+ }
447
+ this.feedList.getStore().mutate([...feedList]);
449
448
  }
450
- this.feedList.getStore().mutate([...feedList]);
451
449
  extendedQuestion.answers[votedAnswerIdx].correct = correctAnswer?.id === answerId;
452
450
  extendedQuestion.answers[votedAnswerIdx].youVoted = true;
453
451
  if (percentsEqual) {
@@ -495,12 +493,16 @@ export class Gamification extends AbstractFeature {
495
493
  animateHiding: false,
496
494
  });
497
495
  let questionType = question?.attributes?.type;
498
- let hasBanner = question?.attributes?.adUnit === AdUnit.STANDART && !!question?.attributes?.adPromotion?.banner?.imageUrl;
496
+ let hasBanner = (question?.attributes?.adUnit === AdUnit.STANDART ||
497
+ question?.attributes?.adPromotion?.adUnit === AdUnit.STANDART) &&
498
+ !!question?.attributes?.adPromotion?.banner?.imageUrl;
499
499
  if (!questionType && !openedFromBetPack) {
500
500
  const feedList = this.feedList.getStore().value?.data || [];
501
501
  const itemFromFeed = feedList.find((item) => item.id === questionId)?.attributes;
502
502
  questionType = itemFromFeed?.type;
503
- hasBanner = itemFromFeed?.adUnit === AdUnit.STANDART && !!itemFromFeed?.adPromotion?.banner?.imageUrl;
503
+ hasBanner =
504
+ (itemFromFeed?.adUnit === AdUnit.STANDART || itemFromFeed?.adPromotion?.adUnit === AdUnit.STANDART) &&
505
+ !!itemFromFeed?.adPromotion?.banner?.imageUrl;
504
506
  }
505
507
  const flags = {
506
508
  eventId: this.background.slStreamId.get() || '',
@@ -512,7 +514,7 @@ export class Gamification extends AbstractFeature {
512
514
  payload: {
513
515
  questionId,
514
516
  questionType,
515
- isAd: question?.attributes?.adUnit == AdUnit.UNSET,
517
+ isAd: question?.attributes?.adUnit != AdUnit.UNSET,
516
518
  questionOpenedFrom: question?.openedFrom === 'bet-pack' ? 'list' : question?.openedFrom,
517
519
  hasBanner,
518
520
  },
@@ -523,7 +525,7 @@ export class Gamification extends AbstractFeature {
523
525
  payload: {
524
526
  questionId,
525
527
  questionType,
526
- isAd: question?.attributes?.adUnit == AdUnit.UNSET,
528
+ isAd: question?.attributes?.adUnit != AdUnit.UNSET,
527
529
  questionOpenedFrom: question?.openedFrom === 'bet-pack' ? 'list' : question?.openedFrom,
528
530
  },
529
531
  });
@@ -606,6 +608,14 @@ export class Gamification extends AbstractFeature {
606
608
  payload: {},
607
609
  });
608
610
  };
611
+ sendWebhookEvent = (questionId, userAccountInfo) => {
612
+ return actions.sendWebhookEvent(this.transport, {
613
+ eventId: this.background.slStreamId.get(),
614
+ questionId,
615
+ userId: this.background.userId.get(),
616
+ userAccountInfo,
617
+ });
618
+ };
609
619
  /**
610
620
  * Show in-app notification for active question
611
621
  * for interactive questions we show notification only if interactiveAllowed
@@ -613,6 +623,9 @@ export class Gamification extends AbstractFeature {
613
623
  * skipping questions with inAppSilence === ON
614
624
  */
615
625
  showInApp = (question) => {
626
+ if (question.loading) {
627
+ return;
628
+ }
616
629
  const onboardingStatus = this.onboardingStatus.$store.get();
617
630
  const isAdUnit = question?.data?.question?.adUnit === AdUnit.STANDART;
618
631
  if (question && question.data && (isAdUnit || (onboardingStatus && onboardingStatus !== OnboardingStatus.Unset))) {
@@ -622,31 +635,36 @@ export class Gamification extends AbstractFeature {
622
635
  !question.data.question.marketClosed) {
623
636
  if (InteractiveQuestionTypes.has(question.data.question.type)) {
624
637
  if ((this.isInteractiveAllowed || isAdUnit) && question.data.question.notification !== undefined) {
625
- this.notifications.add({
626
- type: NotificationType.QUESTION,
627
- action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
628
- close: () => this.closeQuestion(question.data?.question?.id),
629
- autoHideDuration: 1000 * 60,
630
- id: this.background.getCurrentSessionId({
631
- prefix: 'notification',
632
- entity: question.data.question.id,
633
- }),
634
- emitEvent: true,
635
- data: {
636
- questionId: question.data.question.id,
637
- questionType: question.data.question.type,
638
- question: {
639
- title: question.data.question.notification.title,
640
- },
641
- inApp: {
642
- notification: question.data.question.notification,
643
- appearance: question.data.question.appearance,
644
- sponsorship: question.data.question.sponsorship,
645
- adUnit: question.data.question?.adUnit,
646
- sponsorLogo: question.data.question.promotion?.sponsor?.logo,
638
+ if (question.data.question.notification.enabled === NotificationEnabled.NOTIFICATION_ENABLED) {
639
+ this.notifications.add({
640
+ type: NotificationType.QUESTION,
641
+ action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
642
+ close: () => this.closeQuestion(question.data?.question?.id),
643
+ autoHideDuration: 1000 * 60,
644
+ id: this.background.getCurrentSessionId({
645
+ prefix: 'notification',
646
+ entity: question.data.question.id,
647
+ }),
648
+ emitEvent: true,
649
+ data: {
650
+ questionId: question.data.question.id,
651
+ questionType: question.data.question.type,
652
+ question: {
653
+ title: question.data.question.notification.title,
654
+ },
655
+ inApp: {
656
+ notification: question.data.question.notification,
657
+ appearance: question.data.question.appearance,
658
+ sponsorship: question.data.question.sponsorship,
659
+ adUnit: question.data.question?.adUnit,
660
+ sponsorLogo: question.data.question.promotion?.sponsor?.logo,
661
+ },
647
662
  },
648
- },
649
- });
663
+ });
664
+ }
665
+ else {
666
+ this.openQuestion(question.data?.question?.id, question.data?.feedItem);
667
+ }
650
668
  }
651
669
  }
652
670
  else if (question.data.question.type === QuestionType.FACTOID) {
package/lib/onboarding.js CHANGED
@@ -139,9 +139,6 @@ export const onboarding = (service, background, transport, notifications, option
139
139
  else {
140
140
  service.onboardingProcessed.set(true);
141
141
  }
142
- if (onboardingStatus === OnboardingStatus.Completed) {
143
- background.activeQuestionId.invalidate(); // verified, it's necessary
144
- }
145
142
  storage.setOnboardingInstantOpen({
146
143
  userId: background.userId.get() || '',
147
144
  organizationId: background.organizationId.get() || '',
@@ -1,7 +1,14 @@
1
1
  import type { Transport } from '@streamlayer/sdk-web-api';
2
+ import { UserAccountInfo } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
2
3
  export declare const submitAnswer: (transport: Transport, data: {
3
4
  questionId: string;
4
5
  answerId: string;
5
6
  }) => Promise<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").SubmitAnswerResponse>;
6
7
  export declare const submitInplay: (transport: Transport, eventId: string) => Promise<import("@bufbuild/protobuf").Message<"streamlayer.interactive.feed.SubmitInplayResponse">>;
7
8
  export declare const skipQuestion: (transport: Transport, questionId: string) => Promise<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").SkipQuestionResponse>;
9
+ export declare const sendWebhookEvent: (transport: Transport, opts: {
10
+ eventId?: string;
11
+ questionId: string;
12
+ userId?: string;
13
+ userAccountInfo: UserAccountInfo;
14
+ }) => Promise<import("@streamlayer/sl-eslib/sdkSettings/organization/webhooks/organization.webhooks_pb").SendWebhookEventResponse>;
@@ -1,5 +1,7 @@
1
1
  import { ConnectError, Code } from '@connectrpc/connect';
2
2
  import { Feed } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb';
3
+ import { UserAccountInfo } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
4
+ import { OrganizationWebhooks, WebhookEvent, WebhookNotificationType, } from '@streamlayer/sl-eslib/sdkSettings/organization/webhooks/organization.webhooks_pb';
3
5
  import { ERROR } from '../constants';
4
6
  export const submitAnswer = async (transport, data) => {
5
7
  const { client, createRequestOptions } = transport.createPromiseClient(Feed, { method: 'submitAnswer' });
@@ -25,3 +27,31 @@ export const skipQuestion = (transport, questionId) => {
25
27
  const contextValues = createRequestOptions({ retryAttempts: 0 });
26
28
  return client.skipQuestion({ data: { id: questionId } }, { contextValues });
27
29
  };
30
+ const userAccountInfoToNotificationType = (info) => {
31
+ switch (info) {
32
+ case UserAccountInfo.PHONE:
33
+ return WebhookNotificationType.PHONE;
34
+ case UserAccountInfo.EMAIL:
35
+ return WebhookNotificationType.MAIL;
36
+ default:
37
+ return WebhookNotificationType.UNSET;
38
+ }
39
+ };
40
+ export const sendWebhookEvent = (transport, opts) => {
41
+ const { client, createRequestOptions } = transport.createPromiseClient(OrganizationWebhooks, {
42
+ method: 'sendWebhookEvent',
43
+ });
44
+ const contextValues = createRequestOptions({ retryAttempts: 0 });
45
+ return client.sendWebhookEvent({
46
+ eventId: opts.eventId,
47
+ questionId: opts.questionId,
48
+ data: {
49
+ event: WebhookEvent.CLICK_BUTTON,
50
+ notificationType: userAccountInfoToNotificationType(opts.userAccountInfo),
51
+ userId: opts.userId,
52
+ description: '',
53
+ metadata: {},
54
+ customFields: {},
55
+ },
56
+ }, { contextValues });
57
+ };
@@ -441,8 +441,7 @@ export declare const getPromotionDetail: (promoId: string, transport: Transport)
441
441
  promotion: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionOptions_PromotionOptions | undefined;
442
442
  notification: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionNotification | undefined;
443
443
  } | undefined>;
444
- export declare const $pickHistory: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<(import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").PickHistory | undefined)[], any>;
445
- export declare const $feedList: ($slStreamId: ReadableAtom<string | undefined>, $interactiveAllowed: ReadableAtom<InteractiveAllowed>, $slUserId: ReadableAtom<string | undefined>, $slOrganizationId: ReadableAtom<string | undefined>, storage: GamificationStorage, onlyBetPack: boolean, transport: Transport) => import("@nanostores/query").FetcherStore<Omit<import("@streamlayer/sdk-web-types").RecursiveOmitHelper<FeedItem, "$typeName">, "$typeName">[], any>;
444
+ export declare const $feedList: ($slStreamId: ReadableAtom<string | undefined>, $interactiveAllowed: ReadableAtom<InteractiveAllowed>, $slUserId: ReadableAtom<string | undefined>, $slOrganizationId: ReadableAtom<string | undefined>, storage: GamificationStorage, onlyBetPack: boolean, $withPickHistory: ReadableAtom<boolean>, transport: Transport) => import("@nanostores/query").FetcherStore<Omit<import("@streamlayer/sdk-web-types").RecursiveOmitHelper<FeedItem, "$typeName">, "$typeName">[], any>;
446
445
  export declare const $activePromotionId: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<{
447
446
  id: string;
448
447
  question: {
@@ -115,28 +115,14 @@ export const getPromotionDetail = async (promoId, transport) => {
115
115
  const promotion = options?.options.case === 'promotion' ? options?.options.value : undefined;
116
116
  return { id: question.id, question, promotion, notification };
117
117
  };
118
- export const $pickHistory = (slStreamId, transport) => {
119
- const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'pickHistory', params: [slStreamId] });
120
- return transport.nanoquery.createFetcherStore(queryKey, {
121
- fetcher: async (_, __, eventId) => {
122
- if (!eventId) {
123
- return [];
124
- }
125
- const res = await client.pickHistory({
126
- eventId: eventId,
127
- });
128
- return res.data?.map(({ attributes }) => attributes);
129
- },
130
- });
131
- };
132
- export const $feedList = ($slStreamId, $interactiveAllowed, $slUserId, $slOrganizationId, storage, onlyBetPack, transport) => {
118
+ export const $feedList = ($slStreamId, $interactiveAllowed, $slUserId, $slOrganizationId, storage, onlyBetPack, $withPickHistory, transport) => {
133
119
  const { client, queryKey } = transport.createPromiseClient(Feed, {
134
120
  method: 'list',
135
- params: [$slStreamId, $interactiveAllowed, onlyBetPack ? 'true' : ''],
121
+ params: [$slStreamId, $interactiveAllowed, onlyBetPack ? 'true' : '', $withPickHistory],
136
122
  });
137
123
  return transport.nanoquery.createFetcherStore(queryKey, {
138
- fetcher: async (_, __, slStreamId, interactiveAllowed, onlyBetPack) => {
139
- if (!slStreamId || onlyBetPack) {
124
+ fetcher: async (_, __, slStreamId, interactiveAllowed, onlyBetPack, withPickHistory) => {
125
+ if (!slStreamId || onlyBetPack || !withPickHistory) {
140
126
  return [];
141
127
  }
142
128
  const res = await client.list({
@@ -158,7 +144,7 @@ export const $feedList = ($slStreamId, $interactiveAllowed, $slUserId, $slOrgani
158
144
  eventBus.emit('poll', {
159
145
  action: 'received',
160
146
  payload: {
161
- isAd: question.attributes?.adUnit == AdUnit.UNSET,
147
+ isAd: question.attributes?.adUnit != AdUnit.UNSET,
162
148
  questionId: question.id,
163
149
  questionType: question.attributes?.type,
164
150
  },
@@ -194,7 +180,7 @@ export const $activePromotionId = ($slStreamId, transport) => {
194
180
  },
195
181
  });
196
182
  const feedItem = res.data?.[0];
197
- if (feedItem.type === 'promotion') {
183
+ if (feedItem?.type === 'promotion') {
198
184
  return getPromotionDetail(feedItem.id, transport);
199
185
  }
200
186
  return undefined;
@@ -251,7 +237,7 @@ export const $betPack = ($slStreamId, $slUserId, $slOrganizationId, storage, tra
251
237
  eventBus.emit('poll', {
252
238
  action: 'received',
253
239
  payload: {
254
- isAd: question?.adUnit == AdUnit.UNSET,
240
+ isAd: question?.adUnit != AdUnit.UNSET,
255
241
  questionId: question.id,
256
242
  questionType: question.type,
257
243
  },
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "1.25.4",
3
+ "version": "1.26.0",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^2.2.2",
6
6
  "@fastify/deepmerge": "^2.0.0",
7
7
  "@streamlayer/sl-eslib": "^5.229.1",
8
8
  "uuid": "^11.1.0",
9
9
  "nanostores": "^1.1.0",
10
- "@streamlayer/sdk-web-api": "^1.16.4",
11
- "@streamlayer/sdk-web-core": "^1.21.4",
12
- "@streamlayer/sdk-web-interfaces": "^1.9.12",
13
- "@streamlayer/sdk-web-logger": "^1.0.106",
14
- "@streamlayer/sdk-web-notifications": "^1.3.68",
15
- "@streamlayer/sdk-web-storage": "^1.0.106",
16
- "@streamlayer/sdk-web-types": "^1.17.4"
10
+ "@streamlayer/sdk-web-api": "^1.16.6",
11
+ "@streamlayer/sdk-web-interfaces": "^1.9.14",
12
+ "@streamlayer/sdk-web-logger": "^1.0.108",
13
+ "@streamlayer/sdk-web-notifications": "^1.3.70",
14
+ "@streamlayer/sdk-web-core": "^1.21.6",
15
+ "@streamlayer/sdk-web-storage": "^1.0.108",
16
+ "@streamlayer/sdk-web-types": "^1.17.6"
17
17
  },
18
18
  "devDependencies": {
19
19
  "tslib": "^2.7.0"
@@ -23,24 +23,28 @@
23
23
  "typings": "./lib/index.d.ts",
24
24
  "exports": {
25
25
  ".": {
26
+ "types": "./lib/index.d.ts",
27
+ "import": "./lib/index.js",
26
28
  "module": "./lib/index.js",
27
- "require": "./lib/index.js",
28
- "types": "./lib/index.d.ts"
29
+ "default": "./lib/index.js"
29
30
  },
30
31
  "./gamification": {
32
+ "types": "./lib/gamification.d.ts",
33
+ "import": "./lib/gamification.js",
31
34
  "module": "./lib/gamification.js",
32
- "require": "./lib/gamification.js",
33
- "types": "./lib/gamification.d.ts"
35
+ "default": "./lib/gamification.js"
34
36
  },
35
37
  "./externalAd": {
38
+ "types": "./lib/advertisement/externalAd/index.d.ts",
39
+ "import": "./lib/advertisement/externalAd/index.js",
36
40
  "module": "./lib/advertisement/externalAd/index.js",
37
- "require": "./lib/advertisement/externalAd/index.js",
38
- "types": "./lib/advertisement/externalAd/index.d.ts"
41
+ "default": "./lib/advertisement/externalAd/index.js"
39
42
  },
40
43
  "./highlights": {
44
+ "types": "./lib/highlights.d.ts",
45
+ "import": "./lib/highlights.js",
41
46
  "module": "./lib/highlights.js",
42
- "require": "./lib/highlights.js",
43
- "types": "./lib/highlights.d.ts"
47
+ "default": "./lib/highlights.js"
44
48
  }
45
49
  },
46
50
  "files": [