@streamlayer/feature-gamification 0.34.0 → 0.35.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.
@@ -4,6 +4,10 @@ import '@streamlayer/sdk-web-core/store';
4
4
  import { ReadableAtom, WritableAtom } from 'nanostores';
5
5
  import * as queries from './queries';
6
6
  import { detail } from './detail';
7
+ export declare enum InteractiveAllowed {
8
+ ALLOWED = "allowed",
9
+ DISALLOWED = "disallowed"
10
+ }
7
11
  /**
8
12
  * Background class for Gamification feature
9
13
  */
@@ -14,6 +18,11 @@ export declare class GamificationBackground {
14
18
  organizationId: ReadableAtom<string | undefined>;
15
19
  /** current user id */
16
20
  userId: ReadableAtom<string | undefined>;
21
+ /**
22
+ * flag to allow interactive questions (polls, trivia, predictions)
23
+ * controlled by opt-in and onboarding flags
24
+ */
25
+ interactiveAllowed: WritableAtom<InteractiveAllowed>;
17
26
  /** opened question, using to download statistics */
18
27
  openedQuestionId: WritableAtom<string | undefined>;
19
28
  /** opened question statistics */
package/lib/background.js CHANGED
@@ -3,6 +3,11 @@ import { createLogger } from '@streamlayer/sdk-web-logger';
3
3
  import '@streamlayer/sdk-web-core/store';
4
4
  import * as queries from './queries';
5
5
  import { detail } from './detail';
6
+ export var InteractiveAllowed;
7
+ (function (InteractiveAllowed) {
8
+ InteractiveAllowed["ALLOWED"] = "allowed";
9
+ InteractiveAllowed["DISALLOWED"] = "disallowed";
10
+ })(InteractiveAllowed || (InteractiveAllowed = {}));
6
11
  /**
7
12
  * Background class for Gamification feature
8
13
  */
@@ -13,6 +18,11 @@ export class GamificationBackground {
13
18
  organizationId;
14
19
  /** current user id */
15
20
  userId;
21
+ /**
22
+ * flag to allow interactive questions (polls, trivia, predictions)
23
+ * controlled by opt-in and onboarding flags
24
+ */
25
+ interactiveAllowed;
16
26
  /** opened question, using to download statistics */
17
27
  openedQuestionId;
18
28
  /** opened question statistics */
@@ -36,10 +46,11 @@ export class GamificationBackground {
36
46
  this.organizationId = instance.stores.organizationSettings.getAtomStore();
37
47
  this.userId = instance.stores.user.getAtomStore();
38
48
  this.moderationId = new SingleStore(createSingleStore(undefined), 'moderationId').getStore();
49
+ this.interactiveAllowed = new SingleStore(createSingleStore(InteractiveAllowed.DISALLOWED), 'interactiveAllowed').getStore();
39
50
  this.openedQuestionId = new SingleStore(createSingleStore(undefined), 'openedQuestionId').getStore();
40
51
  this.notifications = instance.notifications;
41
52
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
42
- this.feedList = new ApiStore(queries.$feedList(this.slStreamId, instance.transport), 'gamification:feedList');
53
+ this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, instance.transport), 'gamification:feedList');
43
54
  this.activeQuestionId = new ApiStore(queries.$activeQuestion(this.slStreamId, instance.transport), 'gamification:activeQuestionId');
44
55
  this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
45
56
  this.openedQuestionId.listen((questionId) => {
@@ -82,6 +93,16 @@ export class GamificationBackground {
82
93
  });
83
94
  });
84
95
  this.feedSubscription.connect();
96
+ /**
97
+ * invalidate active question on interactiveAllowed change
98
+ * close question if interactiveAllowed changed to disallowed
99
+ * open question if interactiveAllowed changed to allowed
100
+ */
101
+ this.interactiveAllowed.listen(() => {
102
+ window.requestAnimationFrame(() => {
103
+ this.activeQuestionId.invalidate();
104
+ });
105
+ });
85
106
  }
86
107
  /**
87
108
  * Get id for notifications and link with current session
@@ -9,7 +9,7 @@ import { leaderboard } from './leaderboard';
9
9
  import { deepLink } from './deepLink';
10
10
  import { OnboardingStatus } from './onboarding';
11
11
  import { LeaderboardItem } from './queries/leaderboard';
12
- import { GamificationBackground } from './';
12
+ import { GamificationBackground } from './background';
13
13
  /**
14
14
  * Gamification (Games) Overlay
15
15
  * Includes:
@@ -24,6 +24,8 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
24
24
  userSummary: ApiStore<GetApiResponseType<typeof queries.$userSummary>>;
25
25
  /** feed list (pick history) */
26
26
  feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
27
+ /** friends list */
28
+ friends: ApiStore<GetApiResponseType<typeof queries.$friends>>;
27
29
  /** pinned leaderboard id */
28
30
  leaderboardId: WritableAtom<string | undefined>;
29
31
  /** leaderboard list */
@@ -49,6 +51,8 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
49
51
  /** Browser cache */
50
52
  private storage;
51
53
  constructor(config: FeatureProps, source: FeatureSource, instance: StreamLayerContext);
54
+ get isInteractiveAllowed(): boolean;
55
+ checkInteractiveFlag: () => void;
52
56
  connect: (transport: StreamLayerContext['transport']) => void;
53
57
  disconnect: () => void;
54
58
  submitAnswer: (questionId: string, answerId: string) => Promise<void>;
@@ -57,4 +61,11 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
57
61
  closeQuestion: (questionId?: string) => void;
58
62
  openUser: (userId: string) => void;
59
63
  closeUser: () => void;
64
+ /**
65
+ * Show in-app notification for active question
66
+ * for interactive questions we show notification only if interactiveAllowed
67
+ * for factoid and tweet questions we show notification always
68
+ * skipping questions with inAppSilence === ON
69
+ */
70
+ private showInApp;
60
71
  }
@@ -8,8 +8,8 @@ import { GamificationStorage } from './storage';
8
8
  import { leaderboard } from './leaderboard';
9
9
  import { deepLink } from './deepLink';
10
10
  import { OnboardingStatus, onboarding } from './onboarding';
11
- import { GamificationBackground } from './';
12
- const GamificationQuestionTypes = new Set([QuestionType.POLL, QuestionType.PREDICTION, QuestionType.TRIVIA]);
11
+ import { GamificationBackground, InteractiveAllowed } from './background';
12
+ const InteractiveQuestionTypes = new Set([QuestionType.POLL, QuestionType.PREDICTION, QuestionType.TRIVIA]);
13
13
  /**
14
14
  * Gamification (Games) Overlay
15
15
  * Includes:
@@ -24,6 +24,8 @@ export class Gamification extends AbstractFeature {
24
24
  userSummary;
25
25
  /** feed list (pick history) */
26
26
  feedList;
27
+ /** friends list */
28
+ friends;
27
29
  /** pinned leaderboard id */
28
30
  leaderboardId;
29
31
  /** leaderboard list */
@@ -51,6 +53,7 @@ export class Gamification extends AbstractFeature {
51
53
  this.storage = new GamificationStorage();
52
54
  this.userSummary = new ApiStore(queries.$userSummary(this.background.slStreamId, this.background.userId, instance.transport), 'gamification:userSummary');
53
55
  this.feedList = this.background.feedList;
56
+ this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
54
57
  this.currentUserId = this.background.userId;
55
58
  this.openedUser = createSingleStore(undefined);
56
59
  this.leaderboardId = new SingleStore(createSingleStore(this.settings.getValue('pinnedLeaderboardId')), 'pinnedLeaderboardId').getStore();
@@ -61,7 +64,7 @@ export class Gamification extends AbstractFeature {
61
64
  this.openFeature = () => instance.sdk.openFeature(FeatureType.GAMES);
62
65
  this.openedQuestion = this.background.openedQuestion;
63
66
  this.deepLink = deepLink(this.transport, this.background.slStreamId, this.background.userId);
64
- this.leaderboardList = leaderboard(this.transport, this.background.slStreamId);
67
+ this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends);
65
68
  this.status.subscribe((status) => {
66
69
  if (status === FeatureStatus.Ready) {
67
70
  this.connect(instance.transport);
@@ -70,97 +73,45 @@ export class Gamification extends AbstractFeature {
70
73
  this.disconnect();
71
74
  }
72
75
  });
76
+ // refresh leaderboard on user summary update after earning points
77
+ this.userSummary.listen((userSummary) => {
78
+ if (this.leaderboardList.$store.lc !== 0 && userSummary?.data?.summary) {
79
+ window.requestAnimationFrame(() => {
80
+ this.leaderboardList.invalidate();
81
+ });
82
+ }
83
+ });
73
84
  /**
74
85
  * listen for active question and show in-app notification
75
86
  */
76
- this.background.activeQuestionId.listen((question) => {
77
- const onboardingStatus = this.onboardingStatus.$store.get();
78
- if (question && question.data && onboardingStatus && onboardingStatus !== OnboardingStatus.Unset) {
79
- if (question.data.question?.id !== undefined &&
80
- question.data.question.notification !== undefined &&
81
- question.data.question?.bypassNotifications?.inAppSilence !== SilenceSetting.ON &&
82
- question.data.question.status === QuestionStatus.ACTIVE) {
83
- if (GamificationQuestionTypes.has(question.data.question.type)) {
84
- const onboardingEnabled = this.background.moderation.getStore().value?.data?.options?.onboardingEnabled;
85
- const optInEnabled = this.settings.getValues().inplayGame?.titleCard?.optIn;
86
- const onboardingCompleted = onboardingStatus === OnboardingStatus.Completed;
87
- if (!onboardingEnabled || onboardingCompleted || optInEnabled !== true) {
88
- this.notifications.add({
89
- type: NotificationType.QUESTION,
90
- action: () => question.data?.question && this.openQuestion(question.data.question.id),
91
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
92
- autoHideDuration: 1000 * 60,
93
- id: this.background.getCurrentSessionId({
94
- prefix: 'notification',
95
- entity: question.data.question.id,
96
- }),
97
- data: {
98
- questionType: question.data.question.type,
99
- question: {
100
- title: question.data.question.notification.title,
101
- },
102
- },
103
- });
104
- }
105
- }
106
- else if (question.data.question.type === QuestionType.FACTOID) {
107
- const optionsValue = question.data.question.options?.options.value;
108
- const instantView = {
109
- heading: question.data.question.notification.title,
110
- body: question.data.question.notification.body,
111
- image: question.data.question.notification.image,
112
- video: {
113
- id: optionsValue?.video?.id || '',
114
- url: optionsValue?.video?.url || '',
115
- thumbnailUrl: optionsValue?.video?.thumbnailUrl || '',
116
- },
117
- webLink: {
118
- label: '',
119
- url: '',
120
- },
121
- };
122
- this.notifications.add({
123
- type: NotificationType.QUESTION,
124
- action: () => question.data?.question && this.openQuestion(question.data.question.id),
125
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
126
- autoHideDuration: 1000 * 120,
127
- id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
128
- data: {
129
- questionType: question.data.question.type,
130
- insight: instantView,
131
- },
132
- });
133
- }
134
- else if (question.data.question.type === QuestionType.TWEET) {
135
- const optionsValue = question.data.question.options?.options.value;
136
- const tweetView = {
137
- title: question.data.question.notification.title,
138
- body: question.data.question.notification.body,
139
- image: question.data.question.notification.image,
140
- account: optionsValue?.tweetMeta?.account || '',
141
- accountVerified: !!optionsValue?.tweetMeta?.accountVerified,
142
- };
143
- this.notifications.add({
144
- type: NotificationType.QUESTION,
145
- action: () => question.data?.question && this.openQuestion(question.data.question.id),
146
- close: () => question.data?.question && this.closeQuestion(question.data.question.id),
147
- autoHideDuration: 1000 * 120,
148
- id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
149
- data: {
150
- questionType: question.data.question.type,
151
- tweet: tweetView,
152
- },
153
- });
154
- }
155
- }
156
- }
157
- });
87
+ this.background.activeQuestionId.listen(this.showInApp);
88
+ /**
89
+ * listen for onboarding status, moderation onboarding changes and opt-in settings
90
+ */
91
+ this.onboardingStatus.$store.listen(this.checkInteractiveFlag);
92
+ this.background.moderation.getStore().listen(this.checkInteractiveFlag);
93
+ this.settings.subscribe(this.checkInteractiveFlag);
158
94
  }
95
+ get isInteractiveAllowed() {
96
+ return this.background.interactiveAllowed.get() === InteractiveAllowed.ALLOWED;
97
+ }
98
+ checkInteractiveFlag = () => {
99
+ const onboardingStatus = this.onboardingStatus.$store.get();
100
+ const onboardingEnabled = this.background.moderation.getStore().value?.data?.options?.onboardingEnabled;
101
+ const optInEnabled = this.settings.getValues().inplayGame?.titleCard?.optIn;
102
+ const onboardingCompleted = onboardingStatus === OnboardingStatus.Completed;
103
+ const allowed = !onboardingEnabled || onboardingCompleted || optInEnabled !== true;
104
+ this.background.interactiveAllowed.set(allowed ? InteractiveAllowed.ALLOWED : InteractiveAllowed.DISALLOWED);
105
+ };
159
106
  connect = (transport) => {
160
107
  this.userSummary.invalidate();
161
108
  this.leaderboardList.invalidate();
162
109
  this.feedList.invalidate();
110
+ this.friends.invalidate();
163
111
  this.background.feedSubscription.addListener('feed-subscription-prediction-close', (response) => {
112
+ if (!this.isInteractiveAllowed) {
113
+ return;
114
+ }
164
115
  window.requestAnimationFrame(async () => {
165
116
  const question = response.data?.attributes?.question;
166
117
  if (!question) {
@@ -241,4 +192,89 @@ export class Gamification extends AbstractFeature {
241
192
  closeUser = () => {
242
193
  this.openedUser.set(undefined);
243
194
  };
195
+ /**
196
+ * Show in-app notification for active question
197
+ * for interactive questions we show notification only if interactiveAllowed
198
+ * for factoid and tweet questions we show notification always
199
+ * skipping questions with inAppSilence === ON
200
+ */
201
+ showInApp = (question) => {
202
+ const onboardingStatus = this.onboardingStatus.$store.get();
203
+ if (question && question.data && onboardingStatus && onboardingStatus !== OnboardingStatus.Unset) {
204
+ if (question.data.question?.id !== undefined &&
205
+ question.data.question.notification !== undefined &&
206
+ question.data.question?.bypassNotifications?.inAppSilence !== SilenceSetting.ON &&
207
+ question.data.question.status === QuestionStatus.ACTIVE) {
208
+ if (InteractiveQuestionTypes.has(question.data.question.type)) {
209
+ if (this.isInteractiveAllowed) {
210
+ this.notifications.add({
211
+ type: NotificationType.QUESTION,
212
+ action: () => question.data?.question && this.openQuestion(question.data.question.id),
213
+ close: () => question.data?.question && this.closeQuestion(question.data.question.id),
214
+ autoHideDuration: 1000 * 60,
215
+ id: this.background.getCurrentSessionId({
216
+ prefix: 'notification',
217
+ entity: question.data.question.id,
218
+ }),
219
+ data: {
220
+ questionType: question.data.question.type,
221
+ question: {
222
+ title: question.data.question.notification.title,
223
+ },
224
+ },
225
+ });
226
+ }
227
+ }
228
+ else if (question.data.question.type === QuestionType.FACTOID) {
229
+ const optionsValue = question.data.question.options?.options.value;
230
+ const instantView = {
231
+ heading: question.data.question.notification.title,
232
+ body: question.data.question.notification.body,
233
+ image: optionsValue.image,
234
+ video: {
235
+ id: optionsValue?.video?.id || '',
236
+ url: optionsValue?.video?.url || '',
237
+ thumbnailUrl: optionsValue?.video?.thumbnailUrl || '',
238
+ },
239
+ webLink: {
240
+ label: '',
241
+ url: '',
242
+ },
243
+ };
244
+ this.notifications.add({
245
+ type: NotificationType.QUESTION,
246
+ action: () => question.data?.question && this.openQuestion(question.data.question.id),
247
+ close: () => question.data?.question && this.closeQuestion(question.data.question.id),
248
+ autoHideDuration: 1000 * 120,
249
+ id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
250
+ data: {
251
+ questionType: question.data.question.type,
252
+ insight: instantView,
253
+ },
254
+ });
255
+ }
256
+ else if (question.data.question.type === QuestionType.TWEET) {
257
+ const optionsValue = question.data.question.options?.options.value;
258
+ const tweetView = {
259
+ title: question.data.question.notification.title,
260
+ body: question.data.question.notification.body,
261
+ image: question.data.question.notification.image,
262
+ account: optionsValue?.tweetMeta?.account || '',
263
+ accountVerified: !!optionsValue?.tweetMeta?.accountVerified,
264
+ };
265
+ this.notifications.add({
266
+ type: NotificationType.QUESTION,
267
+ action: () => question.data?.question && this.openQuestion(question.data.question.id),
268
+ close: () => question.data?.question && this.closeQuestion(question.data.question.id),
269
+ autoHideDuration: 1000 * 120,
270
+ id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
271
+ data: {
272
+ questionType: question.data.question.type,
273
+ tweet: tweetView,
274
+ },
275
+ });
276
+ }
277
+ }
278
+ }
279
+ };
244
280
  }
@@ -1,16 +1,18 @@
1
1
  import type { Transport } from '@streamlayer/sdk-web-api';
2
2
  import { ReadableAtom } from 'nanostores';
3
3
  import { type LeaderboardItem } from './queries/leaderboard';
4
+ import { Gamification } from './';
4
5
  type LeaderboardOptions = {
5
6
  pageSize?: number;
6
7
  };
7
8
  type LeaderboardStore = {
8
- data?: LeaderboardItem[];
9
+ data: LeaderboardItem[];
9
10
  loading?: boolean;
11
+ key: number;
10
12
  hasMore: boolean;
11
13
  error?: string;
12
14
  };
13
- export declare const leaderboard: (transport: Transport, $eventId: ReadableAtom<string | undefined>, options?: LeaderboardOptions) => {
15
+ export declare const leaderboard: (transport: Transport, $eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification['friends'], options?: LeaderboardOptions) => {
14
16
  $store: import("nanostores").MapStore<LeaderboardStore>;
15
17
  fetchMore: (page?: number) => void;
16
18
  invalidate: () => void;
@@ -4,25 +4,43 @@ import { createLeaderboardListFetch } from './queries/leaderboard';
4
4
  const defaultOptions = {
5
5
  pageSize: 10,
6
6
  };
7
- export const leaderboard = (transport, $eventId, options) => {
7
+ export const leaderboard = (transport, $eventId, $userId, $friends, options) => {
8
8
  let maxPage = 0;
9
9
  const $pagination = createSingleStore({ pageSize: options?.pageSize || defaultOptions.pageSize, page: 0 });
10
- const $store = createMapStore({ data: [], hasMore: true, loading: undefined, error: undefined });
10
+ const $store = createMapStore({
11
+ data: [],
12
+ key: Date.now(),
13
+ hasMore: true,
14
+ loading: undefined,
15
+ error: undefined,
16
+ });
11
17
  const fetch = createLeaderboardListFetch(transport);
12
18
  /**
13
19
  * Refetch leaderboard data, reset pagination
14
20
  */
15
- const refetch = async (eventId) => {
16
- if (eventId) {
21
+ const refetch = async () => {
22
+ const eventId = $eventId.get();
23
+ const friendsIds = $friends
24
+ .getStore()
25
+ .get()
26
+ .data?.map((friend) => friend.slId) || [];
27
+ if (eventId && friendsIds?.length) {
28
+ const userId = $userId.get();
29
+ if (userId) {
30
+ friendsIds.push(userId);
31
+ }
17
32
  $store.setKey('loading', true);
33
+ $store.setKey('data', []);
18
34
  const request = {
19
35
  eventId: eventId,
36
+ usersIds: friendsIds,
20
37
  pagination: { page: 0, pageSize: options?.pageSize || defaultOptions.pageSize },
21
38
  };
22
39
  const newData = await fetch(request);
23
40
  $store.set({
24
41
  data: newData.data.map((item) => item.attributes),
25
42
  hasMore: true,
43
+ key: Date.now(),
26
44
  loading: false,
27
45
  });
28
46
  if (newData.meta) {
@@ -32,7 +50,7 @@ export const leaderboard = (transport, $eventId, options) => {
32
50
  }
33
51
  };
34
52
  const invalidate = () => {
35
- void refetch($eventId.get());
53
+ void refetch();
36
54
  };
37
55
  /**
38
56
  * Whether there is more data to fetch
@@ -46,6 +64,7 @@ export const leaderboard = (transport, $eventId, options) => {
46
64
  };
47
65
  onMount($store, () => {
48
66
  const cancelRefetchListener = $eventId.listen(refetch);
67
+ const cancelRefetchByFriendsListener = $friends.listen(refetch);
49
68
  const cancelPaginationListener = $pagination.listen(async (pagination) => {
50
69
  const eventId = $eventId.get();
51
70
  if (pagination.page > 0 && eventId) {
@@ -59,6 +78,7 @@ export const leaderboard = (transport, $eventId, options) => {
59
78
  const prevData = $store.get().data || [];
60
79
  $store.set({
61
80
  data: [...prevData, ...newData.data.map((item) => item.attributes)],
81
+ key: $store.get().key,
62
82
  loading: false,
63
83
  hasMore: true,
64
84
  });
@@ -70,6 +90,7 @@ export const leaderboard = (transport, $eventId, options) => {
70
90
  });
71
91
  return () => {
72
92
  cancelRefetchListener();
93
+ cancelRefetchByFriendsListener();
73
94
  cancelPaginationListener();
74
95
  };
75
96
  });
package/lib/onboarding.js CHANGED
@@ -28,9 +28,6 @@ const showOnboardingInApp = (service, background, notifications, storage) => {
28
28
  type: NotificationType.ONBOARDING,
29
29
  id: notificationId,
30
30
  action: service.openFeature,
31
- close: () => {
32
- notifications.markAsViewed(notificationId);
33
- },
34
31
  persistent: true,
35
32
  autoHideDuration: 1000000,
36
33
  data: {
@@ -100,6 +97,8 @@ const onboardingProcess = ($store, background, service, notifications, storage,
100
97
  }
101
98
  };
102
99
  export const onboarding = (service, background, transport, notifications) => {
100
+ // show onboarding each time when user open new sdk session
101
+ let onboardingShowed = false;
103
102
  const storage = new GamificationStorage();
104
103
  const $store = createSingleStore(OnboardingStatus.Unset);
105
104
  $store.subscribe((onboardingStatus) => {
@@ -107,7 +106,10 @@ export const onboarding = (service, background, transport, notifications) => {
107
106
  return;
108
107
  }
109
108
  if (onboardingStatus === OnboardingStatus.Optional || onboardingStatus === OnboardingStatus.Required) {
110
- showOnboardingInApp(service, background, notifications, storage);
109
+ if (onboardingShowed === false) {
110
+ showOnboardingInApp(service, background, notifications, storage);
111
+ onboardingShowed = true;
112
+ }
111
113
  }
112
114
  if (onboardingStatus === OnboardingStatus.Completed) {
113
115
  background.activeQuestionId.invalidate();
@@ -0,0 +1,3 @@
1
+ import type { Transport } from '@streamlayer/sdk-web-api';
2
+ import { ReadableAtom } from 'nanostores';
3
+ export declare const $friends: ($userId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/users/users_pb").GetFriendsResponse_FriendData[], any>;
@@ -0,0 +1,16 @@
1
+ import { Users } from '@streamlayer/sl-eslib/users/users_connect';
2
+ export const $friends = ($userId, transport) => {
3
+ const { client, queryKey } = transport.createPromiseClient(Users, {
4
+ method: 'getFriends',
5
+ params: [$userId],
6
+ });
7
+ return transport.nanoquery.createFetcherStore(queryKey, {
8
+ fetcher: async (_, __, userId) => {
9
+ if (!userId) {
10
+ return [];
11
+ }
12
+ const res = await client.getFriends({});
13
+ return res.data;
14
+ },
15
+ });
16
+ };
@@ -1,6 +1,7 @@
1
1
  import type { Transport } from '@streamlayer/sdk-web-api';
2
2
  import { ReadableAtom } from 'nanostores';
3
3
  import type { SubscriptionRequest, SubscriptionResponse, VotingSubscriptionRequest, VotingSubscriptionResponse, QuestionSubscriptionRequest, QuestionSubscriptionResponse } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb';
4
+ import { InteractiveAllowed } from '../background';
4
5
  export declare const $activeQuestion: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedQuestion | undefined, any>;
5
6
  export declare const feedSubscription: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("packages/sdk-web-api/lib/grpc/subscription").ServerStreamSubscription<import("@bufbuild/protobuf").ServiceType, import("@bufbuild/protobuf").Message<import("@bufbuild/protobuf").AnyMessage>, import("@bufbuild/protobuf").Message<import("@bufbuild/protobuf").AnyMessage>, never, never> | import("packages/sdk-web-api/lib/grpc/subscription").ServerStreamSubscription<{
6
7
  readonly typeName: "streamlayer.interactive.feed.Feed";
@@ -330,7 +331,7 @@ export declare const getQuestionByUser: (questionId: string, transport: Transpor
330
331
  export declare const getQuestionDetail: (questionId: string, transport: Transport) => Promise<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Question | undefined>;
331
332
  export declare const $questionByUser: ($questionId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sdk-web-types").ExtendedQuestion | undefined, any>;
332
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>;
333
- export declare const $feedList: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem[], any>;
334
- export declare const $insightHistory: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<(import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").InsightHistory | undefined)[] | undefined, any>;
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';
336
+ export { $friends } from './friends';
336
337
  export { $moderation } from './moderation';
@@ -1,6 +1,7 @@
1
- import { QuestionStatus } from '@streamlayer/sdk-web-types';
1
+ import { QuestionStatus, QuestionType } from '@streamlayer/sdk-web-types';
2
2
  import { atom } from 'nanostores';
3
3
  import { Feed } from '@streamlayer/sl-eslib/interactive/feed/interactive.feed_connect';
4
+ import { InteractiveAllowed } from '../background';
4
5
  export const $activeQuestion = (slStreamId, transport) => {
5
6
  const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'syncQuestion', params: [slStreamId] });
6
7
  return transport.nanoquery.createFetcherStore(queryKey, {
@@ -75,16 +76,22 @@ export const $pickHistory = (slStreamId, transport) => {
75
76
  },
76
77
  });
77
78
  };
78
- export const $feedList = (slStreamId, transport) => {
79
- const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'list', params: [slStreamId] });
79
+ export const $feedList = ($slStreamId, $interactiveAllowed, transport) => {
80
+ const { client, queryKey } = transport.createPromiseClient(Feed, {
81
+ method: 'list',
82
+ params: [$slStreamId, $interactiveAllowed],
83
+ });
80
84
  return transport.nanoquery.createFetcherStore(queryKey, {
81
- fetcher: async (_, __, eventId) => {
82
- if (!eventId) {
85
+ fetcher: async (_, __, slStreamId, interactiveAllowed) => {
86
+ if (!slStreamId) {
83
87
  return [];
84
88
  }
85
89
  const res = await client.list({
86
- eventId: eventId,
90
+ eventId: slStreamId,
87
91
  filter: {
92
+ types: interactiveAllowed === InteractiveAllowed.ALLOWED
93
+ ? [] // allow all types
94
+ : [QuestionType.FACTOID, QuestionType.PROMOTION, QuestionType.TWEET],
88
95
  statuses: [QuestionStatus.ACTIVE, QuestionStatus.RESOLVED],
89
96
  },
90
97
  });
@@ -92,19 +99,6 @@ export const $feedList = (slStreamId, transport) => {
92
99
  },
93
100
  });
94
101
  };
95
- export const $insightHistory = (slStreamId, transport) => {
96
- const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'insightHistory', params: [slStreamId] });
97
- return transport.nanoquery.createFetcherStore(queryKey, {
98
- fetcher: async (_, __, eventId) => {
99
- if (!eventId) {
100
- return undefined;
101
- }
102
- const res = await client.insightHistory({
103
- eventId: eventId,
104
- });
105
- return res.data?.map(({ attributes }) => attributes);
106
- },
107
- });
108
- };
109
102
  export { $userSummary, $leaderboardList } from './leaderboard';
103
+ export { $friends } from './friends';
110
104
  export { $moderation } from './moderation';
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^1.6.0",
6
6
  "@fastify/deepmerge": "*",
7
7
  "@streamlayer/sl-eslib": "^5.63.3",
8
8
  "nanostores": "^0.9.5",
9
- "@streamlayer/sdk-web-api": "^0.18.0",
10
- "@streamlayer/sdk-web-core": "^0.17.8",
11
- "@streamlayer/sdk-web-interfaces": "^0.18.21",
12
- "@streamlayer/sdk-web-logger": "^0.5.10",
13
- "@streamlayer/sdk-web-notifications": "^0.13.4",
14
- "@streamlayer/sdk-web-storage": "^0.3.10",
15
- "@streamlayer/sdk-web-types": "^0.20.1"
9
+ "@streamlayer/sdk-web-api": "^0.20.0",
10
+ "@streamlayer/sdk-web-core": "^0.18.1",
11
+ "@streamlayer/sdk-web-interfaces": "^0.20.0",
12
+ "@streamlayer/sdk-web-logger": "^0.5.11",
13
+ "@streamlayer/sdk-web-notifications": "^0.13.6",
14
+ "@streamlayer/sdk-web-storage": "^0.3.11",
15
+ "@streamlayer/sdk-web-types": "^0.21.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "tslib": "^2.6.2"