@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.
- package/lib/background.d.ts +1 -0
- package/lib/background.js +22 -4
- package/lib/detail.d.ts +5 -1
- package/lib/detail.js +23 -3
- package/lib/gamification.d.ts +3 -0
- package/lib/gamification.js +81 -63
- package/lib/onboarding.js +0 -3
- package/lib/queries/actions.d.ts +7 -0
- package/lib/queries/actions.js +30 -0
- package/lib/queries/index.d.ts +1 -2
- package/lib/queries/index.js +7 -21
- package/package.json +20 -16
package/lib/background.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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">
|
|
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
|
|
37
|
+
return undefined;
|
|
18
38
|
});
|
|
19
|
-
const $storeQuestionId = batched($store, (item) =>
|
|
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;
|
package/lib/gamification.d.ts
CHANGED
|
@@ -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
|
package/lib/gamification.js
CHANGED
|
@@ -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.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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.
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
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
|
|
423
|
+
isAd,
|
|
428
424
|
questionId,
|
|
429
|
-
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
|
-
|
|
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.
|
|
448
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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() || '',
|
package/lib/queries/actions.d.ts
CHANGED
|
@@ -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>;
|
package/lib/queries/actions.js
CHANGED
|
@@ -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
|
+
};
|
package/lib/queries/index.d.ts
CHANGED
|
@@ -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 $
|
|
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: {
|
package/lib/queries/index.js
CHANGED
|
@@ -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 $
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
11
|
-
"@streamlayer/sdk-web-
|
|
12
|
-
"@streamlayer/sdk-web-
|
|
13
|
-
"@streamlayer/sdk-web-
|
|
14
|
-
"@streamlayer/sdk-web-
|
|
15
|
-
"@streamlayer/sdk-web-storage": "^1.0.
|
|
16
|
-
"@streamlayer/sdk-web-types": "^1.17.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"types": "./lib/highlights.d.ts"
|
|
47
|
+
"default": "./lib/highlights.js"
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"files": [
|