@streamlayer/feature-gamification 1.16.9 → 1.16.11

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.js CHANGED
@@ -4,7 +4,6 @@ import { QuestionStatus, QuestionType } from '@streamlayer/sdk-web-types';
4
4
  import '@streamlayer/sdk-web-core/store';
5
5
  import * as queries from './queries';
6
6
  import { detail } from './detail';
7
- import { advertisement } from './advertisement';
8
7
  import { GamificationStorage } from './storage';
9
8
  export var InteractiveAllowed;
10
9
  (function (InteractiveAllowed) {
@@ -61,39 +60,56 @@ export class GamificationBackground {
61
60
  this.notifications = instance.notifications;
62
61
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
63
62
  const onlyBetPack = !!instance.sdk.options.get().betPack;
64
- this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, onlyBetPack, instance.transport), 'gamification:feedList');
63
+ this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, this.userId, this.organizationId, this.storage, onlyBetPack, instance.transport), 'gamification:feedList');
65
64
  this.betPack = new ApiStore(queries.$betPack(this.slStreamId, this.userId, this.organizationId, this.storage, instance.transport), 'gamification:betPack');
66
65
  this.activeQuestionId = queries.$activeQuestion(this.slStreamId, onlyBetPack, instance.transport);
67
66
  this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
68
67
  this.cancels.add(this.openedQuestionId.listen((item) => {
69
68
  this.log.debug({ item }, 'received question');
70
69
  if (item?.questionId) {
70
+ this.log.info('cleanup on close question');
71
+ if (this.questionSubscription !== undefined) {
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ instance.transport.removeSubscription(this.questionSubscription);
74
+ this.questionSubscription = undefined;
75
+ }
71
76
  this.questionSubscription = queries.questionSubscription(item.questionId, instance.transport);
72
- this.questionSubscription.addListener('feed-subscription-opened-question', async (response) => {
77
+ this.questionSubscription.addListener('feed-subscription-opened-question', (response) => {
73
78
  const question = response.data?.attributes?.question;
74
79
  this.openedQuestion.updateExtendedQuestion(question);
75
80
  if (question?.type === QuestionType.PREDICTION &&
76
81
  (question.status === QuestionStatus.ACTIVE || question.status === QuestionStatus.RESOLVED)) {
77
82
  const betPackData = this.betPack.getValues().data?.data || {};
78
83
  const betPackItem = betPackData?.[question.id];
79
- if (betPackItem || Object.keys(betPackData).length < 5) {
80
- const data = queries.$questionByUser(question.id, this.transport);
81
- const cancel = data.subscribe(() => { });
82
- await data.get().promise;
83
- // if question is not in the feed list, get extended question data from the server
84
- let extendedQuestion = data.get().data;
85
- if (!extendedQuestion) {
86
- extendedQuestion = await queries.questionByUser(question.id, this.transport);
87
- }
88
- cancel();
89
- window.requestAnimationFrame(() => {
90
- data.invalidate();
84
+ if (betPackItem) {
85
+ const answers = [];
86
+ const sortedPrev = betPackItem.answers.sort((a, b) => {
87
+ return a.id > b.id ? 1 : -1;
91
88
  });
89
+ const sortedNew = question.answers.sort((a, b) => {
90
+ return a.id > b.id ? 1 : -1;
91
+ });
92
+ for (let i = 0; i < sortedPrev.length; i++) {
93
+ answers.push({
94
+ ...sortedPrev[i],
95
+ ...sortedNew[i],
96
+ correct: sortedPrev[i].correct,
97
+ youVoted: sortedPrev[i].youVoted,
98
+ pointsEarned: sortedPrev[i].pointsEarned,
99
+ });
100
+ }
92
101
  this.betPack.getStore().mutate({
93
102
  timestamp: Date.now(),
94
103
  data: {
95
104
  ...betPackData,
96
- [question.id]: extendedQuestion,
105
+ [question.id]: {
106
+ ...betPackItem,
107
+ status: question.status,
108
+ marketClosed: question.marketClosed,
109
+ activatedAt: question.activatedAt,
110
+ answerSetAt: question.answerSetAt,
111
+ answers,
112
+ },
97
113
  },
98
114
  });
99
115
  }
@@ -117,21 +133,23 @@ export class GamificationBackground {
117
133
  (question.status === QuestionStatus.ACTIVE || question.status === QuestionStatus.RESOLVED)) {
118
134
  const betPackData = this.betPack.getValues().data?.data || {};
119
135
  const betPackItem = betPackData?.[question.id];
120
- if (betPackItem || Object.keys(betPackData).length < 5) {
121
- const data = queries.$questionByUser(question.id, this.transport);
122
- // order of operations is important here
123
- const cancel = data.subscribe(() => { });
124
- await data.get().promise;
125
- // if question is not in the feed list, get extended question data from the server
126
- let extendedQuestion = data.get().data;
127
- if (!extendedQuestion) {
128
- extendedQuestion = await queries.questionByUser(question.id, this.transport);
129
- }
130
- cancel();
131
- window.requestAnimationFrame(() => {
132
- data.invalidate();
136
+ if (betPackItem) {
137
+ this.betPack.getStore().mutate({
138
+ timestamp: Date.now(),
139
+ data: {
140
+ ...betPackData,
141
+ [question.id]: {
142
+ ...betPackItem,
143
+ status: question.status,
144
+ marketClosed: question.marketClosed,
145
+ activatedAt: question.activatedAt,
146
+ answerSetAt: question.answerSetAt,
147
+ },
148
+ },
133
149
  });
134
- // get extended question data and mark as dirty
150
+ }
151
+ else if (!betPackItem && Object.keys(betPackData).length < 5) {
152
+ const extendedQuestion = await queries.questionByUser(question.id, this.transport);
135
153
  this.betPack.getStore().mutate({
136
154
  timestamp: Date.now(),
137
155
  data: {
@@ -215,7 +233,7 @@ export class GamificationBackground {
215
233
  };
216
234
  });
217
235
  if (!onlyBetPack) {
218
- this.advertisement = advertisement(this.slStreamId, this.feedSubscription, instance);
236
+ // this.advertisement = advertisement(this.slStreamId, this.feedSubscription, instance)
219
237
  }
220
238
  }
221
239
  /**
package/lib/detail.js CHANGED
@@ -22,16 +22,24 @@ export const detail = (transport, $openedQuestionId, $feedList) => {
22
22
  const currentQuestion = $extendedStore.get().data;
23
23
  const mergeQuestionAnswers = (currentAnswers, newAnswers) => {
24
24
  if (!currentAnswers || !newAnswers) {
25
- return currentAnswers || newAnswers || [];
25
+ return (currentAnswers || newAnswers || []).sort((a, b) => {
26
+ return a.id > b.id ? 1 : -1;
27
+ });
26
28
  }
29
+ const sortedPrev = currentAnswers.sort((a, b) => {
30
+ return a.id > b.id ? 1 : -1;
31
+ });
32
+ const sortedNew = newAnswers.sort((a, b) => {
33
+ return a.id > b.id ? 1 : -1;
34
+ });
27
35
  const answers = [];
28
- for (let i = 0; i < currentAnswers.length; i++) {
36
+ for (let i = 0; i < sortedPrev.length; i++) {
29
37
  answers.push({
30
- ...currentAnswers[i],
31
- ...newAnswers[i],
32
- correct: currentAnswers[i].correct,
33
- youVoted: currentAnswers[i].youVoted,
34
- pointsEarned: currentAnswers[i].pointsEarned,
38
+ ...sortedPrev[i],
39
+ ...sortedNew[i],
40
+ correct: sortedPrev[i].correct,
41
+ youVoted: sortedPrev[i].youVoted,
42
+ pointsEarned: sortedPrev[i].pointsEarned,
35
43
  });
36
44
  }
37
45
  return answers;
@@ -190,6 +190,7 @@ export class Gamification extends AbstractFeature {
190
190
  let extendedQuestion = data.get().data;
191
191
  if (!extendedQuestion) {
192
192
  extendedQuestion = await questionByUser(id, this.transport);
193
+ data.mutate(extendedQuestion);
193
194
  }
194
195
  cancel();
195
196
  window.requestAnimationFrame(() => {
@@ -301,7 +302,12 @@ export class Gamification extends AbstractFeature {
301
302
  status: prev.attributes.attributes.value.status,
302
303
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
303
304
  // @ts-ignore
304
- openForVoting: prev.attributes.attributes.value.openForVoting,
305
+ openForVoting:
306
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
307
+ // @ts-ignore
308
+ prev.attributes.attributes.value.openForVoting === false
309
+ ? false
310
+ : feedItem.attributes.attributes.value.openForVoting,
305
311
  },
306
312
  },
307
313
  },
@@ -21,7 +21,7 @@ export const $deepLink = (transport, params) => {
21
21
  }
22
22
  },
23
23
  dedupeTime: 1000 * 60 * 60 * 24, // 24 hours
24
- refetchInterval: 0,
24
+ refetchInterval: Infinity,
25
25
  });
26
26
  };
27
27
  export const generateShortLink = (transport, { web, mobile }) => {
@@ -400,7 +400,7 @@ export declare const getPromotionDetail: (promoId: string, transport: Transport)
400
400
  notification: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionNotification | undefined;
401
401
  } | undefined>;
402
402
  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>;
403
- export declare const $feedList: ($slStreamId: ReadableAtom<string | undefined>, $interactiveAllowed: ReadableAtom<InteractiveAllowed>, onlyBetPack: boolean, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem[], any>;
403
+ 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<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem[], any>;
404
404
  export declare const $activePromotionId: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<{
405
405
  id: string;
406
406
  question: {
@@ -24,7 +24,7 @@ export const $activeQuestion = (slStreamId, onlyBetPack, transport) => {
24
24
  return res.data?.attributes;
25
25
  },
26
26
  dedupeTime: 1000 * 60 * 10, // 10 minutes
27
- refetchInterval: 0,
27
+ refetchInterval: Infinity,
28
28
  });
29
29
  };
30
30
  export const getFeedItem = async (questionId, transport) => {
@@ -67,17 +67,29 @@ export const questionByUser = async ($questionId, transport) => {
67
67
  });
68
68
  return res.data?.attributes?.question;
69
69
  };
70
+ const storeCache = new Map();
70
71
  export const $questionByUser = ($questionId, transport) => {
71
- const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'questionByUser', params: [$questionId] });
72
- return transport.nanoquery.createFetcherStore(queryKey, {
73
- fetcher: async (_, __, questionId) => {
74
- const res = await client.questionByUser({
75
- questionId: questionId,
76
- });
77
- return res.data?.attributes?.question;
78
- },
79
- dedupeTime: 1000 * 60 * 5,
72
+ const { client, queryKey, queryKeyStr } = transport.createPromiseClient(Feed, {
73
+ method: 'questionByUser',
74
+ params: [$questionId],
80
75
  });
76
+ if (storeCache.has(queryKeyStr)) {
77
+ return storeCache.get(queryKeyStr);
78
+ }
79
+ else {
80
+ const store = transport.nanoquery.createFetcherStore(queryKey, {
81
+ fetcher: async (_, __, questionId) => {
82
+ const res = await client.questionByUser({
83
+ questionId: questionId,
84
+ });
85
+ return res.data?.attributes?.question;
86
+ },
87
+ dedupeTime: Infinity,
88
+ refetchInterval: Infinity,
89
+ });
90
+ storeCache.set(queryKeyStr, store);
91
+ return store;
92
+ }
81
93
  };
82
94
  export const getPromotionDetail = async (promoId, transport) => {
83
95
  if (!promoId) {
@@ -108,7 +120,7 @@ export const $pickHistory = (slStreamId, transport) => {
108
120
  },
109
121
  });
110
122
  };
111
- export const $feedList = ($slStreamId, $interactiveAllowed, onlyBetPack, transport) => {
123
+ export const $feedList = ($slStreamId, $interactiveAllowed, $slUserId, $slOrganizationId, storage, onlyBetPack, transport) => {
112
124
  const { client, queryKey } = transport.createPromiseClient(Feed, {
113
125
  method: 'list',
114
126
  params: [$slStreamId, $interactiveAllowed, onlyBetPack ? 'true' : ''],
@@ -127,10 +139,27 @@ export const $feedList = ($slStreamId, $interactiveAllowed, onlyBetPack, transpo
127
139
  statuses: [QuestionStatus.ACTIVE, QuestionStatus.RESOLVED],
128
140
  },
129
141
  });
142
+ const flags = {
143
+ eventId: slStreamId,
144
+ userId: $slUserId.get(),
145
+ organizationId: $slOrganizationId.get(),
146
+ };
147
+ for (const question of res.data) {
148
+ if (!storage.isQuestionReceived(flags, question.id)) {
149
+ eventBus.emit('poll', {
150
+ action: 'received',
151
+ payload: {
152
+ questionId: question.id,
153
+ questionType: question.attributes?.type,
154
+ },
155
+ });
156
+ storage.saveQuestionReceived(flags, question.id);
157
+ }
158
+ }
130
159
  return res.data;
131
160
  },
132
- dedupeTime: 0,
133
- refetchInterval: 0,
161
+ dedupeTime: Infinity,
162
+ refetchInterval: Infinity,
134
163
  });
135
164
  };
136
165
  export const $activePromotionId = ($slStreamId, transport) => {
@@ -160,8 +189,8 @@ export const $activePromotionId = ($slStreamId, transport) => {
160
189
  }
161
190
  return undefined;
162
191
  },
163
- dedupeTime: 0,
164
- refetchInterval: 0,
192
+ dedupeTime: Infinity,
193
+ refetchInterval: Infinity,
165
194
  });
166
195
  };
167
196
  export { $userSummary, $leaderboardList } from './leaderboard';
@@ -208,7 +237,7 @@ export const $betPack = ($slStreamId, $slUserId, $slOrganizationId, storage, tra
208
237
  userId: userId,
209
238
  organizationId: organizationId,
210
239
  };
211
- if (!storage.isBetPackQuestionReceived(flags, question.id)) {
240
+ if (!storage.isQuestionReceived(flags, question.id)) {
212
241
  eventBus.emit('poll', {
213
242
  action: 'received',
214
243
  payload: {
@@ -216,13 +245,16 @@ export const $betPack = ($slStreamId, $slUserId, $slOrganizationId, storage, tra
216
245
  questionType: question.type,
217
246
  },
218
247
  });
219
- storage.saveBetPackQuestionReceived(flags, question.id);
248
+ storage.saveQuestionReceived(flags, question.id);
220
249
  }
250
+ question.answers = question.answers.sort((a, b) => {
251
+ return a.id > b.id ? 1 : -1;
252
+ });
221
253
  return { ...acc, [question.id]: question };
222
254
  }, {}),
223
255
  };
224
256
  },
225
- dedupeTime: 0,
226
- refetchInterval: 0,
257
+ dedupeTime: Infinity,
258
+ refetchInterval: Infinity,
227
259
  });
228
260
  };
@@ -8,7 +8,7 @@ export const $moderation = (slStreamId, transport) => {
8
8
  });
9
9
  return res.data?.attributes;
10
10
  },
11
- refetchInterval: 0,
11
+ refetchInterval: Infinity,
12
12
  dedupeTime: 1000 * 60 * 60,
13
13
  });
14
14
  };
package/lib/storage.d.ts CHANGED
@@ -8,8 +8,8 @@ type UserProps = {
8
8
  export declare class GamificationStorage extends Storage {
9
9
  private sessionStorage;
10
10
  constructor();
11
- saveBetPackQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => void;
12
- isBetPackQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => boolean;
11
+ saveQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => void;
12
+ isQuestionReceived: ({ userId, eventId, organizationId }: UserProps, questionId: string) => boolean;
13
13
  saveQuestionOpened: ({ userId, eventId, organizationId }: UserProps, questionId: string) => void;
14
14
  isQuestionOpened: ({ userId, eventId, organizationId }: UserProps, questionId: string) => boolean;
15
15
  saveOnboardingStatus: ({ userId, eventId, organizationId }: UserProps, status: OnboardingStatus) => void;
package/lib/storage.js CHANGED
@@ -3,7 +3,7 @@ var KEY_PREFIX;
3
3
  (function (KEY_PREFIX) {
4
4
  KEY_PREFIX["ONBOARDING"] = "onboarding";
5
5
  KEY_PREFIX["ONBOARDING_IO"] = "onboarding_io";
6
- KEY_PREFIX["BET_PACK_ITEM_RECEIVED"] = "bp-item-received";
6
+ KEY_PREFIX["QUESTION_RECEIVED"] = "q-received";
7
7
  KEY_PREFIX["QUESTION_OPENED"] = "q-opened";
8
8
  })(KEY_PREFIX || (KEY_PREFIX = {}));
9
9
  class GamificationSessionStorage extends Storage {
@@ -23,11 +23,11 @@ export class GamificationStorage extends Storage {
23
23
  super('gamification');
24
24
  this.sessionStorage = new GamificationSessionStorage();
25
25
  }
26
- saveBetPackQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
27
- this.write(KEY_PREFIX.BET_PACK_ITEM_RECEIVED, organizationId, userId, eventId, questionId, '1');
26
+ saveQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
27
+ this.write(KEY_PREFIX.QUESTION_RECEIVED, organizationId, userId, eventId, questionId, '1');
28
28
  };
29
- isBetPackQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
30
- return !!this.read(KEY_PREFIX.BET_PACK_ITEM_RECEIVED, organizationId, userId, eventId, questionId);
29
+ isQuestionReceived = ({ userId, eventId, organizationId }, questionId) => {
30
+ return !!this.read(KEY_PREFIX.QUESTION_RECEIVED, organizationId, userId, eventId, questionId);
31
31
  };
32
32
  saveQuestionOpened = ({ userId, eventId, organizationId }, questionId) => {
33
33
  this.write(KEY_PREFIX.QUESTION_OPENED, organizationId, userId, eventId, questionId, '1');
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "1.16.9",
3
+ "version": "1.16.11",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^1.10.0",
6
6
  "@fastify/deepmerge": "^2.0.0",
7
7
  "@streamlayer/sl-eslib": "^5.149.1",
8
8
  "uuid": "^11.1.0",
9
9
  "nanostores": "^0.11.4",
10
- "@streamlayer/sdk-web-api": "^1.8.9",
11
- "@streamlayer/sdk-web-core": "^1.11.11",
12
- "@streamlayer/sdk-web-interfaces": "^1.5.0",
13
- "@streamlayer/sdk-web-logger": "^1.0.56",
14
- "@streamlayer/sdk-web-notifications": "^1.3.18",
15
- "@streamlayer/sdk-web-storage": "^1.0.56",
16
- "@streamlayer/sdk-web-types": "^1.10.13"
10
+ "@streamlayer/sdk-web-api": "^1.8.11",
11
+ "@streamlayer/sdk-web-core": "^1.11.13",
12
+ "@streamlayer/sdk-web-interfaces": "^1.5.2",
13
+ "@streamlayer/sdk-web-logger": "^1.0.58",
14
+ "@streamlayer/sdk-web-notifications": "^1.3.20",
15
+ "@streamlayer/sdk-web-storage": "^1.0.58",
16
+ "@streamlayer/sdk-web-types": "^1.10.15"
17
17
  },
18
18
  "devDependencies": {
19
19
  "tslib": "^2.7.0"