@streamlayer/feature-gamification 0.38.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/background.d.ts +7 -2
- package/lib/background.js +21 -5
- package/lib/constants.d.ts +4 -0
- package/lib/constants.js +5 -0
- package/lib/detail.d.ts +7 -9
- package/lib/detail.js +39 -87
- package/lib/gamification.d.ts +10 -4
- package/lib/gamification.js +244 -56
- package/lib/leaderboard.js +24 -27
- package/lib/onboarding.js +7 -2
- package/lib/queries/actions.js +13 -2
- package/lib/queries/deepLink.js +2 -0
- package/lib/queries/index.d.ts +1 -1
- package/lib/queries/index.js +5 -0
- package/lib/queries/leaderboard.d.ts +2 -1
- package/lib/queries/leaderboard.js +4 -0
- package/lib/queries/moderation.js +2 -0
- package/lib/userSummary.d.ts +8 -0
- package/lib/userSummary.js +38 -0
- package/package.json +11 -11
package/lib/background.d.ts
CHANGED
|
@@ -27,7 +27,9 @@ export declare class GamificationBackground {
|
|
|
27
27
|
/** opened question, using to download statistics */
|
|
28
28
|
openedQuestionId: WritableAtom<{
|
|
29
29
|
questionId: string;
|
|
30
|
-
question?: FeedItem
|
|
30
|
+
question?: FeedItem & {
|
|
31
|
+
openedFrom?: 'list' | 'notification';
|
|
32
|
+
};
|
|
31
33
|
} | undefined>;
|
|
32
34
|
/** opened question statistics */
|
|
33
35
|
openedQuestion: ReturnType<typeof detail>;
|
|
@@ -44,6 +46,7 @@ export declare class GamificationBackground {
|
|
|
44
46
|
questionSubscription?: ReturnType<typeof queries.questionSubscription>;
|
|
45
47
|
private notifications;
|
|
46
48
|
private log;
|
|
49
|
+
private transport;
|
|
47
50
|
constructor(instance: StreamLayerContext);
|
|
48
51
|
/**
|
|
49
52
|
* Get id for notifications and link with current session
|
|
@@ -64,7 +67,9 @@ export declare class GamificationBackground {
|
|
|
64
67
|
/**
|
|
65
68
|
* Open question and mark notification for this question as viewed
|
|
66
69
|
*/
|
|
67
|
-
openQuestion: (questionId: string, question?: FeedItem
|
|
70
|
+
openQuestion: (questionId: string, question?: FeedItem & {
|
|
71
|
+
openedFrom?: 'list' | 'notification';
|
|
72
|
+
}) => void;
|
|
68
73
|
/**
|
|
69
74
|
* Close question and mark notification for this question as viewed
|
|
70
75
|
*/
|
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';
|
|
@@ -40,7 +41,9 @@ export class GamificationBackground {
|
|
|
40
41
|
questionSubscription;
|
|
41
42
|
notifications;
|
|
42
43
|
log;
|
|
44
|
+
transport;
|
|
43
45
|
constructor(instance) {
|
|
46
|
+
this.transport = instance.transport;
|
|
44
47
|
this.log = createLogger('gamification-background');
|
|
45
48
|
this.slStreamId = instance.stores.slStreamId.getAtomStore();
|
|
46
49
|
this.organizationId = instance.stores.organizationSettings.getAtomStore();
|
|
@@ -58,9 +61,7 @@ export class GamificationBackground {
|
|
|
58
61
|
if (item?.questionId) {
|
|
59
62
|
this.questionSubscription = queries.questionSubscription(item.questionId, instance.transport);
|
|
60
63
|
this.questionSubscription.addListener('feed-subscription-opened-question', (response) => {
|
|
61
|
-
|
|
62
|
-
this.openedQuestion.updateExtendedQuestion(response.data?.attributes?.question);
|
|
63
|
-
});
|
|
64
|
+
this.openedQuestion.updateExtendedQuestion(response.data?.attributes?.question);
|
|
64
65
|
});
|
|
65
66
|
this.questionSubscription.connect();
|
|
66
67
|
}
|
|
@@ -75,9 +76,19 @@ export class GamificationBackground {
|
|
|
75
76
|
});
|
|
76
77
|
this.feedSubscription = queries.feedSubscription(this.slStreamId, instance.transport);
|
|
77
78
|
this.feedSubscription.addListener('feed-subscription-active-question', (response) => {
|
|
78
|
-
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
const $activeQuestionId = this.activeQuestionId.store;
|
|
82
|
+
const activeQuestionId = $activeQuestionId.get().data?.question?.id;
|
|
83
|
+
const question = response.data?.attributes?.question;
|
|
84
|
+
if (!question) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// skip update question, avoid race condition
|
|
88
|
+
if (activeQuestionId && question.status === QuestionStatus.RESOLVED && question.id !== activeQuestionId) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
79
91
|
if ($activeQuestionId) {
|
|
80
|
-
this.activeQuestionId;
|
|
81
92
|
$activeQuestionId.mutate(response.data?.attributes);
|
|
82
93
|
}
|
|
83
94
|
});
|
|
@@ -121,6 +132,11 @@ export class GamificationBackground {
|
|
|
121
132
|
};
|
|
122
133
|
disconnect = () => {
|
|
123
134
|
this.feedSubscription?.disconnect();
|
|
135
|
+
if (this.questionSubscription !== undefined) {
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
this.transport.removeSubscription(this.questionSubscription);
|
|
138
|
+
this.questionSubscription = undefined;
|
|
139
|
+
}
|
|
124
140
|
};
|
|
125
141
|
/**
|
|
126
142
|
* Open question and mark notification for this question as viewed
|
package/lib/constants.js
ADDED
package/lib/detail.d.ts
CHANGED
|
@@ -2,17 +2,15 @@ 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
|
-
type ExtendedQuestionStore = {
|
|
6
|
-
data?: ExtendedQuestion;
|
|
7
|
-
loading?: boolean;
|
|
8
|
-
error?: string;
|
|
9
|
-
};
|
|
10
5
|
export declare const detail: (transport: Transport, $openedQuestionId: ReadableAtom<{
|
|
11
6
|
questionId: string;
|
|
12
|
-
question?: FeedItem
|
|
7
|
+
question?: FeedItem & {
|
|
8
|
+
openedFrom?: 'list' | 'notification';
|
|
9
|
+
};
|
|
13
10
|
} | undefined>, $feedList: ReturnType<GamificationBackground['feedList']['getStore']>) => {
|
|
14
|
-
$store: import("
|
|
15
|
-
|
|
11
|
+
$store: ReadableAtom<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem | (import("@bufbuild/protobuf").PlainMessage<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem> & {
|
|
12
|
+
openedFrom?: "list" | "notification" | undefined;
|
|
13
|
+
}) | undefined>;
|
|
14
|
+
$extendedStore: import("@nanostores/query").FetcherStore<import("@bufbuild/protobuf").PlainMessage<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion>, any>;
|
|
16
15
|
updateExtendedQuestion: (question: ExtendedQuestion | undefined) => void;
|
|
17
16
|
};
|
|
18
|
-
export {};
|
package/lib/detail.js
CHANGED
|
@@ -1,96 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { onMount } from 'nanostores';
|
|
4
|
-
import { getQuestionByUser } from './queries';
|
|
5
|
-
const mergeArray = (options) => (target, source) => {
|
|
6
|
-
let i = 0;
|
|
7
|
-
const tl = target.length;
|
|
8
|
-
const sl = source.length;
|
|
9
|
-
const il = Math.max(tl, sl);
|
|
10
|
-
const result = new Array(il);
|
|
11
|
-
for (i = 0; i < il; ++i) {
|
|
12
|
-
if (i < sl) {
|
|
13
|
-
result[i] = options.deepmerge(target[i], source[i]);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
result[i] = options.clone(target[i]);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
|
-
const mergeQuestion = deepmerge({ mergeArray });
|
|
1
|
+
import { batched } from 'nanostores';
|
|
2
|
+
import { $questionByUser } from './queries';
|
|
22
3
|
export const detail = (transport, $openedQuestionId, $feedList) => {
|
|
23
|
-
const $store =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
else {
|
|
36
|
-
console.error('Feed list is not loaded yet. Issue with the opened question.');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
$store.set(undefined);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
const cancel2 = $feedList.subscribe((feedList) => {
|
|
44
|
-
const openedQuestion = $openedQuestionId.get();
|
|
45
|
-
if (feedList.data && openedQuestion) {
|
|
46
|
-
const question = $feedList.get().data?.find((item) => item.id === openedQuestion.questionId);
|
|
47
|
-
if (question) {
|
|
48
|
-
$store.set(question);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.error('Feed list is not loaded yet. Issue with the opened question.');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
return () => {
|
|
56
|
-
cancel1();
|
|
57
|
-
cancel2();
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
const $extendedStore = createMapStore({
|
|
61
|
-
data: undefined,
|
|
62
|
-
loading: undefined,
|
|
63
|
-
error: undefined,
|
|
64
|
-
});
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
-
$store.subscribe(async (item) => {
|
|
67
|
-
if (item) {
|
|
68
|
-
if (item.type === 'question') {
|
|
69
|
-
$extendedStore.setKey('loading', true);
|
|
70
|
-
const question = await getQuestionByUser(item.id, transport);
|
|
71
|
-
$extendedStore.set({ data: question, loading: false });
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
4
|
+
const $store = batched([$openedQuestionId, $feedList], () => {
|
|
5
|
+
const openedQuestion = $openedQuestionId.get();
|
|
6
|
+
if (!openedQuestion) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const question = $feedList.get().data?.find((item) => item.id === openedQuestion?.questionId);
|
|
10
|
+
const openedFrom = openedQuestion?.question?.openedFrom;
|
|
11
|
+
if (question) {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
question.openedFrom = openedFrom;
|
|
15
|
+
return question;
|
|
74
16
|
}
|
|
75
|
-
|
|
17
|
+
return openedQuestion.question;
|
|
76
18
|
});
|
|
19
|
+
const $storeQuestionId = batched($store, (item) => (item && item.type === 'question' ? item.id : undefined));
|
|
20
|
+
const $extendedStore = $questionByUser($storeQuestionId, transport);
|
|
77
21
|
const updateExtendedQuestion = (question) => {
|
|
78
22
|
const currentQuestion = $extendedStore.get().data;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* can be overwritten by the subscription response,
|
|
83
|
-
* which does not include user-specific data.
|
|
84
|
-
*/
|
|
85
|
-
for (const answer of question.answers) {
|
|
86
|
-
if (answer.youVoted !== true) {
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
88
|
-
// @ts-ignore
|
|
89
|
-
delete answer.youVoted;
|
|
90
|
-
}
|
|
23
|
+
const mergeQuestionAnswers = (currentAnswers, newAnswers) => {
|
|
24
|
+
if (!currentAnswers || !newAnswers) {
|
|
25
|
+
return currentAnswers || newAnswers || [];
|
|
91
26
|
}
|
|
92
|
-
|
|
93
|
-
|
|
27
|
+
const answers = [];
|
|
28
|
+
for (let i = 0; i < currentAnswers.length; i++) {
|
|
29
|
+
answers.push({
|
|
30
|
+
...currentAnswers[i],
|
|
31
|
+
...newAnswers[i],
|
|
32
|
+
correct: currentAnswers[i].correct,
|
|
33
|
+
youVoted: currentAnswers[i].youVoted,
|
|
34
|
+
pointsEarned: currentAnswers[i].pointsEarned,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return answers;
|
|
38
|
+
};
|
|
39
|
+
$extendedStore.mutate({
|
|
40
|
+
...question,
|
|
41
|
+
options: currentQuestion?.options,
|
|
42
|
+
subject: currentQuestion?.subject,
|
|
43
|
+
appearance: currentQuestion?.appearance,
|
|
44
|
+
answers: mergeQuestionAnswers(currentQuestion?.answers, question?.answers),
|
|
45
|
+
});
|
|
94
46
|
};
|
|
95
47
|
return { $store, $extendedStore, updateExtendedQuestion };
|
|
96
48
|
};
|
package/lib/gamification.d.ts
CHANGED
|
@@ -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:
|
|
25
|
+
userSummary: ReturnType<typeof summary>;
|
|
25
26
|
/** feed list (pick history) */
|
|
26
27
|
feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
|
|
27
28
|
/** friends list */
|
|
@@ -44,20 +45,25 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
|
|
|
44
45
|
openedUser: WritableAtom<LeaderboardItem | undefined>;
|
|
45
46
|
closeFeature: () => void;
|
|
46
47
|
openFeature: () => void;
|
|
48
|
+
feedSubscription: GamificationBackground['feedSubscription'];
|
|
49
|
+
activeQuestionId: GamificationBackground['activeQuestionId'];
|
|
50
|
+
openedQuestionId: GamificationBackground['openedQuestionId'];
|
|
47
51
|
private notifications;
|
|
48
52
|
private transport;
|
|
49
53
|
/** gamification background class, handle subscriptions and notifications for closed overlay */
|
|
50
54
|
private background;
|
|
51
55
|
/** Browser cache */
|
|
52
56
|
private storage;
|
|
57
|
+
private submitAnswerTimeout;
|
|
53
58
|
constructor(config: FeatureProps, source: FeatureSource, instance: StreamLayerContext);
|
|
54
59
|
get isInteractiveAllowed(): boolean;
|
|
55
60
|
checkInteractiveFlag: () => void;
|
|
56
|
-
connect: (
|
|
61
|
+
connect: () => void;
|
|
57
62
|
disconnect: () => void;
|
|
58
63
|
submitAnswer: (questionId: string, answerId: string) => Promise<void>;
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
openQuestion: (questionId: string, question?: FeedItem & {
|
|
65
|
+
openedFrom?: 'list' | 'notification';
|
|
66
|
+
}) => void;
|
|
61
67
|
closeQuestion: (questionId?: string) => void;
|
|
62
68
|
openUser: (userId: string) => void;
|
|
63
69
|
closeUser: () => void;
|
package/lib/gamification.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
|
+
import { AbstractFeature, ApiStore, SingleStore, createSingleStore, eventBus, } from '@streamlayer/sdk-web-interfaces';
|
|
3
|
+
import { QuestionStatus, QuestionType, FeatureType, SilenceSetting, PickHistoryStatus, } from '@streamlayer/sdk-web-types';
|
|
3
4
|
import { NotificationType } from '@streamlayer/sdk-web-notifications';
|
|
4
5
|
import '@streamlayer/sdk-web-core/store';
|
|
5
6
|
import * as queries from './queries';
|
|
@@ -9,6 +10,9 @@ import { leaderboard } from './leaderboard';
|
|
|
9
10
|
import { deepLink } from './deepLink';
|
|
10
11
|
import { OnboardingStatus, onboarding } from './onboarding';
|
|
11
12
|
import { GamificationBackground, InteractiveAllowed } from './background';
|
|
13
|
+
import { ERROR } from './constants';
|
|
14
|
+
import { $questionByUser } from './queries';
|
|
15
|
+
import { summary } from './userSummary';
|
|
12
16
|
const InteractiveQuestionTypes = new Set([QuestionType.POLL, QuestionType.PREDICTION, QuestionType.TRIVIA]);
|
|
13
17
|
/**
|
|
14
18
|
* Gamification (Games) Overlay
|
|
@@ -41,17 +45,23 @@ export class Gamification extends AbstractFeature {
|
|
|
41
45
|
openedUser;
|
|
42
46
|
closeFeature;
|
|
43
47
|
openFeature;
|
|
48
|
+
feedSubscription;
|
|
49
|
+
activeQuestionId;
|
|
50
|
+
openedQuestionId;
|
|
44
51
|
notifications;
|
|
45
52
|
transport;
|
|
46
53
|
/** gamification background class, handle subscriptions and notifications for closed overlay */
|
|
47
54
|
background;
|
|
48
55
|
/** Browser cache */
|
|
49
56
|
storage;
|
|
57
|
+
submitAnswerTimeout;
|
|
50
58
|
constructor(config, source, instance) {
|
|
51
59
|
super(config, source);
|
|
52
60
|
this.background = new GamificationBackground(instance);
|
|
61
|
+
this.feedSubscription = this.background.feedSubscription;
|
|
62
|
+
this.activeQuestionId = this.background.activeQuestionId;
|
|
63
|
+
this.openedQuestionId = this.background.openedQuestionId;
|
|
53
64
|
this.storage = new GamificationStorage();
|
|
54
|
-
this.userSummary = new ApiStore(queries.$userSummary(this.background.slStreamId, this.background.userId, instance.transport), 'gamification:userSummary');
|
|
55
65
|
this.feedList = this.background.feedList;
|
|
56
66
|
this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
|
|
57
67
|
this.currentUserId = this.background.userId;
|
|
@@ -64,21 +74,13 @@ export class Gamification extends AbstractFeature {
|
|
|
64
74
|
this.openFeature = () => instance.sdk.openFeature(FeatureType.GAMES);
|
|
65
75
|
this.openedQuestion = this.background.openedQuestion;
|
|
66
76
|
this.deepLink = deepLink(this.transport, this.background.slStreamId, instance.stores.providerStreamId.getStore(), this.background.userId);
|
|
77
|
+
this.userSummary = summary(this.background.slStreamId, this.background.userId, this.friends, this.transport);
|
|
67
78
|
this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends);
|
|
68
|
-
this.
|
|
69
|
-
if (status === FeatureStatus.Ready) {
|
|
70
|
-
this.connect(instance.transport);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
this.disconnect();
|
|
74
|
-
}
|
|
75
|
-
});
|
|
79
|
+
this.connect();
|
|
76
80
|
// refresh leaderboard on user summary update after earning points
|
|
77
|
-
this.userSummary.listen((userSummary) => {
|
|
78
|
-
if (this.leaderboardList.$store.lc !== 0 && userSummary?.
|
|
79
|
-
|
|
80
|
-
this.leaderboardList.invalidate();
|
|
81
|
-
});
|
|
81
|
+
this.userSummary.$store.listen((userSummary) => {
|
|
82
|
+
if (this.leaderboardList.$store.lc !== 0 && userSummary?.summary) {
|
|
83
|
+
this.leaderboardList.invalidate(); // verified, it's necessary
|
|
82
84
|
}
|
|
83
85
|
});
|
|
84
86
|
/**
|
|
@@ -103,41 +105,79 @@ export class Gamification extends AbstractFeature {
|
|
|
103
105
|
const allowed = !onboardingEnabled || onboardingCompleted || optInEnabled !== true;
|
|
104
106
|
this.background.interactiveAllowed.set(allowed ? InteractiveAllowed.ALLOWED : InteractiveAllowed.DISALLOWED);
|
|
105
107
|
};
|
|
106
|
-
connect = (
|
|
107
|
-
this.
|
|
108
|
-
this.leaderboardList.invalidate();
|
|
109
|
-
this.feedList.invalidate();
|
|
110
|
-
this.friends.invalidate();
|
|
111
|
-
this.background.feedSubscription.addListener('feed-subscription-prediction-close', (response) => {
|
|
108
|
+
connect = () => {
|
|
109
|
+
this.background.feedSubscription.addListener('feed-subscription-prediction-close', async (response) => {
|
|
112
110
|
if (!this.isInteractiveAllowed) {
|
|
113
111
|
return;
|
|
114
112
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
const question = response.data?.attributes?.question;
|
|
114
|
+
const feedItem = response.data?.attributes?.feedItem;
|
|
115
|
+
if (!question || !feedItem?.attributes) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const { status, type, id, answers } = question;
|
|
119
|
+
if (status === QuestionStatus.RESOLVED && type === QuestionType.PREDICTION) {
|
|
120
|
+
const notificationId = this.background.getCurrentSessionId({
|
|
121
|
+
prefix: `notification-id:${id}`,
|
|
122
|
+
});
|
|
123
|
+
const feedList = [...(this.feedList.getValues().data || [])];
|
|
124
|
+
const questionFromFeedListIndex = feedList.findIndex((item) => item.id === id);
|
|
125
|
+
const questionFromFeedList = feedList[questionFromFeedListIndex];
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
let votedAnswerId = questionFromFeedList?.attributes?.attributes?.value?.answerId;
|
|
128
|
+
// get voted answer id from extended question or feed list
|
|
129
|
+
const data = $questionByUser(id, this.transport);
|
|
130
|
+
// order of operations is important here
|
|
131
|
+
const cancel = data.subscribe(() => { });
|
|
132
|
+
await data.get().promise;
|
|
133
|
+
const extendedQuestion = data.get().data;
|
|
134
|
+
cancel();
|
|
135
|
+
window.requestAnimationFrame(() => {
|
|
136
|
+
data.invalidate();
|
|
137
|
+
});
|
|
138
|
+
// get extended question data and mark as dirty
|
|
139
|
+
if (!votedAnswerId) {
|
|
140
|
+
votedAnswerId = extendedQuestion?.answers.find(({ youVoted }) => youVoted)?.id;
|
|
120
141
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
const correctAnswer = answers.find(({ correct }) => correct);
|
|
143
|
+
const votedAnswer = votedAnswerId ? answers.find(({ id }) => id === votedAnswerId) : undefined;
|
|
144
|
+
const votedCorrect = !!votedAnswer?.correct;
|
|
145
|
+
// update question in feed list if it's there
|
|
146
|
+
if (questionFromFeedList) {
|
|
147
|
+
if (feedList[questionFromFeedListIndex]?.attributes?.attributes.case === 'question') {
|
|
148
|
+
try {
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
feedList[questionFromFeedListIndex].attributes.attributes.value.answerId = votedAnswerId;
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
feedList[questionFromFeedListIndex].attributes.attributes.value.openForVoting = false;
|
|
153
|
+
if (votedAnswerId) {
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
feedList[questionFromFeedListIndex].attributes.attributes.value.status = votedCorrect
|
|
156
|
+
? PickHistoryStatus.WON
|
|
157
|
+
: PickHistoryStatus.LOST;
|
|
158
|
+
}
|
|
159
|
+
// eslint-disable-next-line no-empty
|
|
160
|
+
}
|
|
161
|
+
catch (e) { }
|
|
162
|
+
this.feedList.getStore().mutate(feedList);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!votedAnswer || !correctAnswer)
|
|
166
|
+
return;
|
|
167
|
+
// avoid showing notification if question already opened
|
|
168
|
+
if (this.openedQuestionId.get()?.questionId !== question.id) {
|
|
131
169
|
this.notifications.add({
|
|
132
170
|
type: NotificationType.QUESTION_RESOLVED,
|
|
133
171
|
action: () => this.openQuestion(question.id, feedItem),
|
|
134
172
|
close: () => this.closeQuestion(id),
|
|
135
|
-
autoHideDuration:
|
|
173
|
+
autoHideDuration: votedCorrect ? 15000 : 12000,
|
|
136
174
|
id: notificationId,
|
|
175
|
+
emitEvent: false,
|
|
137
176
|
data: {
|
|
177
|
+
questionId: id,
|
|
138
178
|
questionType: QuestionType.PREDICTION,
|
|
139
179
|
question: {
|
|
140
|
-
title:
|
|
180
|
+
title: votedCorrect
|
|
141
181
|
? `Congratulations! You answered correctly! You won ${correctAnswer.points} pts!`
|
|
142
182
|
: `Better luck next time! Correct: ${correctAnswer?.text}!`,
|
|
143
183
|
votedAnswer: {
|
|
@@ -145,41 +185,183 @@ export class Gamification extends AbstractFeature {
|
|
|
145
185
|
points: votedAnswer?.points,
|
|
146
186
|
},
|
|
147
187
|
correctAnswerTitle: correctAnswer?.text,
|
|
148
|
-
correct:
|
|
188
|
+
correct: !!votedCorrect,
|
|
149
189
|
predictionResult: status === QuestionStatus.RESOLVED,
|
|
150
190
|
questionTitle: question?.subject,
|
|
151
191
|
},
|
|
152
192
|
},
|
|
153
193
|
});
|
|
154
|
-
this.userSummary.invalidate();
|
|
155
194
|
}
|
|
156
|
-
|
|
195
|
+
this.userSummary.invalidate(); // verified, it's necessary
|
|
196
|
+
}
|
|
157
197
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
// update feed list on question update received from subscription
|
|
199
|
+
// add new question to the top of the list
|
|
200
|
+
this.background.feedSubscription.addListener('feed-subscription-questions-list', (response) => {
|
|
201
|
+
const feedList = [...(this.feedList.getStore().value?.data || [])];
|
|
202
|
+
const feedItem = response.data?.attributes?.feedItem;
|
|
203
|
+
const questionIndex = feedList.findIndex((item) => item.id === feedItem?.id);
|
|
204
|
+
if (!feedItem) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (questionIndex !== -1) {
|
|
208
|
+
if (feedItem.attributes?.attributes.case === 'question' &&
|
|
209
|
+
feedList[questionIndex].attributes?.attributes.case === 'question') {
|
|
210
|
+
const prev = feedList[questionIndex];
|
|
211
|
+
if (prev.attributes) {
|
|
212
|
+
feedList[questionIndex] = {
|
|
213
|
+
...feedList[questionIndex],
|
|
214
|
+
attributes: {
|
|
215
|
+
...prev.attributes,
|
|
216
|
+
attributes: {
|
|
217
|
+
...prev.attributes.attributes,
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
219
|
+
// @ts-ignore
|
|
220
|
+
value: {
|
|
221
|
+
...prev.attributes.attributes.value,
|
|
222
|
+
...feedItem.attributes.attributes.value,
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
224
|
+
// @ts-ignore
|
|
225
|
+
answerId: prev.attributes.attributes.value.answerId,
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
227
|
+
// @ts-ignore
|
|
228
|
+
status: prev.attributes.attributes.value.status,
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
openForVoting: prev.attributes.attributes.value.openForVoting,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const prev = feedList[questionIndex];
|
|
240
|
+
if (prev.attributes && feedItem.attributes) {
|
|
241
|
+
feedList[questionIndex] = {
|
|
242
|
+
...feedList[questionIndex],
|
|
243
|
+
attributes: {
|
|
244
|
+
...prev.attributes,
|
|
245
|
+
attributes: {
|
|
246
|
+
...prev.attributes.attributes,
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
value: {
|
|
250
|
+
...prev.attributes.attributes.value,
|
|
251
|
+
...feedItem.attributes.attributes.value,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (questionIndex === -1) {
|
|
260
|
+
feedList.unshift(feedItem);
|
|
261
|
+
eventBus.emit('poll', {
|
|
262
|
+
action: 'received',
|
|
263
|
+
payload: {
|
|
264
|
+
questionId: feedItem.id,
|
|
265
|
+
questionType: feedItem.attributes?.type,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
this.feedList.getStore().mutate(feedList);
|
|
162
270
|
});
|
|
163
271
|
};
|
|
272
|
+
// not used
|
|
164
273
|
disconnect = () => {
|
|
274
|
+
this.background.feedSubscription.removeListener('feed-subscription-prediction-close');
|
|
165
275
|
this.background.feedSubscription.removeListener('feed-subscription-questions-list');
|
|
166
276
|
};
|
|
167
277
|
submitAnswer = async (questionId, answerId) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
278
|
+
const data = $questionByUser(questionId, this.transport);
|
|
279
|
+
const updateQuestionAndFieldList = () => {
|
|
280
|
+
const feedList = this.feedList.getValues().data;
|
|
281
|
+
if (!feedList) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const questionIndex = feedList.findIndex((item) => item.id === questionId);
|
|
285
|
+
const poll = feedList[questionIndex];
|
|
286
|
+
const question = poll?.attributes?.attributes.case === 'question' && poll.attributes.attributes.value;
|
|
287
|
+
if (question) {
|
|
288
|
+
eventBus.emit('poll', {
|
|
289
|
+
action: 'voted',
|
|
290
|
+
payload: {
|
|
291
|
+
questionId,
|
|
292
|
+
questionType: question.questionType,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
const cancel = data.subscribe(() => { });
|
|
296
|
+
const extendedQuestion = data.get().data;
|
|
297
|
+
cancel();
|
|
298
|
+
if (extendedQuestion) {
|
|
299
|
+
const correctAnswer = extendedQuestion.answers.find((answer) => answer.correct === true);
|
|
300
|
+
const votedAnswerIdx = extendedQuestion.answers.findIndex((answer) => answer.id === answerId);
|
|
301
|
+
const votedAnswer = extendedQuestion.answers[votedAnswerIdx];
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
feedList[questionIndex].attributes.attributes.value.answerId = answerId;
|
|
304
|
+
// @ts-ignore
|
|
305
|
+
feedList[questionIndex].attributes.attributes.value.openForVoting = false;
|
|
306
|
+
// @ts-ignore
|
|
307
|
+
feedList[questionIndex].attributes.attributes.value.text = votedAnswer?.text || '';
|
|
308
|
+
if (correctAnswer) {
|
|
309
|
+
// @ts-ignore
|
|
310
|
+
feedList[questionIndex].attributes.attributes.value.status =
|
|
311
|
+
correctAnswer.id === answerId ? PickHistoryStatus.WON : PickHistoryStatus.LOST;
|
|
312
|
+
}
|
|
313
|
+
this.feedList.getStore().mutate([...feedList]);
|
|
314
|
+
extendedQuestion.answers[votedAnswerIdx].correct = correctAnswer?.id === answerId;
|
|
315
|
+
extendedQuestion.answers[votedAnswerIdx].youVoted = true;
|
|
316
|
+
extendedQuestion.answers.forEach((answer) => {
|
|
317
|
+
answer.percentageDecimal = 0;
|
|
318
|
+
});
|
|
319
|
+
if (correctAnswer?.id === answerId) {
|
|
320
|
+
extendedQuestion.answers[votedAnswerIdx].pointsEarned =
|
|
321
|
+
extendedQuestion.status === QuestionStatus.RESOLVED ? 0 : correctAnswer.points;
|
|
322
|
+
}
|
|
323
|
+
data.mutate({ ...extendedQuestion });
|
|
324
|
+
}
|
|
325
|
+
if (this.submitAnswerTimeout) {
|
|
326
|
+
clearTimeout(this.submitAnswerTimeout);
|
|
327
|
+
}
|
|
328
|
+
this.submitAnswerTimeout = setTimeout(() => {
|
|
329
|
+
this.userSummary.invalidate(); // verified, it's necessary
|
|
330
|
+
}, 1000);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
try {
|
|
334
|
+
await actions.submitAnswer(this.transport, { questionId, answerId });
|
|
335
|
+
updateQuestionAndFieldList();
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
if (error.message === ERROR.ALREADY_VOTED) {
|
|
339
|
+
this.userSummary.invalidate();
|
|
340
|
+
const cancel = data.subscribe(() => { });
|
|
341
|
+
data.invalidate();
|
|
342
|
+
cancel();
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
177
346
|
};
|
|
178
347
|
openQuestion = (questionId, question) => {
|
|
179
348
|
this.notifications.close(this.background.getCurrentSessionId({
|
|
180
349
|
prefix: 'notification',
|
|
181
350
|
entity: questionId,
|
|
182
351
|
}));
|
|
352
|
+
let questionType = question?.attributes?.type;
|
|
353
|
+
if (!questionType) {
|
|
354
|
+
const feedList = this.feedList.getStore().value?.data || [];
|
|
355
|
+
questionType = feedList.find((item) => item.id === questionId)?.attributes?.type;
|
|
356
|
+
}
|
|
357
|
+
eventBus.emit('poll', {
|
|
358
|
+
action: 'opened',
|
|
359
|
+
payload: {
|
|
360
|
+
questionId,
|
|
361
|
+
questionType,
|
|
362
|
+
questionOpenedFrom: question?.openedFrom,
|
|
363
|
+
},
|
|
364
|
+
});
|
|
183
365
|
return this.background.openQuestion(questionId, question);
|
|
184
366
|
};
|
|
185
367
|
closeQuestion = (questionId) => {
|
|
@@ -217,7 +399,9 @@ export class Gamification extends AbstractFeature {
|
|
|
217
399
|
prefix: 'notification',
|
|
218
400
|
entity: question.data.question.id,
|
|
219
401
|
}),
|
|
402
|
+
emitEvent: true,
|
|
220
403
|
data: {
|
|
404
|
+
questionId: question.data.question.id,
|
|
221
405
|
questionType: question.data.question.type,
|
|
222
406
|
question: {
|
|
223
407
|
title: question.data.question.notification.title,
|
|
@@ -231,7 +415,7 @@ export class Gamification extends AbstractFeature {
|
|
|
231
415
|
const instantView = {
|
|
232
416
|
heading: question.data.question.notification.title,
|
|
233
417
|
body: question.data.question.notification.body,
|
|
234
|
-
imageMode: optionsValue
|
|
418
|
+
imageMode: optionsValue?.imageMode,
|
|
235
419
|
image: optionsValue?.image,
|
|
236
420
|
video: {
|
|
237
421
|
id: optionsValue?.video?.id || '',
|
|
@@ -248,8 +432,10 @@ export class Gamification extends AbstractFeature {
|
|
|
248
432
|
action: () => question.data?.question && this.openQuestion(question.data.question.id, question.data.feedItem),
|
|
249
433
|
close: () => question.data?.question && this.closeQuestion(question.data.question.id),
|
|
250
434
|
autoHideDuration: 1000 * 120,
|
|
435
|
+
emitEvent: true,
|
|
251
436
|
id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
|
|
252
437
|
data: {
|
|
438
|
+
questionId: question.data.question.id,
|
|
253
439
|
questionType: question.data.question.type,
|
|
254
440
|
insight: instantView,
|
|
255
441
|
},
|
|
@@ -271,8 +457,10 @@ export class Gamification extends AbstractFeature {
|
|
|
271
457
|
action: () => question.data?.question && this.openQuestion(question.data.question.id, question.data.feedItem),
|
|
272
458
|
close: () => question.data?.question && this.closeQuestion(question.data.question.id),
|
|
273
459
|
autoHideDuration: 1000 * 120,
|
|
460
|
+
emitEvent: true,
|
|
274
461
|
id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
|
|
275
462
|
data: {
|
|
463
|
+
questionId: question.data.question.id,
|
|
276
464
|
questionType: question.data.question.type,
|
|
277
465
|
tweet: tweetView,
|
|
278
466
|
},
|
package/lib/leaderboard.js
CHANGED
|
@@ -34,19 +34,17 @@ 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
40
|
data: newData.data.map((item) => item.attributes),
|
|
42
|
-
hasMore:
|
|
41
|
+
hasMore: false,
|
|
43
42
|
key: Date.now(),
|
|
44
43
|
loading: false,
|
|
45
44
|
});
|
|
46
45
|
if (newData.meta) {
|
|
47
46
|
maxPage = Math.round(newData.meta.count / newData.meta.pageSize);
|
|
48
47
|
}
|
|
49
|
-
$pagination.set(request.pagination);
|
|
50
48
|
}
|
|
51
49
|
};
|
|
52
50
|
const invalidate = () => {
|
|
@@ -65,33 +63,32 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
|
|
|
65
63
|
onMount($store, () => {
|
|
66
64
|
const cancelRefetchListener = $eventId.listen(refetch);
|
|
67
65
|
const cancelRefetchByFriendsListener = $friends.listen(refetch);
|
|
68
|
-
const cancelPaginationListener = $pagination.listen(async (pagination) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
66
|
+
// const cancelPaginationListener = $pagination.listen(async (pagination) => {
|
|
67
|
+
// const eventId = $eventId.get()
|
|
68
|
+
// if (pagination.page > 0 && eventId) {
|
|
69
|
+
// if (pagination.page < maxPage) {
|
|
70
|
+
// $store.setKey('loading', true)
|
|
71
|
+
// const request = {
|
|
72
|
+
// eventId: eventId as unknown as bigint,
|
|
73
|
+
// pagination,
|
|
74
|
+
// }
|
|
75
|
+
// const newData = await fetch(request)
|
|
76
|
+
// const prevData = $store.get().data || []
|
|
77
|
+
// $store.set({
|
|
78
|
+
// data: [...prevData, ...newData.data.map((item) => item.attributes as LeaderboardItem)],
|
|
79
|
+
// key: $store.get().key,
|
|
80
|
+
// loading: false,
|
|
81
|
+
// hasMore: true,
|
|
82
|
+
// })
|
|
83
|
+
// } else {
|
|
84
|
+
// $store.setKey('hasMore', false)
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
// })
|
|
91
88
|
return () => {
|
|
92
89
|
cancelRefetchListener();
|
|
93
90
|
cancelRefetchByFriendsListener();
|
|
94
|
-
cancelPaginationListener()
|
|
91
|
+
// cancelPaginationListener()
|
|
95
92
|
};
|
|
96
93
|
});
|
|
97
94
|
return { $store, fetchMore, invalidate };
|
package/lib/onboarding.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSingleStore } from '@streamlayer/sdk-web-interfaces';
|
|
1
|
+
import { createSingleStore, eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
2
|
import { NotificationType } from '@streamlayer/sdk-web-notifications';
|
|
3
3
|
import { QuestionType } from '@streamlayer/sdk-web-types';
|
|
4
4
|
import { GamificationStorage } from './storage';
|
|
@@ -31,6 +31,7 @@ const showOnboardingInApp = (service, background, notifications, storage) => {
|
|
|
31
31
|
persistent: true,
|
|
32
32
|
autoHideDuration: 1000000,
|
|
33
33
|
data: {
|
|
34
|
+
questionId: 'onboarding',
|
|
34
35
|
questionType: QuestionType.UNSET,
|
|
35
36
|
onboarding: {
|
|
36
37
|
...inplayGame,
|
|
@@ -112,7 +113,7 @@ export const onboarding = (service, background, transport, notifications) => {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
if (onboardingStatus === OnboardingStatus.Completed) {
|
|
115
|
-
background.activeQuestionId.invalidate();
|
|
116
|
+
background.activeQuestionId.invalidate(); // verified, it's necessary
|
|
116
117
|
}
|
|
117
118
|
storage.setOnboardingInstantOpen({
|
|
118
119
|
userId: background.userId.get() || '',
|
|
@@ -149,6 +150,10 @@ export const onboarding = (service, background, transport, notifications) => {
|
|
|
149
150
|
userId: background.userId.get() || '',
|
|
150
151
|
eventId,
|
|
151
152
|
}, OnboardingStatus.Completed);
|
|
153
|
+
eventBus.emit('poll', {
|
|
154
|
+
action: 'onboardingPassed',
|
|
155
|
+
payload: {},
|
|
156
|
+
});
|
|
152
157
|
const notificationId = background.getCurrentSessionId({ prefix: 'onboarding' });
|
|
153
158
|
notifications.close(notificationId);
|
|
154
159
|
}
|
package/lib/queries/actions.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import { ConnectError, Code } from '@connectrpc/connect';
|
|
1
2
|
import { Feed } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_connect';
|
|
2
|
-
|
|
3
|
+
import { ERROR } from '../constants';
|
|
4
|
+
export const submitAnswer = async (transport, data) => {
|
|
3
5
|
const { client, createRequestOptions } = transport.createPromiseClient(Feed, { method: 'submitAnswer' });
|
|
4
6
|
const contextValues = createRequestOptions({ retryAttempts: 0 });
|
|
5
|
-
|
|
7
|
+
try {
|
|
8
|
+
return await client.submitAnswer({ data }, { contextValues });
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
const cErr = ConnectError.from(error);
|
|
12
|
+
if (cErr?.code === Code.AlreadyExists) {
|
|
13
|
+
throw new Error(ERROR.ALREADY_VOTED);
|
|
14
|
+
}
|
|
15
|
+
throw new Error(ERROR.UNKNOWN);
|
|
16
|
+
}
|
|
6
17
|
};
|
|
7
18
|
export const submitInplay = (transport, eventId) => {
|
|
8
19
|
const { client, createRequestOptions } = transport.createPromiseClient(Feed, { method: 'submitInplay' });
|
package/lib/queries/deepLink.js
CHANGED
package/lib/queries/index.d.ts
CHANGED
|
@@ -329,7 +329,7 @@ export declare const questionSubscription: (questionId: string, transport: Trans
|
|
|
329
329
|
}, QuestionSubscriptionRequest, QuestionSubscriptionResponse, "subscription" | "votingSubscription" | "questionSubscription" | "feedSubscription", ((request: import("@bufbuild/protobuf").PartialMessage<SubscriptionRequest>, options?: import("@connectrpc/connect").CallOptions | undefined) => AsyncIterable<SubscriptionResponse>) | ((request: import("@bufbuild/protobuf").PartialMessage<VotingSubscriptionRequest>, options?: import("@connectrpc/connect").CallOptions | undefined) => AsyncIterable<VotingSubscriptionResponse>) | ((request: import("@bufbuild/protobuf").PartialMessage<QuestionSubscriptionRequest>, options?: import("@connectrpc/connect").CallOptions | undefined) => AsyncIterable<QuestionSubscriptionResponse>) | ((request: import("@bufbuild/protobuf").PartialMessage<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedSubscriptionRequest>, options?: import("@connectrpc/connect").CallOptions | undefined) => AsyncIterable<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedSubscriptionResponse>)>;
|
|
330
330
|
export declare const getQuestionByUser: (questionId: string, transport: Transport) => Promise<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion | undefined>;
|
|
331
331
|
export declare const getQuestionDetail: (questionId: string, transport: Transport) => Promise<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Question | undefined>;
|
|
332
|
-
export declare const $questionByUser: ($questionId: ReadableAtom<string | undefined
|
|
332
|
+
export declare const $questionByUser: ($questionId: ReadableAtom<string | undefined> | string, transport: Transport) => import("@nanostores/query").FetcherStore<import("@bufbuild/protobuf").PlainMessage<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").ExtendedQuestion>, any>;
|
|
333
333
|
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>;
|
|
334
334
|
export declare const $feedList: ($slStreamId: ReadableAtom<string | undefined>, $interactiveAllowed: ReadableAtom<InteractiveAllowed>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem[], any>;
|
|
335
335
|
export { $userSummary, $leaderboardList } from './leaderboard';
|
package/lib/queries/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export const $activeQuestion = (slStreamId, transport) => {
|
|
|
16
16
|
});
|
|
17
17
|
return res.data?.attributes;
|
|
18
18
|
},
|
|
19
|
+
dedupeTime: 1000 * 60 * 10, // 10 minutes
|
|
20
|
+
refetchInterval: 0,
|
|
19
21
|
});
|
|
20
22
|
};
|
|
21
23
|
export const feedSubscription = ($slStreamId, transport) => {
|
|
@@ -60,6 +62,7 @@ export const $questionByUser = ($questionId, transport) => {
|
|
|
60
62
|
});
|
|
61
63
|
return res.data?.attributes?.question;
|
|
62
64
|
},
|
|
65
|
+
dedupeTime: 1000 * 60 * 5,
|
|
63
66
|
});
|
|
64
67
|
};
|
|
65
68
|
export const $pickHistory = (slStreamId, transport) => {
|
|
@@ -97,6 +100,8 @@ export const $feedList = ($slStreamId, $interactiveAllowed, transport) => {
|
|
|
97
100
|
});
|
|
98
101
|
return res.data;
|
|
99
102
|
},
|
|
103
|
+
dedupeTime: 0,
|
|
104
|
+
refetchInterval: 0,
|
|
100
105
|
});
|
|
101
106
|
};
|
|
102
107
|
export { $userSummary, $leaderboardList } from './leaderboard';
|
|
@@ -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,8 @@
|
|
|
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 | undefined>;
|
|
7
|
+
invalidate: () => void;
|
|
8
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
const request = {
|
|
17
|
+
eventId: eventId,
|
|
18
|
+
userId: userId,
|
|
19
|
+
usersIds: [...usersIds, userId],
|
|
20
|
+
};
|
|
21
|
+
const res = await fetch(request);
|
|
22
|
+
$store.set(res.data?.attributes);
|
|
23
|
+
};
|
|
24
|
+
const invalidate = () => {
|
|
25
|
+
void refetch();
|
|
26
|
+
};
|
|
27
|
+
onMount($store, () => {
|
|
28
|
+
const cancelRefetchListener = $eventId.listen(refetch);
|
|
29
|
+
const cancelRefetchByFriendsListener = $friends.listen(refetch);
|
|
30
|
+
const cancelRefetchByUserListener = $userId.listen(refetch);
|
|
31
|
+
return () => {
|
|
32
|
+
cancelRefetchListener();
|
|
33
|
+
cancelRefetchByFriendsListener();
|
|
34
|
+
cancelRefetchByUserListener();
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
return { $store, invalidate };
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamlayer/feature-gamification",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.1",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@bufbuild/protobuf": "^1.7.2",
|
|
6
|
-
"@fastify/deepmerge": "
|
|
7
|
-
"@streamlayer/sl-eslib": "^5.
|
|
8
|
-
"nanostores": "^0.
|
|
9
|
-
"@streamlayer/sdk-web-api": "^0.
|
|
10
|
-
"@streamlayer/sdk-web-core": "^0.
|
|
11
|
-
"@streamlayer/sdk-web-interfaces": "^0.
|
|
12
|
-
"@streamlayer/sdk-web-logger": "^0.5.
|
|
13
|
-
"@streamlayer/sdk-web-notifications": "^0.
|
|
14
|
-
"@streamlayer/sdk-web-storage": "^0.4.
|
|
15
|
-
"@streamlayer/sdk-web-types": "^0.
|
|
6
|
+
"@fastify/deepmerge": "^1.3.0",
|
|
7
|
+
"@streamlayer/sl-eslib": "^5.83.1",
|
|
8
|
+
"nanostores": "^0.10.0",
|
|
9
|
+
"@streamlayer/sdk-web-api": "^0.24.1",
|
|
10
|
+
"@streamlayer/sdk-web-core": "^0.22.1",
|
|
11
|
+
"@streamlayer/sdk-web-interfaces": "^0.21.0",
|
|
12
|
+
"@streamlayer/sdk-web-logger": "^0.5.18",
|
|
13
|
+
"@streamlayer/sdk-web-notifications": "^0.15.0",
|
|
14
|
+
"@streamlayer/sdk-web-storage": "^0.4.5",
|
|
15
|
+
"@streamlayer/sdk-web-types": "^0.23.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"tslib": "^2.6.2"
|