@jolibox/implement 1.1.19-beta.1 → 1.1.19-beta.6

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.
Files changed (42) hide show
  1. package/.rush/temp/package-deps_build.json +22 -15
  2. package/.rush/temp/shrinkwrap-deps.json +1 -1
  3. package/dist/common/rewards/index.d.ts +1 -10
  4. package/dist/common/rewards/registers/use-ads.d.ts +3 -2
  5. package/dist/common/rewards/registers/use-jolicoin-only.d.ts +10 -0
  6. package/dist/common/rewards/registers/utils/coins/index.d.ts +32 -0
  7. package/dist/common/rewards/registers/utils/event-listener.d.ts +15 -0
  8. package/dist/common/rewards/registers/utils/index.d.ts +1 -0
  9. package/dist/common/rewards/reward-emitter.d.ts +58 -0
  10. package/dist/common/rewards/reward-helper.d.ts +2 -1
  11. package/dist/common/utils/index.d.ts +49 -2
  12. package/dist/h5/api/ads.d.ts +1 -1
  13. package/dist/h5/http/index.d.ts +1 -1
  14. package/dist/h5/rewards/index.d.ts +4 -0
  15. package/dist/index.js +197 -3
  16. package/dist/index.native.js +402 -21
  17. package/dist/native/api/ads.d.ts +1 -1
  18. package/dist/native/api/login.d.ts +4 -1
  19. package/dist/native/rewards/index.d.ts +4 -0
  20. package/dist/native/ui/modal-iframe.d.ts +26 -0
  21. package/implement.build.log +2 -2
  22. package/package.json +5 -4
  23. package/src/common/context/index.ts +3 -3
  24. package/src/common/rewards/fetch-reward.ts +9 -3
  25. package/src/common/rewards/index.ts +1 -12
  26. package/src/common/rewards/registers/use-ads.ts +3 -2
  27. package/src/common/rewards/registers/use-jolicoin-only.ts +57 -0
  28. package/src/common/rewards/registers/use-jolicoin.ts +27 -77
  29. package/src/common/rewards/registers/utils/coins/index.ts +186 -0
  30. package/src/common/rewards/registers/utils/event-listener.ts +66 -0
  31. package/src/common/rewards/registers/utils/index.ts +8 -0
  32. package/src/common/rewards/reward-emitter.ts +79 -0
  33. package/src/common/rewards/reward-helper.ts +3 -2
  34. package/src/common/utils/index.ts +117 -4
  35. package/src/h5/api/ads.ts +65 -12
  36. package/src/h5/http/index.ts +2 -2
  37. package/src/h5/rewards/index.ts +69 -0
  38. package/src/native/api/ads.ts +95 -33
  39. package/src/native/api/login.ts +1 -1
  40. package/src/native/bootstrap/index.ts +5 -0
  41. package/src/native/rewards/index.ts +138 -0
  42. package/src/native/ui/modal-iframe.ts +271 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * rewards event handlers
3
+ */
4
+
5
+ import {
6
+ rewardsEmitter,
7
+ UseModalEventName,
8
+ IUseModalEvent,
9
+ PaymentResultEventName,
10
+ UseModalResultEventName,
11
+ InvokePaymentEventName,
12
+ IInvokePaymentEvent,
13
+ IPaymentChoice
14
+ } from '@/common/rewards/reward-emitter';
15
+ import { createConfirmJolicoinModal, createPaymentJolicoinModal } from '@jolibox/ui';
16
+ import { innerFetch as fetch } from '../network';
17
+ import { StandardResponse } from '@jolibox/types';
18
+ import { context } from '@/common/context';
19
+ import { login } from '../api/login';
20
+
21
+ /**
22
+ * confirm jolicoin modal
23
+ */
24
+ rewardsEmitter.on(UseModalEventName, (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IUseModalEvent) => {
25
+ if (type === 'ADS-JOLI_COIN') {
26
+ //TODO
27
+ console.log('use modal show by frequency');
28
+ }
29
+ const modal = createConfirmJolicoinModal({
30
+ data: {
31
+ enableAutoDeduct: params.enableAutoDeduct,
32
+ userJolicoin: params.userJoliCoin,
33
+ joliCoinQuantity: params.joliCoinQuantity
34
+ },
35
+ buttons: {
36
+ confirm: {
37
+ text: params.confirmButtonText,
38
+ onPress: () => {
39
+ rewardsEmitter.emit(UseModalResultEventName, { useModalResult: 'CONFIRM' });
40
+ modal.destroy();
41
+ }
42
+ },
43
+ cancel: {
44
+ text: params.cancelButtonText,
45
+ onPress: ({ type }) => {
46
+ rewardsEmitter.emit(UseModalResultEventName, {
47
+ useModalResult: (type ?? '') === 'CANCEL' ? 'CANCEL' : 'FAILED'
48
+ });
49
+ modal.destroy();
50
+ }
51
+ }
52
+ }
53
+ });
54
+ });
55
+
56
+ /**
57
+ * payment jolicoin modal
58
+ */
59
+ rewardsEmitter.on(
60
+ InvokePaymentEventName,
61
+ async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IInvokePaymentEvent) => {
62
+ try {
63
+ // handle showup frequecy
64
+ if (type === 'ADS-JOLI_COIN') {
65
+ //
66
+ console.log('show by frequency');
67
+ }
68
+
69
+ const balenceDetails = await getBalenceDetails();
70
+ if (!balenceDetails) {
71
+ rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
72
+ return;
73
+ }
74
+ const modal = createPaymentJolicoinModal({
75
+ data: {
76
+ userJolicoin: params.userJoliCoin,
77
+ joliCoinQuantity: params.joliCoinQuantity,
78
+ paymentChoices: balenceDetails.paymentChoices,
79
+ enableAutoDeduct: balenceDetails.enableAutoDeduct
80
+ },
81
+ buttons: {
82
+ confirm: {
83
+ text: params.confirmButtonText,
84
+ onPress: async (productId: string) => {
85
+ // TODO: native payment
86
+ // await invokeNativePayment(productId);
87
+ if (!context.hostUserInfo?.isLogin) {
88
+ const { data } = await login();
89
+ if (!data?.isLogin) {
90
+ console.log('login failed');
91
+ return;
92
+ }
93
+ }
94
+ console.log('invokeNativePayment', productId);
95
+ rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS' });
96
+ modal.destroy();
97
+ }
98
+ },
99
+ cancel: {
100
+ text: params.cancelButtonText,
101
+ onPress: ({ type }) => {
102
+ rewardsEmitter.emit(PaymentResultEventName, {
103
+ paymentResult: (type ?? '') === 'CANCEL' ? 'CANCEL' : 'FAILED'
104
+ });
105
+ modal.destroy();
106
+ }
107
+ }
108
+ }
109
+ });
110
+ } catch (error) {
111
+ console.info('payment failed', error);
112
+ rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
113
+ }
114
+ }
115
+ );
116
+
117
+ const getBalenceDetails = async (): Promise<
118
+ | {
119
+ balance: number;
120
+ enableAutoDeduct: boolean;
121
+ paymentChoices: IPaymentChoice[];
122
+ }
123
+ | undefined
124
+ > => {
125
+ const { response } = await fetch<
126
+ StandardResponse<{
127
+ balance: number;
128
+ enableAutoDeduct: boolean;
129
+ paymentChoices: IPaymentChoice[];
130
+ }>
131
+ >('/api/joli-coin/balance-detail', {
132
+ method: 'GET',
133
+ appendHostCookie: true,
134
+ responseType: 'json'
135
+ });
136
+
137
+ return response.data?.data;
138
+ };
@@ -0,0 +1,271 @@
1
+ import { invokeNative } from '@jolibox/native-bridge';
2
+ import { context } from '@/common/context';
3
+ import { hostEmitter } from '@jolibox/common';
4
+
5
+ interface IframeModalOptions {
6
+ url: string;
7
+ height?: string; // default '50%'
8
+ showCloseButton?: boolean; // default true
9
+ enableDragClose?: boolean; // default true
10
+ useAnimation?: boolean; // default true
11
+ onClose?: () => void;
12
+ onLoad?: () => void;
13
+ }
14
+
15
+ const DEFAULT_OPTIONS: Partial<IframeModalOptions> = {
16
+ height: '50%',
17
+ showCloseButton: true,
18
+ enableDragClose: true,
19
+ useAnimation: true
20
+ };
21
+
22
+ let modalOverlay: HTMLElement | null = null;
23
+ let modalContainer: HTMLElement | null = null;
24
+ let iframe: HTMLIFrameElement | null = null;
25
+ /**
26
+ * create a half-screen iframe modal
27
+ */
28
+ export function createIframeModal(options: IframeModalOptions): { close: () => void } {
29
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
30
+ const { url, height, showCloseButton, enableDragClose, useAnimation, onClose, onLoad } = mergedOptions;
31
+
32
+ let startY = 0;
33
+ let currentY = 0;
34
+ let isModalVisible = false;
35
+
36
+ const getAnimationStyles = () => {
37
+ if (useAnimation) {
38
+ return {
39
+ overlayTransition: 'opacity 0.3s ease',
40
+ containerTransition: 'transform 0.3s ease',
41
+ iframeTransition: 'opacity 0.3s ease'
42
+ };
43
+ } else {
44
+ return {
45
+ overlayTransition: 'none',
46
+ containerTransition: 'none',
47
+ iframeTransition: 'none'
48
+ };
49
+ }
50
+ };
51
+
52
+ // 获取动画延迟时间
53
+ const getAnimationDelay = () => (useAnimation ? 300 : 0);
54
+
55
+ // create modal dom
56
+ function createModalDOM() {
57
+ const { overlayTransition, containerTransition, iframeTransition } = getAnimationStyles();
58
+
59
+ modalOverlay = document.createElement('div');
60
+ modalOverlay.id = 'jolibox-modal-overlay';
61
+ modalOverlay.style.cssText = `position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:1000; display:flex; justify-content:center; align-items:flex-end; transition:${overlayTransition}; opacity:0; visibility:hidden;`;
62
+
63
+ modalContainer = document.createElement('div');
64
+ modalContainer.id = 'jolibox-modal-container';
65
+ modalContainer.style.cssText = `width:100%; height:${height}; background:white; border-radius:12px 12px 0 0; overflow:hidden; box-shadow:0 -2px 10px rgba(0,0,0,0.2); transform:translateY(100%); transition:${containerTransition};`;
66
+
67
+ const loadingIndicator = document.createElement('div');
68
+ loadingIndicator.id = 'jolibox-modal-loading';
69
+ loadingIndicator.style.cssText =
70
+ 'position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:40px; height:40px; border:4px solid #f3f3f3; border-top:4px solid #3498db; border-radius:50%; animation:spin 1s linear infinite;';
71
+
72
+ const styleElement = document.createElement('style');
73
+ styleElement.textContent =
74
+ '@keyframes spin { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } }';
75
+ document.head.appendChild(styleElement);
76
+
77
+ // safe area adapter
78
+ const safeAreaDiv = document.createElement('div');
79
+ safeAreaDiv.style.cssText =
80
+ 'width:100%; height:100%; display:flex; flex-direction:column; padding-bottom:env(safe-area-inset-bottom, 0); position:relative;';
81
+
82
+ // create iframe
83
+ iframe = document.createElement('iframe');
84
+ iframe.style.cssText = `width:100%; flex:1; border:none; opacity:0; transition:${iframeTransition};`;
85
+ iframe.src = url;
86
+
87
+ // iframe loaded event
88
+ iframe.onload = () => {
89
+ if (loadingIndicator.parentNode) {
90
+ loadingIndicator.parentNode.removeChild(loadingIndicator);
91
+ }
92
+
93
+ // 显示iframe内容
94
+ iframe!.style.opacity = '1';
95
+
96
+ // 显示modal
97
+ showModal();
98
+
99
+ // 触发加载完成回调
100
+ onLoad?.();
101
+
102
+ // 发送全局事件
103
+ hostEmitter.emit('onModalIframeLoaded', { url });
104
+ };
105
+
106
+ // 组装DOM
107
+ safeAreaDiv.appendChild(iframe);
108
+ safeAreaDiv.appendChild(loadingIndicator);
109
+ modalContainer.appendChild(safeAreaDiv);
110
+
111
+ // 添加关闭按钮
112
+ if (showCloseButton) {
113
+ const closeBtn = document.createElement('div');
114
+ closeBtn.style.cssText =
115
+ 'position:absolute; top:10px; right:10px; width:30px; height:30px; background:rgba(0,0,0,0.1); border-radius:15px; display:flex; justify-content:center; align-items:center; cursor:pointer; z-index:10;';
116
+ closeBtn.innerHTML = '✕';
117
+ closeBtn.onclick = closeModal;
118
+ modalContainer.appendChild(closeBtn);
119
+ }
120
+
121
+ modalOverlay.appendChild(modalContainer);
122
+ document.body.appendChild(modalOverlay);
123
+
124
+ // 点击遮罩关闭
125
+ modalOverlay.addEventListener('click', function (e) {
126
+ if (e.target === modalOverlay) closeModal();
127
+ });
128
+
129
+ if (enableDragClose) {
130
+ addDragSupport();
131
+ }
132
+ }
133
+
134
+ // show modal
135
+ function showModal() {
136
+ if (!modalOverlay || !modalContainer || isModalVisible) return;
137
+
138
+ modalOverlay.style.visibility = 'visible';
139
+
140
+ if (useAnimation) {
141
+ requestAnimationFrame(() => {
142
+ if (modalOverlay) modalOverlay.style.opacity = '1';
143
+ if (modalContainer) modalContainer.style.transform = 'translateY(0)';
144
+ isModalVisible = true;
145
+ });
146
+ } else {
147
+ if (modalOverlay) modalOverlay.style.opacity = '1';
148
+ if (modalContainer) modalContainer.style.transform = 'translateY(0)';
149
+ isModalVisible = true;
150
+ }
151
+
152
+ invokeNative('updateContainerConfigSync', {
153
+ displayCapsuleButton: false,
154
+ webviewId: context.webviewId
155
+ });
156
+ }
157
+
158
+ function addDragSupport() {
159
+ if (!modalContainer) return;
160
+
161
+ const handleTouchStart = (e: TouchEvent) => {
162
+ startY = e.touches[0].clientY;
163
+ };
164
+
165
+ const handleTouchMove = (e: TouchEvent) => {
166
+ if (!isModalVisible) return;
167
+
168
+ currentY = e.touches[0].clientY;
169
+ const deltaY = currentY - startY;
170
+
171
+ if (deltaY > 0) {
172
+ if (modalContainer) {
173
+ // 拖动时始终不使用过渡效果
174
+ modalContainer.style.transition = 'none';
175
+ modalContainer.style.transform = `translateY(${deltaY}px)`;
176
+ }
177
+ e.preventDefault();
178
+ }
179
+ };
180
+
181
+ const handleTouchEnd = () => {
182
+ if (!isModalVisible || !modalContainer) return;
183
+
184
+ modalContainer.style.transition = useAnimation ? 'transform 0.3s ease' : 'none';
185
+
186
+ if (currentY - startY > 80) {
187
+ closeModal();
188
+ } else {
189
+ modalContainer.style.transform = 'translateY(0)';
190
+ }
191
+ };
192
+
193
+ modalContainer.addEventListener('touchstart', handleTouchStart);
194
+ modalContainer.addEventListener('touchmove', handleTouchMove);
195
+ modalContainer.addEventListener('touchend', handleTouchEnd);
196
+ }
197
+
198
+ // close
199
+ function closeModal() {
200
+ if (!modalOverlay || !modalContainer || !isModalVisible) return;
201
+
202
+ if (useAnimation) {
203
+ modalContainer.style.transform = 'translateY(100%)';
204
+ modalOverlay.style.opacity = '0';
205
+ } else {
206
+ modalOverlay.style.visibility = 'hidden';
207
+ }
208
+
209
+ isModalVisible = false;
210
+
211
+ // 恢复原生UI
212
+ invokeNative('updateContainerConfigSync', {
213
+ displayCapsuleButton: true,
214
+ webviewId: context.webviewId
215
+ });
216
+
217
+ setTimeout(() => {
218
+ if (modalOverlay) {
219
+ modalOverlay.remove();
220
+ modalOverlay = null;
221
+ modalContainer = null;
222
+ iframe = null;
223
+
224
+ // 触发关闭回调
225
+ onClose?.();
226
+
227
+ // 发送全局事件
228
+ hostEmitter.emit('onModalIframeClosed', { url });
229
+ }
230
+ }, getAnimationDelay());
231
+ }
232
+
233
+ createModalDOM();
234
+
235
+ return { close: closeModal };
236
+ }
237
+
238
+ declare global {
239
+ interface Window {
240
+ joliboxUI?: {
241
+ createIframeModal: (options: IframeModalOptions) => { close: () => void };
242
+ };
243
+ }
244
+ }
245
+
246
+ export function registerIframeModalToGlobal() {
247
+ window.joliboxUI = {
248
+ ...window.joliboxUI,
249
+ createIframeModal
250
+ };
251
+
252
+ return () => {
253
+ window.joliboxUI = undefined;
254
+ if (modalOverlay && modalOverlay.parentNode) {
255
+ modalOverlay.parentNode.removeChild(modalOverlay);
256
+ }
257
+
258
+ if (modalContainer && modalContainer.parentNode) {
259
+ modalContainer.parentNode.removeChild(modalContainer);
260
+ }
261
+
262
+ if (iframe && iframe.parentNode) {
263
+ iframe.parentNode.removeChild(iframe);
264
+ }
265
+
266
+ // 清空引用
267
+ modalOverlay = null;
268
+ modalContainer = null;
269
+ iframe = null;
270
+ };
271
+ }