react-native-quicktracking-analytics-module 1.0.4 → 2.0.2

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 (51) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/QTSDKManager.java +93 -0
  3. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/{QuicktrackingAnalyticsModuleModule.java → QuicktrackingAnalyticsModule.java} +35 -12
  4. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/QuicktrackingAnalyticsModulePackage.java +1 -1
  5. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/property/QTViewProperties.java +48 -0
  6. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/utils/QTLog.java +122 -0
  7. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/utils/RNPropertyManager.java +29 -0
  8. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/utils/RNTouchTargetHelper.java +139 -0
  9. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/utils/RNUtils.java +188 -0
  10. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/utils/RNViewUtils.java +148 -0
  11. package/ios/NSObject+QTReactNativeSwizzler.h +19 -0
  12. package/ios/NSObject+QTReactNativeSwizzler.m +38 -0
  13. package/ios/QTRNUtils.h +21 -0
  14. package/ios/QTRNUtils.m +48 -0
  15. package/ios/QTReactNativeRootViewManager.h +41 -0
  16. package/ios/QTReactNativeRootViewManager.m +100 -0
  17. package/ios/QTReactNativeViewProperty.h +28 -0
  18. package/ios/QTReactNativeViewProperty.m +27 -0
  19. package/ios/QuickTrackingSDKManager.h +25 -0
  20. package/ios/QuickTrackingSDKManager.m +140 -0
  21. package/ios/QuicktrackingAnalyticsModule.h +11 -1
  22. package/ios/QuicktrackingAnalyticsModule.m +46 -3
  23. package/ios/RCTRootView+QTReactNative.h +23 -0
  24. package/ios/RCTRootView+QTReactNative.m +56 -0
  25. package/lib/typescript/index.d.ts +3 -2
  26. package/package.json +27 -4
  27. package/react-native-quicktracking-analytics-module.podspec +2 -2
  28. package/src/hook.js +679 -0
  29. package/HISTORY.md +0 -17
  30. package/android/.gradle/7.4/checksums/checksums.lock +0 -0
  31. package/android/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock +0 -0
  32. package/android/.gradle/7.4/dependencies-accessors/gc.properties +0 -0
  33. package/android/.gradle/7.4/executionHistory/executionHistory.lock +0 -0
  34. package/android/.gradle/7.4/fileChanges/last-build.bin +0 -0
  35. package/android/.gradle/7.4/fileHashes/fileHashes.lock +0 -0
  36. package/android/.gradle/7.4/gc.properties +0 -0
  37. package/android/.gradle/7.4.2/checksums/checksums.lock +0 -0
  38. package/android/.gradle/7.4.2/dependencies-accessors/dependencies-accessors.lock +0 -0
  39. package/android/.gradle/7.4.2/dependencies-accessors/gc.properties +0 -0
  40. package/android/.gradle/7.4.2/fileChanges/last-build.bin +0 -0
  41. package/android/.gradle/7.4.2/fileHashes/fileHashes.lock +0 -0
  42. package/android/.gradle/7.4.2/gc.properties +0 -0
  43. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  44. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  45. package/android/.gradle/vcs-1/gc.properties +0 -0
  46. package/android/src/main/java/com/reactnativequicktrackinganalyticsmodule/MainApplication.java +0 -47
  47. package/ios/.DS_Store +0 -0
  48. package/ios/QuicktrackingAnalyticsModule.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
  49. package/ios/QuicktrackingAnalyticsModule.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  50. package/ios/QuicktrackingAnalyticsModule.xcodeproj/project.xcworkspace/xcuserdata/yuzhao.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  51. package/ios/QuicktrackingAnalyticsModule.xcodeproj/xcuserdata/yuzhao.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
package/src/hook.js ADDED
@@ -0,0 +1,679 @@
1
+ #! node option
2
+ // 系统变量
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const packageJSON = require('../../../package.json');
6
+ const dir = path.resolve(__dirname, '../../');
7
+
8
+ const ChartSet_UTF8 = 'utf8';
9
+ const QTHookTag = 'QUICKTRACKING HOOK';
10
+ const QTBackUpSuffix = '_quicktracking_backup';
11
+
12
+ // 工具函数- add try catch
13
+ function addTryCatch(functionBody) {
14
+ functionBody = functionBody.replace(/this/g, 'thatThis');
15
+ return (
16
+ '(function(thatThis){\n' +
17
+ ' try{\n ' +
18
+ functionBody +
19
+ " \n } catch (error) { throw new Error('QUICKTRACKING RN Hook Code 调用异常: ' + error);}\n" +
20
+ '})(this); /* QUICKTRACKING HOOK */'
21
+ );
22
+ }
23
+
24
+ // 工具函数 - 计算位置
25
+ function lastArgumentName(content, index) {
26
+ --index;
27
+ const lastComma = content.lastIndexOf(',', index);
28
+ const lastParentheses = content.lastIndexOf('(', index);
29
+ const start = Math.max(lastComma, lastParentheses);
30
+ return content.substring(start + 1, index + 1);
31
+ }
32
+
33
+ // 自动PV开关
34
+ let enableAutoCLK = false;
35
+
36
+ if (packageJSON && packageJSON['QTSDKConfig']) {
37
+ enableAutoCLK = !!packageJSON['QTSDKConfig']['enableAutoCLK'];
38
+ }
39
+
40
+ // react native clickable component path
41
+ const RNClickTouchableFilePath =
42
+ dir + '/react-native/Libraries/Components/Touchable/Touchable.js'; //原RNClickFilePath
43
+ const RNClickPressabilityFilePath =
44
+ dir + '/react-native/Libraries/Pressability/Pressability.js';
45
+ const RNClickableFiles = [
46
+ dir +
47
+ '/react-native/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js',
48
+ dir +
49
+ '/react-native/Libraries/Renderer/src/renderers/native/ReactNativeFiber-dev.js',
50
+ dir +
51
+ '/react-native/Libraries/Renderer/src/renderers/native/ReactNativeFiber-prod.js',
52
+ dir +
53
+ '/react-native/Libraries/Renderer/src/renderers/native/ReactNativeFiber-profiling.js',
54
+ dir + '/react-native/Libraries/Renderer/ReactNativeFiber-dev.js',
55
+ dir + '/react-native/Libraries/Renderer/ReactNativeFiber-prod.js',
56
+ dir + '/react-native/Libraries/Renderer/oss/ReactNativeRenderer-dev.js',
57
+ dir + '/react-native/Libraries/Renderer/oss/ReactNativeRenderer-prod.js',
58
+ dir + '/react-native/Libraries/Renderer/ReactNativeStack-dev.js',
59
+ dir + '/react-native/Libraries/Renderer/ReactNativeStack-prod.js',
60
+ dir + '/react-native/Libraries/Renderer/oss/ReactNativeRenderer-profiling.js',
61
+ dir + '/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js',
62
+ dir + '/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js',
63
+ dir +
64
+ '/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.js',
65
+ dir +
66
+ '/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js',
67
+ dir +
68
+ '/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js',
69
+ ];
70
+
71
+ // react native slider files path
72
+ const RNSliderFiles = [
73
+ dir + '/react-native/Libraries/Components/Slider/Slider.js',
74
+ dir + '/react-native/Libraries/Components/Slider/Slider.js',
75
+ dir + '/@react-native-community/slider/js/Slider.js',
76
+ dir + '/@react-native-community/slider/dist/Slider.js',
77
+ dir + '/@react-native-community/js/Slider.js',
78
+ dir + '/@react-native-community/src/js/Slider.js',
79
+ ];
80
+
81
+ // react native switch files path
82
+ const RNSwitchFiles = [
83
+ dir + '/react-native/Libraries/Components/Switch/Switch.js',
84
+ ];
85
+
86
+ // react native segmentedControl files path
87
+ const RNSegmentedControlFilePath = [
88
+ dir +
89
+ '/react-native/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js',
90
+ dir + '/@react-native-community/segmented-control/js/SegmentedControl.ios.js',
91
+ ];
92
+
93
+ // react native gestureButtons files path
94
+ const RNGestureButtonsFilePaths = [
95
+ dir + '/react-native-gesture-handler/GestureButtons.js',
96
+ dir + '/react-native-gesture-handler/src/components/GestureButtons.tsx',
97
+ ];
98
+
99
+ const QTClickTouchableHookCode = `(function(thatThis) {
100
+ try {
101
+ const ReactNative = require('react-native');
102
+ const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;
103
+ thatThis.props.onPress && qtModule && qtModule?.onEventAutoCLK(ReactNative.findNodeHandle(thatThis));
104
+ } catch (error) {
105
+ throw new Error('QuickTracking RN Hook Code 调用异常: ' + error);
106
+ }
107
+ })(this)
108
+ /* QUICKTRACKING HOOK */ `;
109
+
110
+ const QTClickPressabilityHookCode = `const tag = event.currentTarget && event.currentTarget._nativeTag ? event.currentTarget._nativeTag:event.currentTarget;
111
+ (function(thatThis, e, that){
112
+ if(thatThis){
113
+ try {
114
+ const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;
115
+ qtModule && qtModule?.onEventAutoCLK(thatThis);
116
+ }catch (error){
117
+ throw new Error('QuickTracking RN Hook Code 调用异常:' + error);
118
+ }
119
+ }
120
+ })(tag, event, this); /* QUICKTRACKING HOOK */ `;
121
+ // iOS 全埋点已覆盖
122
+ const QTSliderHookCode =
123
+ '(function(thatThis){\n' +
124
+ ' try {\n' +
125
+ " const ReactNative = require('react-native');\n" +
126
+ ' const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;\n' +
127
+ ' qtModule && qtModule?.onEventAutoCLK(event.nativeEvent.target);\n' +
128
+ ' } catch (error) { \n' +
129
+ " throw new Error('QUICKTRACKING RN Hook Code 调用异常: ' + error);\n" +
130
+ ' }\n' +
131
+ '})(this); /* QUICKTRACKING HOOK */';
132
+ // iOS 全埋点覆盖
133
+ const QTSegmentedControlHookCode =
134
+ 'if(this.props.onChange != null || this.props.onValueChange != null){\n' +
135
+ '(function(thatThis){\n' +
136
+ ' try {\n' +
137
+ " const ReactNative = require('react-native');\n" +
138
+ ' const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;\n' +
139
+ ' qtModule && qtModule?.onEventAutoCLK(event.nativeEvent.target);\n' +
140
+ ' } catch (error) { \n' +
141
+ " throw new Error('QUICKTRACKING RN Hook Code 调用异常: ' + error);}\n" +
142
+ '})(this); /* QUICKTRACKING HOOK */}';
143
+ // iOS 全埋点已覆盖
144
+ const QTSwitchHookCode = `if(this.props.onChange != null || this.props.onValueChange != null){
145
+ (function(thatThis){
146
+ try {
147
+ const ReactNative = require(react-native);
148
+ const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;
149
+ qtModule && qtModule?.onEventAutoCLK(ReactNative.findNodeHandle(thatThis));
150
+ } catch (error) {
151
+ throw new Error('QUICKTRACKING RN Hook Code 调用异常: ' + error);
152
+ }
153
+ })(this);
154
+ /* QUICKTRACKING HOOK */}`;
155
+ // iOS 已覆盖
156
+ const QTSwitchHookCode66 = `if(nativeSwitchRef.current && onValueChange){
157
+ (function(thatThis){
158
+ try {
159
+ const ReactNative = require(react-native);
160
+ const qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;
161
+ qtModule && qtModule?.onEventAutoCLK(ReactNative.findNodeHandle(nativeSwitchRef.current));
162
+ } catch (error) {
163
+ throw new Error('QUICKTRACKING RN Hook Code 调用异常:' + error);
164
+ }
165
+ })(this); /* QUICKTRACKING HOOK */}`;
166
+
167
+ // import ReactNative if need
168
+ const QTImportReactNativeHookCode = "import ReactNative from 'react-native';\n";
169
+
170
+ // hook Touchable.js
171
+ function QTTouchableHookRN() {
172
+ if (fs.existsSync(RNClickTouchableFilePath)) {
173
+ // read file content
174
+ let fileContent = fs.readFileSync(RNClickTouchableFilePath, ChartSet_UTF8);
175
+ // already hook, pass
176
+ if (fileContent.indexOf(QTHookTag) > -1) {
177
+ return;
178
+ }
179
+ console.log(`found Touchable.js: ${RNClickTouchableFilePath}`);
180
+ // get position of the injected code
181
+ const hookIndex = fileContent.indexOf('this.touchableHandlePress(');
182
+ // if method of touchableHandlePress not exist, throw exception
183
+ if (hookIndex === -1) {
184
+ throw "Can't not find touchableHandlePress function";
185
+ }
186
+
187
+ const injectedContent = `${fileContent.substring(
188
+ 0,
189
+ hookIndex
190
+ )}\n${QTClickTouchableHookCode}\n${fileContent.substring(hookIndex)}`;
191
+
192
+ // backup original Touchable.js file
193
+ fs.renameSync(
194
+ RNClickTouchableFilePath,
195
+ `${RNClickTouchableFilePath}${QTBackUpSuffix}`
196
+ );
197
+
198
+ // rewrite Touchable.js file
199
+ fs.writeFileSync(RNClickTouchableFilePath, injectedContent, ChartSet_UTF8);
200
+ console.log(`modify Touchable.js succeed`);
201
+ }
202
+ }
203
+
204
+ // hook 0.62\0.63 Pressable click
205
+ function QTPressableHookRN() {
206
+ if (fs.existsSync(RNClickPressabilityFilePath)) {
207
+ // read file content
208
+ var fileContent = fs.readFileSync(RNClickPressabilityFilePath, 'utf8');
209
+ // already hook, pass
210
+ if (fileContent.indexOf(QTHookTag) > -1) {
211
+ return;
212
+ }
213
+ console.log(`found Pressability.js: ${RNClickPressabilityFilePath}`);
214
+ // get position of the injected code
215
+ var scriptStr = 'onPress(event);';
216
+ var hookIndex = fileContent.lastIndexOf(scriptStr);
217
+ // if method of onPress not exist, throw exception
218
+
219
+ if (hookIndex === -1) {
220
+ throw "Can't not find onPress(event); code";
221
+ }
222
+
223
+ var hookedContent = `${fileContent.substring(
224
+ 0,
225
+ hookIndex
226
+ )}\n${QTClickPressabilityHookCode}\n${fileContent.substring(hookIndex)}`;
227
+ // backup original Pressability.js file
228
+ if (fileContent.indexOf(`${QTImportReactNativeHookCode}`) === -1) {
229
+ hookedContent = QTImportReactNativeHookCode + hookedContent;
230
+ }
231
+ fs.renameSync(
232
+ RNClickPressabilityFilePath,
233
+ `${RNClickPressabilityFilePath}${QTBackUpSuffix}`
234
+ );
235
+ // rewrite Pressability.js file
236
+ fs.writeFileSync(RNClickPressabilityFilePath, hookedContent, ChartSet_UTF8);
237
+ console.log(`modify Pressability.js succeed`);
238
+ }
239
+ }
240
+
241
+ // hook slider
242
+ function QTHookSliderRN(reset = false) {
243
+ RNSliderFiles.forEach(function (onefile) {
244
+ if (fs.existsSync(onefile)) {
245
+ // 读取文件内容
246
+ const fileContent = fs.readFileSync(onefile, ChartSet_UTF8);
247
+ if (reset) {
248
+ // 未被 hook 过代码,不需要处理
249
+ if (fileContent.indexOf(QTHookTag) === -1) {
250
+ return;
251
+ }
252
+ // 检查备份文件是否存在
253
+ const backFilePath = `${onefile}${QTBackUpSuffix}`;
254
+ if (!fs.existsSync(backFilePath)) {
255
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
256
+ }
257
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名文件
258
+ fs.renameSync(backFilePath, onefile);
259
+ console.log(`found and reset Slider.js: ${onefile}`);
260
+ } else {
261
+ // 已经 hook 过了,不需要再次 hook
262
+ if (fileContent.indexOf(QTHookTag) > -1) {
263
+ return;
264
+ }
265
+ console.log(`found Slider.js: ${onefile}`);
266
+ // 获取 hook 的代码插入的位置
267
+ const scriptStr = 'onSlidingComplete(event.nativeEvent.value);';
268
+ const hookIndex = fileContent.indexOf(scriptStr);
269
+ // 判断文件是否异常,不存在 touchableHandlePress 方法,导致无法 hook 点击事件
270
+ if (hookIndex === -1) {
271
+ throw "Can't not find onSlidingComplete function";
272
+ }
273
+ // 插入 hook 代码
274
+ const hookedContent = `${fileContent.substring(
275
+ 0,
276
+ hookIndex + scriptStr.length
277
+ )}\n${QTSliderHookCode}\n${fileContent.substring(
278
+ hookIndex + scriptStr.length
279
+ )}`;
280
+ // 备份源文件
281
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
282
+ // 重写文件
283
+ fs.writeFileSync(onefile, hookedContent, ChartSet_UTF8);
284
+ console.log(`modify Slider.js succeed`);
285
+ }
286
+ }
287
+ });
288
+ }
289
+ // hook switch
290
+ function QTHookSwitchRN(reset = false) {
291
+ RNSwitchFiles.forEach(function (onefile) {
292
+ if (fs.existsSync(onefile)) {
293
+ // 读取文件内容
294
+ const fileContent = fs.readFileSync(onefile, 'utf8');
295
+ if (reset) {
296
+ // 未被 hook 过代码,不需要处理
297
+ if (fileContent.indexOf(`${QTHookTag}`) === -1) {
298
+ return;
299
+ }
300
+ // 检查备份文件是否存在
301
+ const backFilePath = `${onefile}${QTBackUpSuffix}`;
302
+ if (!fs.existsSync(backFilePath)) {
303
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
304
+ }
305
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名文件
306
+ fs.renameSync(backFilePath, onefile);
307
+ console.log(`found and reset Switch.js: ${onefile}`);
308
+ } else {
309
+ // 已经 hook 过了,不需要再次 hook
310
+ if (fileContent.indexOf(`${QTHookTag}`) > -1) {
311
+ return;
312
+ }
313
+ console.log(`found Switch.js: ${onefile}`);
314
+ // 特殊情况的单独插入
315
+ // if (this.props.onValueChange != null) {
316
+ let scriptStr = 'if (this.props.onValueChange != null) {';
317
+ let hookIndex = fileContent.indexOf(scriptStr);
318
+ if (hookIndex > -1) {
319
+ // 插入 hook 代码
320
+ const hookedContent = `${fileContent.substring(
321
+ 0,
322
+ hookIndex
323
+ )}\n${QTSwitchHookCode}\n${fileContent.substring(hookIndex)}`;
324
+ // 备份源文件
325
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
326
+ // 重写文件
327
+ fs.writeFileSync(onefile, hookedContent, 'utf8');
328
+ console.log(`modify Switch.js: ${onefile}`);
329
+ } else {
330
+ // 获取 hook 的代码插入的位置
331
+ scriptStr = 'this.props.onValueChange(event.nativeEvent.value);';
332
+ hookIndex = fileContent.indexOf(scriptStr);
333
+ let hookcontent;
334
+ if (hookIndex === -1) {
335
+ scriptStr = 'onValueChange?.(event.nativeEvent.value);';
336
+ hookIndex = fileContent.indexOf(scriptStr);
337
+ hookcontent = QTSwitchHookCode66;
338
+ } else {
339
+ hookcontent = QTSwitchHookCode;
340
+ }
341
+ // 判断文件是否异常,不存在 touchableHandlePress 方法,导致无法 hook 点击事件
342
+ if (hookIndex === -1) {
343
+ throw "Can't not find onValueChange function";
344
+ }
345
+ // 插入 hook 代码
346
+ const hookedContent = `${fileContent.substring(
347
+ 0,
348
+ hookIndex + scriptStr.length
349
+ )}\n${hookcontent}\n${fileContent.substring(
350
+ hookIndex + scriptStr.length
351
+ )}`;
352
+ // 备份源文件
353
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
354
+ // 重写文件
355
+ fs.writeFileSync(onefile, hookedContent, 'utf8');
356
+ console.log(`modify Switch.js succeed`);
357
+ }
358
+ }
359
+ }
360
+ });
361
+ }
362
+ // hook SegmentedControl
363
+ function QTHookSegmentedControlRN(reset = false) {
364
+ RNSegmentedControlFilePath.forEach(function (onefile) {
365
+ if (fs.existsSync(onefile)) {
366
+ // 读取文件内容
367
+ const fileContent = fs.readFileSync(onefile, ChartSet_UTF8);
368
+ if (reset) {
369
+ // 未被 hook 过代码,不需要处理
370
+ if (fileContent.indexOf(`${QTHookTag}`) === -1) {
371
+ return;
372
+ }
373
+ // 检查备份文件是否存在
374
+ const backFilePath = `${onefile}${QTBackUpSuffix}`;
375
+ if (!fs.existsSync(backFilePath)) {
376
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
377
+ }
378
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名文件
379
+ fs.renameSync(backFilePath, onefile);
380
+ console.log(`found and reset SegmentedControl.js: ${onefile}`);
381
+ } else {
382
+ // 已经 hook 过了,不需要再次 hook
383
+ if (fileContent.indexOf(`${QTHookTag}`) > -1) {
384
+ return;
385
+ }
386
+ console.log(`found SegmentedControl.js: ${onefile}`);
387
+ // 获取 hook 的代码插入的位置
388
+ const scriptStr = 'this.props.onValueChange(event.nativeEvent.value);';
389
+ const hookIndex = fileContent.indexOf(scriptStr);
390
+ // 判断文件是否异常,不存在 touchableHandlePress 方法,导致无法 hook 点击事件
391
+ if (hookIndex === -1) {
392
+ throw "Can't not find onValueChange function";
393
+ }
394
+ // 插入 hook 代码
395
+ const hookedContent = `${fileContent.substring(
396
+ 0,
397
+ hookIndex + scriptStr.length
398
+ )}\n${QTSegmentedControlHookCode}\n${fileContent.substring(
399
+ hookIndex + scriptStr.length
400
+ )}`;
401
+ // 备份 Touchable.js 源文件
402
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
403
+ // 重写 Touchable.js 文件
404
+ fs.writeFileSync(onefile, hookedContent, ChartSet_UTF8);
405
+ console.log(`modify SegmentedControl.js succeed`);
406
+ }
407
+ }
408
+ });
409
+ }
410
+
411
+ // hook GestureButtons
412
+ function QTHookGestureButtonsRN(reset = false) {
413
+ RNGestureButtonsFilePaths.forEach(function (onefile) {
414
+ if (fs.existsSync(onefile)) {
415
+ // 读取文件内容
416
+ const fileContent = fs.readFileSync(onefile, ChartSet_UTF8);
417
+ if (reset) {
418
+ // 未被 hook 过代码,不需要处理
419
+ if (fileContent.indexOf(`${QTHookTag}`) === -1) {
420
+ return;
421
+ }
422
+ // 检查备份文件是否存在
423
+ const backFilePath = `${onefile}${QTBackUpSuffix}`;
424
+ if (!fs.existsSync(backFilePath)) {
425
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
426
+ }
427
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名文件
428
+ fs.renameSync(backFilePath, onefile);
429
+ console.log(`found and reset GestureButtons: ${onefile}`);
430
+ } else {
431
+ // 已经 hook 过了,不需要再次 hook
432
+ if (fileContent.indexOf(`${QTHookTag}`) > -1) {
433
+ return;
434
+ }
435
+ console.log(`found GestureButtons: ${onefile}`);
436
+ // 获取 hook 的代码插入的位置
437
+ const scriptStr = 'this.props.onPress(active);';
438
+ const hookIndex = fileContent.indexOf(scriptStr);
439
+ // 判断文件是否异常,不存在 this.props.onPress(active); 导致无法 hook 点击事件
440
+ if (hookIndex === -1) {
441
+ throw "Can't not find this.props.onPress(active); ";
442
+ }
443
+ // 插入 hook 代码
444
+ const hookedContent = `${fileContent.substring(
445
+ 0,
446
+ hookIndex + scriptStr.length
447
+ )}\n${QTClickTouchableHookCode}\n${fileContent.substring(
448
+ hookIndex + scriptStr.length
449
+ )}`;
450
+ // 备份目标源文件
451
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
452
+ // 重写修改后的文件
453
+ fs.writeFileSync(onefile, hookedContent, ChartSet_UTF8);
454
+ console.log(`modify GestureButtons succeed`);
455
+ }
456
+ }
457
+ });
458
+ }
459
+
460
+ // hook clickable
461
+ function QTHookClickableRN(reset = false) {
462
+ RNClickableFiles.forEach(function (onefile) {
463
+ if (fs.existsSync(onefile)) {
464
+ if (reset) {
465
+ // 读取文件内容
466
+ const fileContent = fs.readFileSync(onefile, ChartSet_UTF8);
467
+ // 未被 hook 过代码,不需要处理
468
+ if (fileContent.indexOf(`${QTHookTag}`) === -1) {
469
+ return;
470
+ }
471
+ // 检查备份文件是否存在
472
+ const backFilePath = `${onefile}${QTBackUpSuffix}`;
473
+ if (!fs.existsSync(backFilePath)) {
474
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
475
+ }
476
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名文件
477
+ fs.renameSync(backFilePath, onefile);
478
+ console.log(`found and reset clickable: ${onefile}`);
479
+ } else {
480
+ // 读取文件内容
481
+ const content = fs.readFileSync(onefile, 'utf8');
482
+ // 已经 hook 过了,不需要再次 hook
483
+ if (content.indexOf(`${QTHookTag}`) > -1) {
484
+ return;
485
+ }
486
+ console.log(`found clickable.js: ${onefile}`);
487
+ // 获取 hook 的代码插入的位置
488
+ let objRe =
489
+ /ReactNativePrivateInterface\.UIManager\.createView\([\s\S]{1,60}\.uiViewClassName,[\s\S]*?\)[,;]/;
490
+ let match = objRe.exec(content);
491
+ if (!match) {
492
+ objRe =
493
+ /UIManager\.createView\([\s\S]{1,60}\.uiViewClassName,[\s\S]*?\)[,;]/;
494
+ match = objRe.exec(content);
495
+ }
496
+ if (!match) {
497
+ throw "can't inject clickable js";
498
+ }
499
+ const lastParentheses = content.lastIndexOf(')', match.index);
500
+ const nextCommaIndex = content.indexOf(',', match.index);
501
+ if (nextCommaIndex === -1)
502
+ throw "can't inject clickable js, and nextCommaIndex is -1";
503
+ const tagName = lastArgumentName(content, nextCommaIndex).trim();
504
+ const functionBody = `
505
+ var qtElement;
506
+ if(typeof internalInstanceHandle !== 'undefined') {
507
+ qtElement = internalInstanceHandle;
508
+ } else if(typeof workInProgress !== 'undefined') {
509
+ qtElement = workInProgress;
510
+ } else if(typeof thatThis._currentElement !== 'undefined') {
511
+ qtElement = thatThis._currentElement;
512
+ }
513
+ var eachProgress = function (workInProgress) {
514
+ if(workInProgress == null){
515
+ return;
516
+ }
517
+ var props;
518
+ if(workInProgress.memoizedProps) {
519
+ props = workInProgress.memoizedProps;
520
+ } else if(workInProgress.props){
521
+ props = workInProgress.props;
522
+ }
523
+ if(props && props.qtParams) {
524
+ return props.qtParams;
525
+ } else {
526
+ if(!props ||
527
+ !workInProgress.type ||
528
+ workInProgress.type.displayName === 'TouchableOpacity' ||
529
+ workInProgress.type.displayName === 'TouchableHighlight' ||
530
+ workInProgress.type.displayName === 'TouchableWithoutFeedback'||
531
+ workInProgress.type.displayName === 'TouchableNativeFeedback'||
532
+ workInProgress.type.displayName === 'Pressable'||
533
+ workInProgress.type.name === 'TouchableOpacity' ||
534
+ workInProgress.type.name === 'TouchableHighlight' ||
535
+ workInProgress.type.name === 'TouchableNativeFeedback'||
536
+ workInProgress.type.name === 'TouchableWithoutFeedback'||
537
+ workInProgress.type.displayName === undefined||
538
+ workInProgress.type.name === undefined ||
539
+ !props.onPress
540
+ ) {
541
+ if(workInProgress.return) {
542
+ return eachProgress(workInProgress.return);
543
+ } else {
544
+ if(workInProgress._owner && workInProgress._owner._currentElement) {
545
+ return eachProgress(workInProgress._owner._currentElement);
546
+ } else {
547
+ return eachProgress(workInProgress._owner);
548
+ }
549
+ }
550
+ }
551
+ }
552
+ };
553
+ var elementProps;
554
+ if(qtElement && qtElement.memoizedProps) {
555
+ elementProps = qtElement.memoizedProps;
556
+ } else if(qtElement && qtElement.props) {
557
+ elementProps = qtElement.props;
558
+ }
559
+ if(elementProps) {
560
+ // iOS 兼容 SegmentedControl 逻辑
561
+ var isSegmentedControl = (
562
+ qtElement && (
563
+ qtElement.type === 'RNCSegmentedControl' ||
564
+ qtElement.type === 'RCTSegmentedControl' ||
565
+ qtElement.type.name === 'RNCSegmentedControl' ||
566
+ qtElement.type.name === 'RCTSegmentedControl' ||
567
+ qtElement.type.displayName === 'RNCSegmentedControl' ||
568
+ qtElement.type.displayName === 'RCTSegmentedControl'
569
+ )
570
+ );
571
+ if(elementProps.onStartShouldSetResponder || isSegmentedControl) {
572
+ var qtProps = eachProgress(qtElement);
573
+ var ReactNative = require('react-native');
574
+
575
+ var qtModule = ReactNative.NativeModules.QuicktrackingAnalyticsModule;
576
+ if(qtModule && qtModule.saveRootViewProperties) {
577
+ var qtRootTag;
578
+ if(typeof nativeTopRootTag !== 'undefined') {
579
+ qtRootTag = nativeTopRootTag;
580
+ } else if(typeof rootContainerInstance !== 'undefined') {
581
+ qtRootTag = rootContainerInstance;
582
+ } else if(typeof renderExpirationTime !== 'undefined') {
583
+ qtRootTag = renderExpirationTime;
584
+ } else if(typeof renderLanes !== 'undefined') {
585
+ qtRootTag = renderLanes;
586
+ }
587
+ if (qtRootTag && (typeof qtRootTag === 'number')) {
588
+ qtModule.saveRootViewProperties(${tagName}, true , qtProps, qtRootTag);
589
+ return;
590
+ }
591
+ }
592
+ qtModule && qtModule.saveViewProperties && qtModule.saveViewProperties(${tagName}, true, qtProps);
593
+ }
594
+ }`;
595
+ const call = addTryCatch(functionBody);
596
+ const lastReturn = content.lastIndexOf('return', match.index);
597
+ let splitIndex = match.index;
598
+ if (lastReturn > lastParentheses) {
599
+ splitIndex = lastReturn;
600
+ }
601
+ const hookedContent = `${content.substring(
602
+ 0,
603
+ splitIndex
604
+ )}\n${call}\n${content.substring(splitIndex)}`;
605
+
606
+ // 备份源文件
607
+ fs.renameSync(onefile, `${onefile}${QTBackUpSuffix}`);
608
+ // 重写文件
609
+ fs.writeFileSync(onefile, hookedContent, ChartSet_UTF8);
610
+ console.log(`modify clickable.js succeed`);
611
+ }
612
+ }
613
+ });
614
+ }
615
+
616
+ function resetQTHookCodeRN(resetFilePath) {
617
+ // 判断需要被恢复的文件是否存在
618
+ if (!fs.existsSync(resetFilePath)) {
619
+ return;
620
+ }
621
+ const fileContent = fs.readFileSync(resetFilePath, 'utf8');
622
+ // 未被 hook 过代码,不需要处理
623
+ if (fileContent.indexOf(`${QTHookTag}`) === -1) {
624
+ return;
625
+ }
626
+ // 检查备份文件是否存在
627
+ const backFilePath = `${resetFilePath}${QTBackUpSuffix}`;
628
+ if (!fs.existsSync(backFilePath)) {
629
+ throw `File: ${backFilePath} not found, Please rm -rf node_modules and npm install again`;
630
+ }
631
+ // 将备份文件重命名恢复 + 自动覆盖被 hook 过的同名 Touchable.js 文件
632
+ fs.renameSync(backFilePath, resetFilePath);
633
+ console.log(`found and reset file: ${resetFilePath}`);
634
+ }
635
+
636
+ // hook all auto click event entry
637
+ function QTHookClickEventRN() {
638
+ QTTouchableHookRN(RNClickTouchableFilePath);
639
+ QTHookClickableRN();
640
+ QTHookSliderRN();
641
+ QTHookSegmentedControlRN();
642
+ QTHookGestureButtonsRN();
643
+ QTPressableHookRN();
644
+ }
645
+
646
+ function resetAllQTHookFiles() {
647
+ resetQTHookCodeRN(RNClickTouchableFilePath);
648
+
649
+ QTHookClickableRN(true);
650
+
651
+ QTHookSliderRN(true);
652
+ QTHookSwitchRN(true);
653
+ QTHookSegmentedControlRN(true);
654
+ QTHookGestureButtonsRN(true);
655
+
656
+ resetQTHookCodeRN(RNClickPressabilityFilePath);
657
+ }
658
+
659
+ // hook all files entry
660
+ function QTHookAllFiles() {
661
+ if (!enableAutoCLK) {
662
+ console.log('customers ignore auto click event');
663
+ } else {
664
+ QTHookClickEventRN();
665
+ }
666
+ }
667
+
668
+ // 命令行
669
+ switch (process.argv[2]) {
670
+ case '-run':
671
+ resetAllQTHookFiles();
672
+ QTHookAllFiles();
673
+ break;
674
+ case '-reset':
675
+ resetAllQTHookFiles();
676
+ break;
677
+ default:
678
+ console.log('can not find this options: ' + process.argv[2]);
679
+ }
package/HISTORY.md DELETED
@@ -1,17 +0,0 @@
1
- ### v1.0.4
2
- * fix: 修复iOS不能设置页面属性的问题
3
-
4
- ### v1.0.3
5
- * feat: 添加了页面属性上传的埋点API uploadPageProperties
6
- * chore: Android底层SDK版本升级至 => 1.4.0.PX
7
- * chore: iOS底层SDK版本升级至 => 1.3.9.PX
8
-
9
- ### v1.0.2
10
- * fix: 初始化接口中隐藏了preInit
11
- * feat: 自定义事件支持传入事件编码
12
-
13
- ### v1.0.1
14
- * feat: 支持初始化接口
15
-
16
- ### v1.0.0
17
- * feat: 基于Android、iOS封装了常见埋点API,利用React Native的native module能力