@streamlayer/feature-gamification 1.5.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/advertisement/index.d.ts +10 -21
- package/lib/advertisement/index.js +85 -138
- package/lib/advertisement/storage.d.ts +1 -1
- package/lib/advertisement/storage.js +1 -1
- package/lib/advertisement/utils.d.ts +31 -0
- package/lib/advertisement/utils.js +21 -0
- package/lib/background.js +24 -2
- package/lib/gamification.d.ts +1 -1
- package/lib/gamification.js +1 -1
- package/lib/queries/index.d.ts +56 -7
- package/lib/queries/index.js +17 -17
- package/package.json +9 -9
- package/lib/advertisement/queue.d.ts +0 -25
- package/lib/advertisement/queue.js +0 -142
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PromotionOptions } from '@streamlayer/sdk-web-types';
|
|
3
|
-
import { QuestionNotification } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
|
|
1
|
+
import { StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
|
|
4
2
|
import { type GamificationBackground } from '../background';
|
|
5
3
|
import { getPromotionDetail } from '../queries';
|
|
6
|
-
type AdvertisementData =
|
|
7
|
-
loading?: boolean;
|
|
8
|
-
error?: unknown;
|
|
9
|
-
data?: {
|
|
10
|
-
promotions: PromotionOptions;
|
|
11
|
-
notification?: QuestionNotification;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
4
|
+
type AdvertisementData = Exclude<Awaited<ReturnType<typeof getPromotionDetail>>, undefined>;
|
|
14
5
|
export type Advertisement = {
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
data?: AdvertisementData;
|
|
7
|
+
loading?: boolean;
|
|
17
8
|
hiding?: boolean;
|
|
18
|
-
|
|
19
|
-
close?: (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
id: string;
|
|
9
|
+
isViewed?: boolean;
|
|
10
|
+
close?: () => void;
|
|
11
|
+
error?: string;
|
|
12
|
+
ctx?: Record<string, unknown>;
|
|
23
13
|
};
|
|
24
14
|
/**
|
|
25
15
|
* @name Advertisement
|
|
@@ -34,9 +24,8 @@ export type Advertisement = {
|
|
|
34
24
|
* - we subscribe to $feedSubscription, and show advertisement on activate
|
|
35
25
|
*/
|
|
36
26
|
export declare const advertisement: ($slStreamId: GamificationBackground["slStreamId"], $feedSubscription: GamificationBackground["feedSubscription"], instance: StreamLayerContext) => {
|
|
37
|
-
hide: (notificationId
|
|
27
|
+
hide: (notificationId?: string) => void;
|
|
38
28
|
show: (advertisementId: string, data?: Awaited<ReturnType<typeof getPromotionDetail>>) => void;
|
|
39
|
-
$
|
|
40
|
-
getActiveAdvertisement: (persistent?: boolean) => Advertisement | null;
|
|
29
|
+
$store: import("nanostores").MapStore<Advertisement>;
|
|
41
30
|
};
|
|
42
31
|
export {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createMapStore, eventBus } from '@streamlayer/sdk-web-interfaces';
|
|
2
2
|
import { QuestionStatus } from '@streamlayer/sdk-web-types';
|
|
3
3
|
import { createLogger } from '@streamlayer/sdk-web-logger';
|
|
4
|
-
import { onMount } from 'nanostores';
|
|
5
4
|
import { NotificationEnabled } from '@streamlayer/sl-eslib/interactive/interactive.common_pb';
|
|
6
|
-
import { $
|
|
5
|
+
import { $activePromotionId, getPromotionDetail } from '../queries';
|
|
7
6
|
import { AdvertisementStorage } from './storage';
|
|
8
|
-
import {
|
|
7
|
+
import { parsePromotion } from './utils';
|
|
9
8
|
/**
|
|
10
9
|
* @name Advertisement
|
|
11
10
|
* @description advertisement functionality, show, hide
|
|
@@ -20,159 +19,107 @@ import { AdvertisementsQueue } from './queue';
|
|
|
20
19
|
*/
|
|
21
20
|
export const advertisement = ($slStreamId, $feedSubscription, instance) => {
|
|
22
21
|
const transport = instance.transport;
|
|
23
|
-
const logger = createLogger('
|
|
24
|
-
const queue = new AdvertisementsQueue({ concurrency: 1, animationDelay: 1000 });
|
|
22
|
+
const logger = createLogger('advertisement');
|
|
25
23
|
const storage = new AdvertisementStorage();
|
|
26
|
-
const $
|
|
24
|
+
const $store = createMapStore({});
|
|
25
|
+
const $activeAdvertisement = $activePromotionId($slStreamId, transport);
|
|
27
26
|
/**
|
|
28
|
-
* Show advertisement by id
|
|
27
|
+
* Show advertisement by id
|
|
29
28
|
*/
|
|
30
29
|
const show = (advertisementId, data) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else {
|
|
57
|
-
this.data.setKey('data', response);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
this.data.setKey('error', error);
|
|
62
|
-
}
|
|
63
|
-
finally {
|
|
64
|
-
this.data.setKey('loading', false);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
});
|
|
30
|
+
if (!data) {
|
|
31
|
+
$store.setKey('loading', true);
|
|
32
|
+
void getPromotionDetail(advertisementId, transport)
|
|
33
|
+
.then((response) => $store.set({
|
|
34
|
+
loading: false,
|
|
35
|
+
error: undefined,
|
|
36
|
+
data: response,
|
|
37
|
+
close: () => hide(response?.question.id),
|
|
38
|
+
isViewed: response && !!storage.isViewed(response.question.id),
|
|
39
|
+
}))
|
|
40
|
+
.catch((error) => $store.set({
|
|
41
|
+
loading: false,
|
|
42
|
+
error,
|
|
43
|
+
data: undefined,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
$store.set({
|
|
48
|
+
loading: false,
|
|
49
|
+
error: undefined,
|
|
50
|
+
data,
|
|
51
|
+
close: () => hide(data.question.id),
|
|
52
|
+
isViewed: !!storage.isViewed(data.question.id),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
68
55
|
};
|
|
56
|
+
$activeAdvertisement.subscribe((active, prevActive) => {
|
|
57
|
+
if (active.data) {
|
|
58
|
+
eventBus.emit('advertisement', {
|
|
59
|
+
action: 'received',
|
|
60
|
+
payload: {
|
|
61
|
+
advertisementId: active.data.question.id,
|
|
62
|
+
advertisementType: active.data?.promotion.type,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (!prevActive?.data || active.data.id !== prevActive.data.id) {
|
|
66
|
+
show(active.data.question.id, active.data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
$store.subscribe((active, prevActive) => {
|
|
71
|
+
if (active.data) {
|
|
72
|
+
instance.sdk.onAdvertisementActivate({
|
|
73
|
+
stage: 'activate',
|
|
74
|
+
id: active.data.question.id,
|
|
75
|
+
hasNotification: active.data.notification?.enabled === NotificationEnabled.NOTIFICATION_ENABLED,
|
|
76
|
+
isViewed: !!storage.isViewed(active.data.question.id),
|
|
77
|
+
});
|
|
78
|
+
eventBus.emit('advertisement', {
|
|
79
|
+
action: 'opened',
|
|
80
|
+
payload: {
|
|
81
|
+
advertisementId: active.data.question.id,
|
|
82
|
+
advertisementType: active.data?.promotion.type,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (!active?.data && prevActive?.data) {
|
|
87
|
+
instance.sdk.onAdvertisementActivate({
|
|
88
|
+
stage: 'deactivate',
|
|
89
|
+
id: prevActive.data.question.id,
|
|
90
|
+
isViewed: !!storage.isViewed(prevActive.data.question.id),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
69
94
|
const markAsViewed = (notificationId) => {
|
|
70
95
|
logger.debug({ notificationId }, 'markAsViewed: %o');
|
|
71
96
|
storage.setShowed(notificationId);
|
|
72
97
|
};
|
|
73
98
|
const hide = (notificationId) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
instance.sdk.onAdvertisementActivate({
|
|
77
|
-
stage: 'deactivate',
|
|
78
|
-
id: notificationId,
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
const getActiveAdvertisement = (persistent) => {
|
|
82
|
-
const advertisements = queue.advertisementList.get();
|
|
83
|
-
if (!advertisements?.size) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const advertisement = advertisements.values().next().value;
|
|
87
|
-
const advertisementData = advertisement.data.get();
|
|
88
|
-
if (!persistent && storage.isShowed(advertisement.id)) {
|
|
89
|
-
queue.removeFromList(advertisement.id);
|
|
90
|
-
return getActiveAdvertisement(persistent);
|
|
99
|
+
if (!notificationId || $store.get()?.data?.question.id === notificationId) {
|
|
100
|
+
$store.set({});
|
|
91
101
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
102
|
+
if (notificationId) {
|
|
103
|
+
markAsViewed(notificationId);
|
|
94
104
|
}
|
|
95
|
-
eventBus.emit('advertisement', {
|
|
96
|
-
action: 'opened',
|
|
97
|
-
payload: {
|
|
98
|
-
advertisementId: advertisement.id,
|
|
99
|
-
advertisementType: advertisementData.data?.promotions.type,
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
return advertisement;
|
|
103
105
|
};
|
|
104
|
-
$
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (last) {
|
|
108
|
-
show(last.id);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
$feedSubscription.addListener('promotion cb', (response) => {
|
|
113
|
-
const feedItem = response.data?.attributes?.feedItem?.attributes?.attributes?.case === 'promotion'
|
|
114
|
-
? response.data.attributes.feedItem.attributes
|
|
115
|
-
: undefined;
|
|
116
|
-
const promotionItem = response.data?.attributes?.question?.options?.options.case === 'promotion'
|
|
117
|
-
? response.data.attributes.question.options.options.value
|
|
118
|
-
: undefined;
|
|
119
|
-
if (feedItem === undefined || promotionItem === undefined) {
|
|
120
|
-
logger.debug('not promotion');
|
|
106
|
+
$feedSubscription.addListener('promotion', (response) => {
|
|
107
|
+
const promotion = parsePromotion(response);
|
|
108
|
+
if (!promotion) {
|
|
121
109
|
return;
|
|
122
110
|
}
|
|
123
|
-
if (
|
|
124
|
-
hide(
|
|
125
|
-
logger.debug({
|
|
111
|
+
if (promotion.question.status === QuestionStatus.RESOLVED) {
|
|
112
|
+
hide(promotion.question.id);
|
|
113
|
+
logger.debug({ promotion }, 'resolved: %o');
|
|
126
114
|
return;
|
|
127
115
|
}
|
|
128
|
-
if (
|
|
129
|
-
logger.debug({
|
|
130
|
-
show(
|
|
116
|
+
if (promotion.question.status === QuestionStatus.ACTIVE) {
|
|
117
|
+
logger.debug({ promotion }, 'active: %o');
|
|
118
|
+
show(promotion.question.id, promotion);
|
|
131
119
|
return;
|
|
132
120
|
}
|
|
133
|
-
logger.debug({
|
|
134
|
-
return;
|
|
135
|
-
});
|
|
136
|
-
onMount(queue.advertisementList, () => {
|
|
137
|
-
$advertisementList.subscribe((list) => {
|
|
138
|
-
if (list.data) {
|
|
139
|
-
const last = list.data[list.data.length - 1];
|
|
140
|
-
if (last) {
|
|
141
|
-
show(last.id);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
$feedSubscription.addListener('promotion', (response) => {
|
|
146
|
-
const feedItem = response.data?.attributes?.feedItem?.attributes?.attributes?.case === 'promotion'
|
|
147
|
-
? response.data.attributes.feedItem.attributes
|
|
148
|
-
: undefined;
|
|
149
|
-
const promotionItem = response.data?.attributes?.question?.options?.options.case === 'promotion'
|
|
150
|
-
? response.data.attributes.question.options.options.value
|
|
151
|
-
: undefined;
|
|
152
|
-
if (feedItem === undefined || promotionItem === undefined) {
|
|
153
|
-
logger.debug('not promotion');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (feedItem.status === QuestionStatus.RESOLVED) {
|
|
157
|
-
hide(feedItem.id);
|
|
158
|
-
logger.debug({ feedItem }, 'resolved: %o');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (feedItem.status === QuestionStatus.ACTIVE) {
|
|
162
|
-
logger.debug({ feedItem }, 'active: %o');
|
|
163
|
-
show(feedItem.id, {
|
|
164
|
-
promotions: promotionItem,
|
|
165
|
-
notification: response.data?.attributes?.question?.notification,
|
|
166
|
-
});
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
logger.debug({ feedItem }, 'skip: %o');
|
|
170
|
-
return;
|
|
171
|
-
});
|
|
121
|
+
logger.debug({ promotion }, 'skip: %o');
|
|
172
122
|
return;
|
|
173
|
-
// return () => {
|
|
174
|
-
// queue.drain()
|
|
175
|
-
// }
|
|
176
123
|
});
|
|
177
|
-
return { hide, show, $
|
|
124
|
+
return { hide, show, $store };
|
|
178
125
|
};
|
|
@@ -2,5 +2,5 @@ import { Storage } from '@streamlayer/sdk-web-storage';
|
|
|
2
2
|
export declare class AdvertisementStorage extends Storage {
|
|
3
3
|
constructor();
|
|
4
4
|
setShowed: (advertId: string) => void;
|
|
5
|
-
|
|
5
|
+
isViewed: (advertId: string) => string | undefined;
|
|
6
6
|
}
|
|
@@ -10,7 +10,7 @@ export class AdvertisementStorage extends Storage {
|
|
|
10
10
|
setShowed = (advertId) => {
|
|
11
11
|
this.write(KEY_PREFIX.SHOWED, advertId, 'true');
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
isViewed = (advertId) => {
|
|
14
14
|
return this.read(KEY_PREFIX.SHOWED, advertId);
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SubscriptionResponse } from '@streamlayer/sdk-web-types';
|
|
2
|
+
export declare const parsePromotion: (response: SubscriptionResponse) => {
|
|
3
|
+
id: string;
|
|
4
|
+
question: {
|
|
5
|
+
id: string;
|
|
6
|
+
type: import("@streamlayer/sdk-web-types").QuestionType;
|
|
7
|
+
subject?: string;
|
|
8
|
+
appearance?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAppearance;
|
|
9
|
+
sponsorship?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Sponsorship;
|
|
10
|
+
answers: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAnswer[];
|
|
11
|
+
status: import("@streamlayer/sdk-web-types").QuestionStatus;
|
|
12
|
+
position?: number;
|
|
13
|
+
marketClosed: boolean;
|
|
14
|
+
activatedAt: string;
|
|
15
|
+
answerSetAt: string;
|
|
16
|
+
overrides: {
|
|
17
|
+
[key: string]: boolean;
|
|
18
|
+
};
|
|
19
|
+
eventId: string;
|
|
20
|
+
streamTimestamp?: import("@bufbuild/protobuf").Timestamp;
|
|
21
|
+
tags: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Tag[];
|
|
22
|
+
bypassNotifications?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").BypassNotifications;
|
|
23
|
+
importId: string;
|
|
24
|
+
activationTriggerCount?: number;
|
|
25
|
+
source: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionSource;
|
|
26
|
+
ai?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAi;
|
|
27
|
+
hasTriggers?: boolean;
|
|
28
|
+
};
|
|
29
|
+
notification: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionNotification | undefined;
|
|
30
|
+
promotion: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionOptions_PromotionOptions;
|
|
31
|
+
} | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const parsePromotion = (response) => {
|
|
2
|
+
const questionItem = response.data?.attributes?.question;
|
|
3
|
+
const feedItem = response.data?.attributes?.feedItem?.attributes?.attributes?.case === 'promotion'
|
|
4
|
+
? response.data.attributes.feedItem.attributes
|
|
5
|
+
: undefined;
|
|
6
|
+
if (feedItem === undefined || !questionItem) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { options, notification, ...question } = questionItem;
|
|
10
|
+
const promotionItem = options?.options.case === 'promotion' ? options.options.value : undefined;
|
|
11
|
+
if (!promotionItem) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line consistent-return
|
|
15
|
+
return {
|
|
16
|
+
id: question.id,
|
|
17
|
+
question,
|
|
18
|
+
notification,
|
|
19
|
+
promotion: promotionItem,
|
|
20
|
+
};
|
|
21
|
+
};
|
package/lib/background.js
CHANGED
|
@@ -84,8 +84,10 @@ export class GamificationBackground {
|
|
|
84
84
|
if (!question || question.type === QuestionType.PROMOTION) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (question.status === QuestionStatus.RESOLVED) {
|
|
88
|
+
if (activeQuestionId === question.id) {
|
|
89
|
+
this.activeQuestionId.mutate(undefined);
|
|
90
|
+
}
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
91
93
|
this.activeQuestionId.mutate(response.data?.attributes);
|
|
@@ -100,6 +102,26 @@ export class GamificationBackground {
|
|
|
100
102
|
}
|
|
101
103
|
});
|
|
102
104
|
}));
|
|
105
|
+
this.cancels.add(this.activeQuestionId.subscribe((item, prevItem) => {
|
|
106
|
+
if (item.data?.feedItem) {
|
|
107
|
+
instance.sdk.onQuestionActivate({
|
|
108
|
+
stage: 'activate',
|
|
109
|
+
id: item.data.feedItem.id,
|
|
110
|
+
isViewed: !!this.notifications.isViewed(item.data.feedItem.id),
|
|
111
|
+
hasNotification: true,
|
|
112
|
+
type: item.data.feedItem.type,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (!item.data?.feedItem && prevItem?.data?.feedItem) {
|
|
116
|
+
instance.sdk.onQuestionActivate({
|
|
117
|
+
stage: 'deactivate',
|
|
118
|
+
id: prevItem.data.feedItem.id,
|
|
119
|
+
isViewed: !!this.notifications.isViewed(prevItem.data.feedItem.id),
|
|
120
|
+
hasNotification: true,
|
|
121
|
+
type: prevItem.data.feedItem.type,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
103
125
|
this.feedSubscription.connect();
|
|
104
126
|
/**
|
|
105
127
|
* invalidate active question on interactiveAllowed change
|
package/lib/gamification.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
|
|
|
67
67
|
openedFrom?: "list" | "notification";
|
|
68
68
|
}) => void | (() => void);
|
|
69
69
|
getFeedItem: (id: string) => Promise<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem | undefined>;
|
|
70
|
-
isOpenedQuestion: (questionId: string) =>
|
|
70
|
+
isOpenedQuestion: (questionId: string) => boolean;
|
|
71
71
|
closeQuestion: (questionId?: string) => void;
|
|
72
72
|
openUser: (friendId: string) => Promise<void>;
|
|
73
73
|
closeUser: () => void;
|
package/lib/gamification.js
CHANGED
|
@@ -401,7 +401,7 @@ export class Gamification extends AbstractFeature {
|
|
|
401
401
|
return queries.getFeedItem(id, this.transport);
|
|
402
402
|
};
|
|
403
403
|
isOpenedQuestion = (questionId) => {
|
|
404
|
-
return this.notifications.isViewed(questionId);
|
|
404
|
+
return !!this.notifications.isViewed(questionId);
|
|
405
405
|
};
|
|
406
406
|
closeQuestion = (questionId) => {
|
|
407
407
|
return this.background.closeQuestion(questionId);
|
package/lib/queries/index.d.ts
CHANGED
|
@@ -351,18 +351,67 @@ export declare const getQuestionByUser: (questionId: string, transport: Transpor
|
|
|
351
351
|
export declare const getQuestionDetail: (questionId: string, transport: Transport) => Promise<import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Question | undefined>;
|
|
352
352
|
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>;
|
|
353
353
|
export declare const getPromotionDetail: (promoId: string, transport: Transport) => Promise<{
|
|
354
|
-
|
|
354
|
+
id: string;
|
|
355
|
+
question: {
|
|
356
|
+
id: string;
|
|
357
|
+
type: QuestionType;
|
|
358
|
+
subject?: string;
|
|
359
|
+
appearance?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAppearance;
|
|
360
|
+
sponsorship?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Sponsorship;
|
|
361
|
+
answers: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAnswer[];
|
|
362
|
+
status: QuestionStatus;
|
|
363
|
+
position?: number;
|
|
364
|
+
marketClosed: boolean;
|
|
365
|
+
activatedAt: string;
|
|
366
|
+
answerSetAt: string;
|
|
367
|
+
overrides: {
|
|
368
|
+
[key: string]: boolean;
|
|
369
|
+
};
|
|
370
|
+
eventId: string;
|
|
371
|
+
streamTimestamp?: import("@bufbuild/protobuf").Timestamp;
|
|
372
|
+
tags: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Tag[];
|
|
373
|
+
bypassNotifications?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").BypassNotifications;
|
|
374
|
+
importId: string;
|
|
375
|
+
activationTriggerCount?: number;
|
|
376
|
+
source: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionSource;
|
|
377
|
+
ai?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAi;
|
|
378
|
+
hasTriggers?: boolean;
|
|
379
|
+
};
|
|
380
|
+
promotion: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionOptions_PromotionOptions;
|
|
355
381
|
notification: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionNotification | undefined;
|
|
356
382
|
} | undefined>;
|
|
357
383
|
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>;
|
|
358
384
|
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>;
|
|
359
|
-
export declare const $
|
|
360
|
-
attributes: import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").PromotionHistory;
|
|
385
|
+
export declare const $activePromotionId: ($slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<{
|
|
361
386
|
id: string;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
387
|
+
question: {
|
|
388
|
+
id: string;
|
|
389
|
+
type: QuestionType;
|
|
390
|
+
subject?: string;
|
|
391
|
+
appearance?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAppearance;
|
|
392
|
+
sponsorship?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Sponsorship;
|
|
393
|
+
answers: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAnswer[];
|
|
394
|
+
status: QuestionStatus;
|
|
395
|
+
position?: number;
|
|
396
|
+
marketClosed: boolean;
|
|
397
|
+
activatedAt: string;
|
|
398
|
+
answerSetAt: string;
|
|
399
|
+
overrides: {
|
|
400
|
+
[key: string]: boolean;
|
|
401
|
+
};
|
|
402
|
+
eventId: string;
|
|
403
|
+
streamTimestamp?: import("@bufbuild/protobuf").Timestamp;
|
|
404
|
+
tags: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").Tag[];
|
|
405
|
+
bypassNotifications?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").BypassNotifications;
|
|
406
|
+
importId: string;
|
|
407
|
+
activationTriggerCount?: number;
|
|
408
|
+
source: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionSource;
|
|
409
|
+
ai?: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionAi;
|
|
410
|
+
hasTriggers?: boolean;
|
|
411
|
+
};
|
|
412
|
+
promotion: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionOptions_PromotionOptions;
|
|
413
|
+
notification: import("@streamlayer/sl-eslib/interactive/interactive.common_pb").QuestionNotification | undefined;
|
|
414
|
+
} | undefined, any>;
|
|
366
415
|
export { $userSummary, $leaderboardList } from './leaderboard';
|
|
367
416
|
export { $friends } from './friends';
|
|
368
417
|
export { $moderation } from './moderation';
|
package/lib/queries/index.js
CHANGED
|
@@ -80,10 +80,12 @@ export const getPromotionDetail = async (promoId, transport) => {
|
|
|
80
80
|
const res = await client.getQuestion({
|
|
81
81
|
id: promoId,
|
|
82
82
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
if (!res.data?.attributes) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const { options, notification, ...question } = res.data.attributes;
|
|
87
|
+
const promotion = options?.options.case === 'promotion' ? options?.options.value : undefined;
|
|
88
|
+
return promotion ? { id: question.id, question, promotion, notification } : undefined;
|
|
87
89
|
};
|
|
88
90
|
export const $pickHistory = (slStreamId, transport) => {
|
|
89
91
|
const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'pickHistory', params: [slStreamId] });
|
|
@@ -124,7 +126,7 @@ export const $feedList = ($slStreamId, $interactiveAllowed, transport) => {
|
|
|
124
126
|
refetchInterval: 0,
|
|
125
127
|
});
|
|
126
128
|
};
|
|
127
|
-
export const $
|
|
129
|
+
export const $activePromotionId = ($slStreamId, transport) => {
|
|
128
130
|
const { client, queryKey } = transport.createPromiseClient(Feed, {
|
|
129
131
|
method: 'list',
|
|
130
132
|
params: [$slStreamId],
|
|
@@ -132,7 +134,7 @@ export const $promotionList = ($slStreamId, transport) => {
|
|
|
132
134
|
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
133
135
|
fetcher: async (_, __, slStreamId) => {
|
|
134
136
|
if (!slStreamId) {
|
|
135
|
-
return
|
|
137
|
+
return undefined;
|
|
136
138
|
}
|
|
137
139
|
const res = await client.list({
|
|
138
140
|
eventId: slStreamId,
|
|
@@ -140,18 +142,16 @@ export const $promotionList = ($slStreamId, transport) => {
|
|
|
140
142
|
types: [QuestionType.PROMOTION],
|
|
141
143
|
statuses: [QuestionStatus.ACTIVE],
|
|
142
144
|
},
|
|
145
|
+
pagination: {
|
|
146
|
+
page: 0,
|
|
147
|
+
pageSize: 1,
|
|
148
|
+
},
|
|
143
149
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
...attributes,
|
|
151
|
-
attributes: attributes.attributes.value,
|
|
152
|
-
};
|
|
153
|
-
})
|
|
154
|
-
.filter(Boolean);
|
|
150
|
+
const feedItem = res.data?.[0];
|
|
151
|
+
if (feedItem.type === 'promotion') {
|
|
152
|
+
return getPromotionDetail(feedItem.id, transport);
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
155
|
},
|
|
156
156
|
dedupeTime: 0,
|
|
157
157
|
refetchInterval: 0,
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamlayer/feature-gamification",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@bufbuild/protobuf": "^1.10.0",
|
|
6
6
|
"@fastify/deepmerge": "^2.0.0",
|
|
7
|
-
"@streamlayer/sl-eslib": "^5.
|
|
7
|
+
"@streamlayer/sl-eslib": "^5.123.1",
|
|
8
8
|
"nanostores": "^0.10.3",
|
|
9
|
-
"@streamlayer/sdk-web-api": "^1.
|
|
10
|
-
"@streamlayer/sdk-web-core": "^1.
|
|
11
|
-
"@streamlayer/sdk-web-interfaces": "^1.1.
|
|
12
|
-
"@streamlayer/sdk-web-logger": "^1.0.
|
|
13
|
-
"@streamlayer/sdk-web-notifications": "^1.1.
|
|
14
|
-
"@streamlayer/sdk-web-storage": "^1.0.
|
|
15
|
-
"@streamlayer/sdk-web-types": "^1.
|
|
9
|
+
"@streamlayer/sdk-web-api": "^1.5.0",
|
|
10
|
+
"@streamlayer/sdk-web-core": "^1.4.0",
|
|
11
|
+
"@streamlayer/sdk-web-interfaces": "^1.1.12",
|
|
12
|
+
"@streamlayer/sdk-web-logger": "^1.0.17",
|
|
13
|
+
"@streamlayer/sdk-web-notifications": "^1.1.12",
|
|
14
|
+
"@streamlayer/sdk-web-storage": "^1.0.17",
|
|
15
|
+
"@streamlayer/sdk-web-types": "^1.6.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"tslib": "^2.7.0"
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { SingleStore, createComputedStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
-
import { Advertisement } from '.';
|
|
3
|
-
export type AdvertisementsQueueOptions = {
|
|
4
|
-
concurrency: number;
|
|
5
|
-
animationDelay: number;
|
|
6
|
-
};
|
|
7
|
-
export type AdvertisementsList = ReturnType<typeof createComputedStore<Advertisement[]>>;
|
|
8
|
-
export declare class AdvertisementsQueue {
|
|
9
|
-
advertisementList: ReturnType<SingleStore<Map<Advertisement['id'], Advertisement>>['getStore']>;
|
|
10
|
-
private store;
|
|
11
|
-
private timeouts;
|
|
12
|
-
private waitingQueue;
|
|
13
|
-
private activeQueue;
|
|
14
|
-
private options;
|
|
15
|
-
private logger;
|
|
16
|
-
constructor(options: AdvertisementsQueueOptions);
|
|
17
|
-
addToQueue: (advertisement: Advertisement) => void;
|
|
18
|
-
tickWaitingQueue: () => void;
|
|
19
|
-
tickActiveQueue: (advertisementId: string) => void;
|
|
20
|
-
removeFromList: (advertisementId: string) => void;
|
|
21
|
-
closeAdvertisement: (advertisementId: string, { animateHiding }?: {
|
|
22
|
-
animateHiding?: boolean | undefined;
|
|
23
|
-
}) => Advertisement | undefined;
|
|
24
|
-
drain: () => void;
|
|
25
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { createSingleStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
-
import { createLogger } from '@streamlayer/sdk-web-logger';
|
|
3
|
-
export class AdvertisementsQueue {
|
|
4
|
-
advertisementList;
|
|
5
|
-
store;
|
|
6
|
-
timeouts;
|
|
7
|
-
waitingQueue;
|
|
8
|
-
activeQueue;
|
|
9
|
-
options;
|
|
10
|
-
logger;
|
|
11
|
-
constructor(options) {
|
|
12
|
-
this.options = options;
|
|
13
|
-
this.logger = createLogger('advertisement_queue');
|
|
14
|
-
this.store = new Map();
|
|
15
|
-
this.timeouts = new Map();
|
|
16
|
-
this.waitingQueue = new Set();
|
|
17
|
-
this.activeQueue = new Set();
|
|
18
|
-
this.advertisementList = createSingleStore(new Map());
|
|
19
|
-
}
|
|
20
|
-
addToQueue = (advertisement) => {
|
|
21
|
-
if (this.store.has(advertisement.id)) {
|
|
22
|
-
this.logger.debug({ advertisement }, 'skip existed advertisement: %o');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const close = advertisement.close;
|
|
26
|
-
const action = advertisement.action;
|
|
27
|
-
this.store.set(advertisement.id, {
|
|
28
|
-
...advertisement,
|
|
29
|
-
close: (...args) => {
|
|
30
|
-
if (close) {
|
|
31
|
-
close(...args);
|
|
32
|
-
}
|
|
33
|
-
this.closeAdvertisement(advertisement.id);
|
|
34
|
-
},
|
|
35
|
-
action: (...args) => {
|
|
36
|
-
if (action) {
|
|
37
|
-
action(...args);
|
|
38
|
-
}
|
|
39
|
-
this.closeAdvertisement(advertisement.id, { animateHiding: false });
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
this.waitingQueue.add(advertisement.id);
|
|
43
|
-
/**
|
|
44
|
-
* Hide an oldest advertisement if the active queue is full,
|
|
45
|
-
* and show a new advertisement on the next tick
|
|
46
|
-
*/
|
|
47
|
-
if (this.activeQueue.size === this.options.concurrency) {
|
|
48
|
-
const [job] = this.activeQueue;
|
|
49
|
-
this.closeAdvertisement(job);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
this.tickWaitingQueue();
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
tickWaitingQueue = () => {
|
|
56
|
-
if (this.activeQueue.size < this.options.concurrency) {
|
|
57
|
-
const [job] = this.waitingQueue;
|
|
58
|
-
if (!job) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
this.activeQueue.add(job);
|
|
62
|
-
this.waitingQueue.delete(job);
|
|
63
|
-
this.logger.debug({ job }, 'waiting queue tick');
|
|
64
|
-
this.tickActiveQueue(job);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
this.logger.debug({ queueSize: this.activeQueue.size, concurrency: this.options.concurrency }, 'waiting queue tick skipped');
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
tickActiveQueue = (advertisementId) => {
|
|
71
|
-
if (!advertisementId) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const advertisement = this.store.get(advertisementId);
|
|
75
|
-
if (!advertisement) {
|
|
76
|
-
this.logger.debug({ advertisementId }, 'active queue tick skipped, advertisement not exist');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const timeout = setTimeout(() => {
|
|
80
|
-
const closureId = advertisementId;
|
|
81
|
-
const prevQueue = new Map(this.advertisementList.get());
|
|
82
|
-
prevQueue.set(advertisement.id, advertisement);
|
|
83
|
-
this.advertisementList.set(prevQueue);
|
|
84
|
-
if (advertisement.autoHideDuration !== Infinity) {
|
|
85
|
-
const timeout = setTimeout(() => {
|
|
86
|
-
this.logger.debug({ advertisementId: closureId, delay: advertisement.autoHideDuration || 5000 }, 'advertisement hiding by autoHideDuration');
|
|
87
|
-
this.closeAdvertisement(closureId);
|
|
88
|
-
}, advertisement.autoHideDuration || 5000);
|
|
89
|
-
this.timeouts.set(closureId, timeout);
|
|
90
|
-
}
|
|
91
|
-
this.logger.debug({ advertisementId: closureId, queue: [...prevQueue.values()] }, 'advertisement displayed');
|
|
92
|
-
}, advertisement.delay || 0);
|
|
93
|
-
this.timeouts.set(advertisementId, timeout);
|
|
94
|
-
this.logger.debug({ advertisementId }, 'active queue tick completed');
|
|
95
|
-
this.tickWaitingQueue();
|
|
96
|
-
};
|
|
97
|
-
removeFromList = (advertisementId) => {
|
|
98
|
-
const prevQueue = new Map(this.advertisementList.get());
|
|
99
|
-
prevQueue.delete(advertisementId);
|
|
100
|
-
this.advertisementList.set(prevQueue);
|
|
101
|
-
this.store.delete(advertisementId);
|
|
102
|
-
this.activeQueue.delete(advertisementId);
|
|
103
|
-
this.waitingQueue.delete(advertisementId);
|
|
104
|
-
this.logger.debug({ advertisementId }, 'advertisement removed from list');
|
|
105
|
-
};
|
|
106
|
-
closeAdvertisement = (advertisementId, { animateHiding = true } = {}) => {
|
|
107
|
-
const prevQueue = new Map(this.advertisementList.get());
|
|
108
|
-
const advertisement = prevQueue.get(advertisementId);
|
|
109
|
-
if (advertisement) {
|
|
110
|
-
// do not hide advertisement if we have more than one advertisement in waiting queue,
|
|
111
|
-
// because we need to show next advertisement immediately
|
|
112
|
-
advertisement.hiding = !(this.waitingQueue.size >= this.options.concurrency) && animateHiding;
|
|
113
|
-
this.advertisementList.set(prevQueue);
|
|
114
|
-
const timeout = setTimeout(() => {
|
|
115
|
-
const prevQueue = new Map(this.advertisementList.get());
|
|
116
|
-
prevQueue.delete(advertisementId);
|
|
117
|
-
this.advertisementList.set(prevQueue);
|
|
118
|
-
const timeout = this.timeouts.get(advertisementId);
|
|
119
|
-
if (timeout !== undefined) {
|
|
120
|
-
clearTimeout(timeout);
|
|
121
|
-
this.timeouts.delete(advertisementId);
|
|
122
|
-
}
|
|
123
|
-
this.logger.debug({ advertisementId }, 'advertisement hidden');
|
|
124
|
-
}, animateHiding ? this.options.animationDelay || 0 : 0);
|
|
125
|
-
this.timeouts.set(advertisementId, timeout);
|
|
126
|
-
}
|
|
127
|
-
this.store.delete(advertisementId);
|
|
128
|
-
this.activeQueue.delete(advertisementId);
|
|
129
|
-
this.waitingQueue.delete(advertisementId);
|
|
130
|
-
this.tickWaitingQueue();
|
|
131
|
-
this.logger.debug({ advertisementId }, 'advertisement hiding');
|
|
132
|
-
return advertisement;
|
|
133
|
-
};
|
|
134
|
-
drain = () => {
|
|
135
|
-
this.store.clear();
|
|
136
|
-
this.timeouts.clear();
|
|
137
|
-
this.waitingQueue.clear();
|
|
138
|
-
this.activeQueue.clear();
|
|
139
|
-
this.advertisementList.off();
|
|
140
|
-
this.advertisementList.set(new Map());
|
|
141
|
-
};
|
|
142
|
-
}
|