@truewatchtech/cocos-sdk 0.1.0-alpha.4

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 (200) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +31 -0
  3. package/THIRD_PARTY_NOTICES +5 -0
  4. package/bin/cli.cjs +138 -0
  5. package/components/creator2/ReplayPrivacy.ts +23 -0
  6. package/components/creator3/ReplayPrivacy.ts +25 -0
  7. package/creator2/package.json +4 -0
  8. package/creator3/package.json +5 -0
  9. package/dist/creator2/core/auto-tracking.d.ts +35 -0
  10. package/dist/creator2/core/auto-tracking.d.ts.map +1 -0
  11. package/dist/creator2/core/auto-tracking.js +337 -0
  12. package/dist/creator2/core/auto-tracking.js.map +1 -0
  13. package/dist/creator2/core/client.d.ts +74 -0
  14. package/dist/creator2/core/client.d.ts.map +1 -0
  15. package/dist/creator2/core/client.js +176 -0
  16. package/dist/creator2/core/client.js.map +1 -0
  17. package/dist/creator2/core/index.d.ts +3 -0
  18. package/dist/creator2/core/index.d.ts.map +1 -0
  19. package/dist/creator2/core/index.js +6 -0
  20. package/dist/creator2/core/index.js.map +1 -0
  21. package/dist/creator2/core/modules.d.ts +159 -0
  22. package/dist/creator2/core/modules.d.ts.map +1 -0
  23. package/dist/creator2/core/modules.js +319 -0
  24. package/dist/creator2/core/modules.js.map +1 -0
  25. package/dist/creator2/core/replay-async-transport.d.ts +4 -0
  26. package/dist/creator2/core/replay-async-transport.d.ts.map +1 -0
  27. package/dist/creator2/core/replay-async-transport.js +67 -0
  28. package/dist/creator2/core/replay-async-transport.js.map +1 -0
  29. package/dist/creator2/core/replay-budget.d.ts +28 -0
  30. package/dist/creator2/core/replay-budget.d.ts.map +1 -0
  31. package/dist/creator2/core/replay-budget.js +113 -0
  32. package/dist/creator2/core/replay-budget.js.map +1 -0
  33. package/dist/creator2/core/replay-diagnostics.d.ts +36 -0
  34. package/dist/creator2/core/replay-diagnostics.d.ts.map +1 -0
  35. package/dist/creator2/core/replay-diagnostics.js +40 -0
  36. package/dist/creator2/core/replay-diagnostics.js.map +1 -0
  37. package/dist/creator2/core/replay-encoding.d.ts +31 -0
  38. package/dist/creator2/core/replay-encoding.d.ts.map +1 -0
  39. package/dist/creator2/core/replay-encoding.js +63 -0
  40. package/dist/creator2/core/replay-encoding.js.map +1 -0
  41. package/dist/creator2/core/replay-file.d.ts +5 -0
  42. package/dist/creator2/core/replay-file.d.ts.map +1 -0
  43. package/dist/creator2/core/replay-file.js +101 -0
  44. package/dist/creator2/core/replay-file.js.map +1 -0
  45. package/dist/creator2/core/replay-pixels.d.ts +3 -0
  46. package/dist/creator2/core/replay-pixels.d.ts.map +1 -0
  47. package/dist/creator2/core/replay-pixels.js +16 -0
  48. package/dist/creator2/core/replay-pixels.js.map +1 -0
  49. package/dist/creator2/core/replay-privacy-nodes.d.ts +14 -0
  50. package/dist/creator2/core/replay-privacy-nodes.d.ts.map +1 -0
  51. package/dist/creator2/core/replay-privacy-nodes.js +18 -0
  52. package/dist/creator2/core/replay-privacy-nodes.js.map +1 -0
  53. package/dist/creator2/core/replay-privacy.d.ts +15 -0
  54. package/dist/creator2/core/replay-privacy.d.ts.map +1 -0
  55. package/dist/creator2/core/replay-privacy.js +36 -0
  56. package/dist/creator2/core/replay-privacy.js.map +1 -0
  57. package/dist/creator2/core/replay.d.ts +99 -0
  58. package/dist/creator2/core/replay.d.ts.map +1 -0
  59. package/dist/creator2/core/replay.js +611 -0
  60. package/dist/creator2/core/replay.js.map +1 -0
  61. package/dist/creator2/core/transport.d.ts +17 -0
  62. package/dist/creator2/core/transport.d.ts.map +1 -0
  63. package/dist/creator2/core/transport.js +33 -0
  64. package/dist/creator2/core/transport.js.map +1 -0
  65. package/dist/creator2/core/types.d.ts +260 -0
  66. package/dist/creator2/core/types.d.ts.map +1 -0
  67. package/dist/creator2/core/types.js +3 -0
  68. package/dist/creator2/core/types.js.map +1 -0
  69. package/dist/creator2/core/validation.d.ts +14 -0
  70. package/dist/creator2/core/validation.d.ts.map +1 -0
  71. package/dist/creator2/core/validation.js +81 -0
  72. package/dist/creator2/core/validation.js.map +1 -0
  73. package/dist/creator2/core/version.d.ts +9 -0
  74. package/dist/creator2/core/version.d.ts.map +1 -0
  75. package/dist/creator2/core/version.js +14 -0
  76. package/dist/creator2/core/version.js.map +1 -0
  77. package/dist/creator2/creator2/auto.d.ts +9 -0
  78. package/dist/creator2/creator2/auto.d.ts.map +1 -0
  79. package/dist/creator2/creator2/auto.js +65 -0
  80. package/dist/creator2/creator2/auto.js.map +1 -0
  81. package/dist/creator2/creator2/capture.d.ts +17 -0
  82. package/dist/creator2/creator2/capture.d.ts.map +1 -0
  83. package/dist/creator2/creator2/capture.js +168 -0
  84. package/dist/creator2/creator2/capture.js.map +1 -0
  85. package/dist/creator2/creator2/index.d.ts +12 -0
  86. package/dist/creator2/creator2/index.d.ts.map +1 -0
  87. package/dist/creator2/creator2/index.js +41 -0
  88. package/dist/creator2/creator2/index.js.map +1 -0
  89. package/dist/creator2/creator2/transport.d.ts +8 -0
  90. package/dist/creator2/creator2/transport.d.ts.map +1 -0
  91. package/dist/creator2/creator2/transport.js +38 -0
  92. package/dist/creator2/creator2/transport.js.map +1 -0
  93. package/dist/creator3/core/auto-tracking.d.ts +35 -0
  94. package/dist/creator3/core/auto-tracking.d.ts.map +1 -0
  95. package/dist/creator3/core/auto-tracking.js +247 -0
  96. package/dist/creator3/core/auto-tracking.js.map +1 -0
  97. package/dist/creator3/core/client.d.ts +74 -0
  98. package/dist/creator3/core/client.d.ts.map +1 -0
  99. package/dist/creator3/core/client.js +158 -0
  100. package/dist/creator3/core/client.js.map +1 -0
  101. package/dist/creator3/core/index.d.ts +3 -0
  102. package/dist/creator3/core/index.d.ts.map +1 -0
  103. package/dist/creator3/core/index.js +2 -0
  104. package/dist/creator3/core/index.js.map +1 -0
  105. package/dist/creator3/core/modules.d.ts +159 -0
  106. package/dist/creator3/core/modules.d.ts.map +1 -0
  107. package/dist/creator3/core/modules.js +293 -0
  108. package/dist/creator3/core/modules.js.map +1 -0
  109. package/dist/creator3/core/replay-async-transport.d.ts +4 -0
  110. package/dist/creator3/core/replay-async-transport.d.ts.map +1 -0
  111. package/dist/creator3/core/replay-async-transport.js +16 -0
  112. package/dist/creator3/core/replay-async-transport.js.map +1 -0
  113. package/dist/creator3/core/replay-budget.d.ts +28 -0
  114. package/dist/creator3/core/replay-budget.d.ts.map +1 -0
  115. package/dist/creator3/core/replay-budget.js +105 -0
  116. package/dist/creator3/core/replay-budget.js.map +1 -0
  117. package/dist/creator3/core/replay-diagnostics.d.ts +36 -0
  118. package/dist/creator3/core/replay-diagnostics.d.ts.map +1 -0
  119. package/dist/creator3/core/replay-diagnostics.js +36 -0
  120. package/dist/creator3/core/replay-diagnostics.js.map +1 -0
  121. package/dist/creator3/core/replay-encoding.d.ts +31 -0
  122. package/dist/creator3/core/replay-encoding.d.ts.map +1 -0
  123. package/dist/creator3/core/replay-encoding.js +62 -0
  124. package/dist/creator3/core/replay-encoding.js.map +1 -0
  125. package/dist/creator3/core/replay-file.d.ts +5 -0
  126. package/dist/creator3/core/replay-file.d.ts.map +1 -0
  127. package/dist/creator3/core/replay-file.js +31 -0
  128. package/dist/creator3/core/replay-file.js.map +1 -0
  129. package/dist/creator3/core/replay-pixels.d.ts +3 -0
  130. package/dist/creator3/core/replay-pixels.d.ts.map +1 -0
  131. package/dist/creator3/core/replay-pixels.js +13 -0
  132. package/dist/creator3/core/replay-pixels.js.map +1 -0
  133. package/dist/creator3/core/replay-privacy-nodes.d.ts +14 -0
  134. package/dist/creator3/core/replay-privacy-nodes.d.ts.map +1 -0
  135. package/dist/creator3/core/replay-privacy-nodes.js +15 -0
  136. package/dist/creator3/core/replay-privacy-nodes.js.map +1 -0
  137. package/dist/creator3/core/replay-privacy.d.ts +15 -0
  138. package/dist/creator3/core/replay-privacy.d.ts.map +1 -0
  139. package/dist/creator3/core/replay-privacy.js +33 -0
  140. package/dist/creator3/core/replay-privacy.js.map +1 -0
  141. package/dist/creator3/core/replay.d.ts +99 -0
  142. package/dist/creator3/core/replay.d.ts.map +1 -0
  143. package/dist/creator3/core/replay.js +534 -0
  144. package/dist/creator3/core/replay.js.map +1 -0
  145. package/dist/creator3/core/transport.d.ts +17 -0
  146. package/dist/creator3/core/transport.d.ts.map +1 -0
  147. package/dist/creator3/core/transport.js +27 -0
  148. package/dist/creator3/core/transport.js.map +1 -0
  149. package/dist/creator3/core/types.d.ts +260 -0
  150. package/dist/creator3/core/types.d.ts.map +1 -0
  151. package/dist/creator3/core/types.js +2 -0
  152. package/dist/creator3/core/types.js.map +1 -0
  153. package/dist/creator3/core/validation.d.ts +14 -0
  154. package/dist/creator3/core/validation.d.ts.map +1 -0
  155. package/dist/creator3/core/validation.js +75 -0
  156. package/dist/creator3/core/validation.js.map +1 -0
  157. package/dist/creator3/core/version.d.ts +9 -0
  158. package/dist/creator3/core/version.d.ts.map +1 -0
  159. package/dist/creator3/core/version.js +11 -0
  160. package/dist/creator3/core/version.js.map +1 -0
  161. package/dist/creator3/creator3/auto.d.ts +9 -0
  162. package/dist/creator3/creator3/auto.d.ts.map +1 -0
  163. package/dist/creator3/creator3/auto.js +73 -0
  164. package/dist/creator3/creator3/auto.js.map +1 -0
  165. package/dist/creator3/creator3/capture.d.ts +17 -0
  166. package/dist/creator3/creator3/capture.d.ts.map +1 -0
  167. package/dist/creator3/creator3/capture.js +112 -0
  168. package/dist/creator3/creator3/capture.js.map +1 -0
  169. package/dist/creator3/creator3/index.d.ts +12 -0
  170. package/dist/creator3/creator3/index.d.ts.map +1 -0
  171. package/dist/creator3/creator3/index.js +23 -0
  172. package/dist/creator3/creator3/index.js.map +1 -0
  173. package/dist/creator3/creator3/replay-render-cycle.d.ts +9 -0
  174. package/dist/creator3/creator3/replay-render-cycle.d.ts.map +1 -0
  175. package/dist/creator3/creator3/replay-render-cycle.js +12 -0
  176. package/dist/creator3/creator3/replay-render-cycle.js.map +1 -0
  177. package/dist/creator3/creator3/transport.d.ts +9 -0
  178. package/dist/creator3/creator3/transport.d.ts.map +1 -0
  179. package/dist/creator3/creator3/transport.js +28 -0
  180. package/dist/creator3/creator3/transport.js.map +1 -0
  181. package/dist/creator3/package.json +1 -0
  182. package/extensions/creator2/install-native.cjs +385 -0
  183. package/extensions/creator2/main.js +23 -0
  184. package/extensions/creator2/package.json +6 -0
  185. package/extensions/creator3/dist/builder.js +9 -0
  186. package/extensions/creator3/dist/hooks.js +70 -0
  187. package/extensions/creator3/dist/main.js +4 -0
  188. package/extensions/creator3/install-native.cjs +385 -0
  189. package/extensions/creator3/package.json +10 -0
  190. package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosBridge.java +536 -0
  191. package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosBridgeProvider.java +70 -0
  192. package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosReplayImageJobs.java +55 -0
  193. package/native/ios/FTCocosBridge.h +10 -0
  194. package/native/ios/FTCocosBridge.m +487 -0
  195. package/native/ios/FTCocosBridge.podspec +18 -0
  196. package/native/ios/FTCocosReplayImageJobs.h +11 -0
  197. package/native/ios/FTCocosReplayImageJobs.m +54 -0
  198. package/native/shared/FTReplayFileBridge.h +62 -0
  199. package/native/shared/FTReplayFileWorker.h +88 -0
  200. package/package.json +61 -0
@@ -0,0 +1,487 @@
1
+ #import "FTCocosBridge.h"
2
+ #import "FTCocosReplayImageJobs.h"
3
+
4
+ #import "FTMobileSDK.h"
5
+ #import "FTSessionReplay.h"
6
+ #import <UIKit/UIKit.h>
7
+ #import <objc/message.h>
8
+
9
+ @implementation FTCocosBridge
10
+
11
+ + (NSString *)invoke:(NSString *)method payload:(NSString *)payload {
12
+ @try {
13
+ NSData *data = [payload dataUsingEncoding:NSUTF8StringEncoding];
14
+ NSDictionary *arguments = data.length > 0
15
+ ? [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]
16
+ : @{};
17
+ if (![arguments isKindOfClass:NSDictionary.class]) {
18
+ return [self responseWithValue:nil error:@"Payload must be a JSON object"];
19
+ }
20
+ id value = [self dispatch:method arguments:arguments];
21
+ return [self responseWithValue:value error:nil];
22
+ } @catch (NSException *exception) {
23
+ return [self responseWithValue:nil error:[NSString stringWithFormat:@"%@: %@", exception.name, exception.reason ?: @"unknown error"]];
24
+ }
25
+ }
26
+
27
+ + (id)dispatch:(NSString *)method arguments:(NSDictionary *)arguments {
28
+ if ([method isEqualToString:@"replay.beginSaveImage"]) return [self beginSaveImage:arguments];
29
+ if ([method isEqualToString:@"replay.pollSaveImage"]) {
30
+ return [FTCocosReplayImageJobs poll:[self requiredText:arguments key:@"job"]];
31
+ }
32
+ FTExternalDataManager *rum = [FTExternalDataManager sharedManager];
33
+ if ([method isEqualToString:@"hybrid.attach"]) {
34
+ [self ensureNativeHostInitialized];
35
+ NSString *sdkVersion = [self text:arguments[@"sdkVersion"]];
36
+ if (sdkVersion.length > 0) {
37
+ [FTMobileAgent appendGlobalContext:@{ @"sdk_package_cocos": sdkVersion }];
38
+ }
39
+ if ([arguments[@"requiresReplay"] boolValue]) [self ensureHybridRecorderSwitch];
40
+ return nil;
41
+ }
42
+ if ([method isEqualToString:@"hybrid.setExternalRecorderActive"]) {
43
+ [self ensureNativeHostInitialized];
44
+ [self setExternalRecorderActive:[arguments[@"active"] boolValue]];
45
+ return nil;
46
+ }
47
+ if ([method isEqualToString:@"sdk.configure"]) {
48
+ NSString *datakit = [self text:arguments[@"datakitUrl"]];
49
+ NSString *dataway = [self text:arguments[@"datawayUrl"]];
50
+ NSString *token = [self text:arguments[@"clientToken"]];
51
+ FTSDKConfig *config;
52
+ if (datakit.length > 0) {
53
+ config = [[FTSDKConfig alloc] initWithDatakitUrl:datakit];
54
+ } else if (dataway.length > 0 && token.length > 0) {
55
+ config = [[FTSDKConfig alloc] initWithDatawayUrl:dataway clientToken:token];
56
+ } else {
57
+ [self fail:@"datakitUrl or datawayUrl/clientToken is required"];
58
+ }
59
+ if (arguments[@"debug"]) config.enableSDKDebugLog = [arguments[@"debug"] boolValue];
60
+ if ([self text:arguments[@"env"]].length > 0) config.env = arguments[@"env"];
61
+ if ([self text:arguments[@"serviceName"]].length > 0) config.service = arguments[@"serviceName"];
62
+ if ([arguments[@"globalContext"] isKindOfClass:NSDictionary.class]) config.globalContext = arguments[@"globalContext"];
63
+ [FTMobileAgent startWithConfigOptions:config];
64
+ return nil;
65
+ }
66
+ if ([method isEqualToString:@"sdk.bindUser"]) {
67
+ [[FTMobileAgent sharedInstance]
68
+ bindUserWithUserID:[self requiredText:arguments key:@"userId"]
69
+ userName:[self text:arguments[@"userName"]]
70
+ userEmail:[self text:arguments[@"userEmail"]]
71
+ extra:[self dictionary:arguments[@"extra"]]];
72
+ return nil;
73
+ }
74
+ if ([method isEqualToString:@"sdk.unbindUser"]) {
75
+ [[FTMobileAgent sharedInstance] unbindUser];
76
+ return nil;
77
+ }
78
+ if ([method isEqualToString:@"sdk.shutdown"]) {
79
+ [FTMobileAgent shutDown];
80
+ return nil;
81
+ }
82
+ if ([method isEqualToString:@"rum.configure"]) {
83
+ NSString *appID = [self text:arguments[@"iosAppId"]];
84
+ if (appID.length == 0) [self fail:@"rum.iosAppId is required on iOS"];
85
+ FTRumConfig *config = [[FTRumConfig alloc] initWithAppid:appID];
86
+ if (arguments[@"sampleRate"]) config.sampleRate = [self percent:arguments[@"sampleRate"]];
87
+ if (arguments[@"sessionOnErrorSampleRate"]) config.sessionOnErrorSampleRate = [self percent:arguments[@"sessionOnErrorSampleRate"]];
88
+ if (arguments[@"enableNativeUserAction"]) config.enableTraceUserAction = [arguments[@"enableNativeUserAction"] boolValue];
89
+ if (arguments[@"enableNativeUserView"]) config.enableTraceUserView = [arguments[@"enableNativeUserView"] boolValue];
90
+ if (arguments[@"enableNativeUserResource"]) config.enableTraceUserResource = [arguments[@"enableNativeUserResource"] boolValue];
91
+ if (arguments[@"enableNativeCrash"]) config.enableTrackAppCrash = [arguments[@"enableNativeCrash"] boolValue];
92
+ if (arguments[@"enableNativeAnr"]) config.enableTrackAppANR = [arguments[@"enableNativeAnr"] boolValue];
93
+ if (arguments[@"enableNativeUiBlock"]) {
94
+ [config setEnableTrackAppFreeze:[arguments[@"enableNativeUiBlock"] boolValue]
95
+ freezeDurationMs:[arguments[@"nativeUiBlockDurationMs"] longValue] ?: 250];
96
+ }
97
+ if (arguments[@"errorMonitorType"]) config.errorMonitorType = [arguments[@"errorMonitorType"] unsignedIntegerValue];
98
+ if (arguments[@"deviceMetricsMonitorType"]) config.deviceMetricsMonitorType = [arguments[@"deviceMetricsMonitorType"] unsignedIntegerValue];
99
+ config.monitorFrequency = [self monitorFrequency:[self text:arguments[@"detectFrequency"]]];
100
+ if ([arguments[@"globalContext"] isKindOfClass:NSDictionary.class]) config.globalContext = arguments[@"globalContext"];
101
+ [[FTMobileAgent sharedInstance] startRumWithConfigOptions:config];
102
+ return nil;
103
+ }
104
+ if ([method isEqualToString:@"rum.startView"]) {
105
+ [rum startViewWithName:[self requiredText:arguments key:@"name"] property:[self dictionary:arguments[@"attributes"]]];
106
+ return nil;
107
+ }
108
+ if ([method isEqualToString:@"rum.stopView"]) {
109
+ [rum stopViewWithProperty:[self dictionary:arguments[@"attributes"]]];
110
+ return nil;
111
+ }
112
+ if ([method isEqualToString:@"rum.addAction"]) {
113
+ [rum addAction:[self requiredText:arguments key:@"name"]
114
+ actionType:[self text:arguments[@"type"]] ?: @"click"
115
+ property:[self dictionary:arguments[@"attributes"]]];
116
+ return nil;
117
+ }
118
+ if ([method isEqualToString:@"rum.startAction"]) {
119
+ [rum startAction:[self requiredText:arguments key:@"name"]
120
+ actionType:[self text:arguments[@"type"]] ?: @"click"
121
+ property:[self dictionary:arguments[@"attributes"]]];
122
+ return nil;
123
+ }
124
+ if ([method isEqualToString:@"rum.addError"]) {
125
+ [rum addErrorWithType:[self text:arguments[@"type"]] ?: @"cocos_error"
126
+ message:[self text:arguments[@"message"]] ?: @""
127
+ stack:[self text:arguments[@"stack"]] ?: @""
128
+ property:[self dictionary:arguments[@"attributes"]]];
129
+ return nil;
130
+ }
131
+ if ([method isEqualToString:@"rum.addLongTask"]) {
132
+ [rum addLongTaskWithStack:[self text:arguments[@"stack"]] ?: @""
133
+ duration:arguments[@"durationNs"] ?: @0
134
+ property:[self dictionary:arguments[@"attributes"]]];
135
+ return nil;
136
+ }
137
+ if ([method isEqualToString:@"rum.startResource"]) {
138
+ [rum startResourceWithKey:[self requiredText:arguments key:@"key"] property:[self dictionary:arguments[@"attributes"]]];
139
+ return nil;
140
+ }
141
+ if ([method isEqualToString:@"rum.stopResource"]) {
142
+ [rum stopResourceWithKey:[self requiredText:arguments key:@"key"] property:[self dictionary:arguments[@"attributes"]]];
143
+ return nil;
144
+ }
145
+ if ([method isEqualToString:@"rum.addResource"]) {
146
+ [self addResource:arguments manager:rum];
147
+ return nil;
148
+ }
149
+ if ([method isEqualToString:@"logger.configure"]) {
150
+ FTLoggerConfig *config = [[FTLoggerConfig alloc] init];
151
+ if (arguments[@"sampleRate"]) config.sampleRate = [self percent:arguments[@"sampleRate"]];
152
+ if (arguments[@"enableLinkRumData"]) config.enableLinkRumData = [arguments[@"enableLinkRumData"] boolValue];
153
+ if (arguments[@"enableCustomLog"]) config.enableCustomLog = [arguments[@"enableCustomLog"] boolValue];
154
+ if (arguments[@"printCustomLogToConsole"]) config.printCustomLogToConsole = [arguments[@"printCustomLogToConsole"] boolValue];
155
+ if (arguments[@"logCacheLimitCount"]) config.logCacheLimitCount = [arguments[@"logCacheLimitCount"] intValue];
156
+ config.discardType = [[self text:arguments[@"discardStrategy"]] isEqualToString:@"discardOldest"] ? FTDiscardOldest : FTDiscard;
157
+ if ([arguments[@"logLevelFilters"] isKindOfClass:NSArray.class]) config.logLevelFilter = [self logLevelFilters:arguments[@"logLevelFilters"]];
158
+ if ([arguments[@"globalContext"] isKindOfClass:NSDictionary.class]) config.globalContext = arguments[@"globalContext"];
159
+ [[FTMobileAgent sharedInstance] startLoggerWithConfigOptions:config];
160
+ return nil;
161
+ }
162
+ if ([method isEqualToString:@"logger.log"]) {
163
+ [[FTMobileAgent sharedInstance] logging:[self text:arguments[@"content"]] ?: @""
164
+ status:[self logStatus:[self text:arguments[@"level"]]]
165
+ property:[self dictionary:arguments[@"attributes"]]];
166
+ return nil;
167
+ }
168
+ if ([method isEqualToString:@"trace.configure"]) {
169
+ FTTraceConfig *config = [[FTTraceConfig alloc] init];
170
+ if (arguments[@"sampleRate"]) config.sampleRate = [self percent:arguments[@"sampleRate"]];
171
+ if (arguments[@"enableLinkRumData"]) config.enableLinkRumData = [arguments[@"enableLinkRumData"] boolValue];
172
+ if (arguments[@"enableNativeAutoTrace"]) config.enableAutoTrace = [arguments[@"enableNativeAutoTrace"] boolValue];
173
+ config.networkTraceType = [self traceType:[self text:arguments[@"traceType"]]];
174
+ [[FTMobileAgent sharedInstance] startTraceWithConfigOptions:config];
175
+ return nil;
176
+ }
177
+ if ([method isEqualToString:@"trace.getHeaders"]) {
178
+ NSURL *url = [NSURL URLWithString:[self requiredText:arguments key:@"url"]];
179
+ if (!url) [self fail:@"Invalid trace URL"];
180
+ NSString *key = [self text:arguments[@"resourceKey"]];
181
+ return key.length > 0 ? [rum getTraceHeaderWithKey:key url:url] : [rum getTraceHeaderWithUrl:url];
182
+ }
183
+ if ([method isEqualToString:@"replay.configure"]) {
184
+ FTSessionReplayConfig *config = [[FTSessionReplayConfig alloc] init];
185
+ [self ensureExternalReplayAPIForConfig:config];
186
+ if (arguments[@"sampleRate"]) config.sampleRate = [self percent:arguments[@"sampleRate"]];
187
+ if (arguments[@"sessionOnErrorSampleRate"]) config.sessionReplayOnErrorSampleRate = [self percent:arguments[@"sessionOnErrorSampleRate"]];
188
+ if ([[self text:arguments[@"touchPrivacy"]] isEqualToString:@"show"]) config.touchPrivacy = FTTouchPrivacyLevelShow;
189
+ SEL externalMode = NSSelectorFromString(@"setExternalRecorderMode:");
190
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(config, externalMode, YES);
191
+ [[FTRumSessionReplay sharedInstance] startWithSessionReplayConfig:config];
192
+ return nil;
193
+ }
194
+ if ([method isEqualToString:@"replay.getContext"]) {
195
+ return [self invokeReplayObjectSelector:NSSelectorFromString(@"currentExternalRUMContext")];
196
+ }
197
+ if ([method isEqualToString:@"replay.saveImage"]) {
198
+ NSData *rgba = [NSData dataWithContentsOfFile:[self requiredText:arguments key:@"path"]];
199
+ if (!rgba) [self fail:@"Unable to read replay RGBA file"];
200
+ NSData *png = [self pngDataFromRGBA:rgba
201
+ width:[arguments[@"width"] unsignedIntegerValue]
202
+ height:[arguments[@"height"] unsignedIntegerValue]];
203
+ if (!png) [self fail:@"Unable to encode replay image"];
204
+ return [self invokeReplayObjectSelector:NSSelectorFromString(@"saveExternalImageResourceData:mimeType:")
205
+ first:png
206
+ second:@"image/png"];
207
+ }
208
+ if ([method isEqualToString:@"replay.saveImageV2"]) {
209
+ NSUInteger width = [arguments[@"width"] unsignedIntegerValue];
210
+ NSUInteger height = [arguments[@"height"] unsignedIntegerValue];
211
+ NSData *rgba = [NSData dataWithContentsOfFile:[self requiredText:arguments key:@"path"]];
212
+ if (!rgba) [self fail:@"Unable to read replay RGBA file"];
213
+ UIImage *image = [self imageFromRGBA:rgba width:width height:height];
214
+ if (!image) [self fail:@"Unable to decode replay RGBA image"];
215
+
216
+ CGFloat quality = MIN(1.0, MAX(0.1, [arguments[@"quality"] doubleValue] ?: 0.45));
217
+ NSUInteger maxFrameBytes = [arguments[@"maxFrameBytes"] unsignedIntegerValue] ?: 40 * 1024;
218
+ UIImage *encodedImage = [self opaqueImageFromImage:image size:CGSizeMake(width, height)];
219
+ NSData *jpeg = UIImageJPEGRepresentation(encodedImage, quality);
220
+ if (jpeg.length > maxFrameBytes) {
221
+ quality = MAX(0.2, quality * 0.7);
222
+ jpeg = UIImageJPEGRepresentation(encodedImage, quality);
223
+ }
224
+ if (jpeg.length > maxFrameBytes && width > 1 && height > 1) {
225
+ width = MAX(1, (NSUInteger)floor(width * 0.75));
226
+ height = MAX(1, (NSUInteger)floor(height * 0.75));
227
+ encodedImage = [self opaqueImageFromImage:image size:CGSizeMake(width, height)];
228
+ jpeg = UIImageJPEGRepresentation(encodedImage, quality);
229
+ }
230
+ if (!jpeg || jpeg.length > maxFrameBytes) return @{ @"accepted": @NO };
231
+
232
+ NSString *resourceID = [self invokeReplayObjectSelector:NSSelectorFromString(@"saveExternalImageResourceData:mimeType:")
233
+ first:jpeg
234
+ second:@"image/jpeg"];
235
+ if (resourceID.length == 0) [self fail:@"Unable to save replay image resource"];
236
+ return @{
237
+ @"accepted": @YES,
238
+ @"resourceId": resourceID,
239
+ @"byteSize": @(jpeg.length),
240
+ @"width": @(width),
241
+ @"height": @(height),
242
+ @"mimeType": @"image/jpeg",
243
+ };
244
+ }
245
+ if ([method isEqualToString:@"replay.writeSegment"]) {
246
+ [self invokeReplayVoidSelector:NSSelectorFromString(@"writeExternalSegment:viewID:")
247
+ first:[self requiredText:arguments key:@"segment"]
248
+ second:[self requiredText:arguments key:@"viewId"]];
249
+ return nil;
250
+ }
251
+ if ([method isEqualToString:@"replay.setRecordCount"]) {
252
+ [self invokeReplayCountSelector:NSSelectorFromString(@"setExternalRecordCountForViewID:count:")
253
+ viewID:[self requiredText:arguments key:@"viewId"]
254
+ count:[arguments[@"count"] unsignedIntegerValue]];
255
+ return nil;
256
+ }
257
+ if ([method isEqualToString:@"replay.stop"]) return nil;
258
+ [self fail:[NSString stringWithFormat:@"Unknown bridge method: %@", method]];
259
+ return nil;
260
+ }
261
+
262
+ + (NSString *)beginSaveImage:(NSDictionary *)arguments {
263
+ NSString *method = [self requiredText:arguments key:@"method"];
264
+ if (![method isEqualToString:@"replay.saveImage"] && ![method isEqualToString:@"replay.saveImageV2"]) {
265
+ [self fail:@"Unsupported asynchronous Replay method"];
266
+ }
267
+ NSDictionary *payload = [[self dictionary:arguments[@"arguments"]] copy];
268
+ if (!payload) [self fail:@"Replay image arguments are required"];
269
+ return [FTCocosReplayImageJobs beginWithWork:^NSString *{
270
+ return [self responseWithValue:[self dispatch:method arguments:payload] error:nil];
271
+ }];
272
+ }
273
+
274
+ + (void)ensureNativeHostInitialized {
275
+ @try {
276
+ if (![FTMobileAgent sharedInstance]) {
277
+ [self fail:@"The native host must initialize the native SDK before FTCocosSDK.attach()"];
278
+ }
279
+ } @catch (__unused NSException *exception) {
280
+ [self fail:@"The native host must initialize the native SDK before FTCocosSDK.attach()"];
281
+ }
282
+ }
283
+
284
+ + (void)ensureHybridRecorderSwitch {
285
+ SEL selector = NSSelectorFromString(@"setExternalRecorderActive:");
286
+ if (![[FTRumSessionReplay sharedInstance] respondsToSelector:selector]) {
287
+ [self fail:@"iOS Session Replay does not support Hybrid recorder switching; upgrade the Native SDK"];
288
+ }
289
+ }
290
+
291
+ + (void)setExternalRecorderActive:(BOOL)active {
292
+ [self ensureHybridRecorderSwitch];
293
+ SEL selector = NSSelectorFromString(@"setExternalRecorderActive:");
294
+ ((void (*)(id, SEL, BOOL))objc_msgSend)([FTRumSessionReplay sharedInstance], selector, active);
295
+ }
296
+
297
+ + (void)addResource:(NSDictionary *)arguments manager:(FTExternalDataManager *)manager {
298
+ NSDictionary *input = [self dictionary:arguments[@"content"]] ?: @{};
299
+ NSURL *url = [NSURL URLWithString:[self text:input[@"url"]] ?: @""];
300
+ if (!url) [self fail:@"Invalid resource URL"];
301
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
302
+ request.HTTPMethod = [self text:input[@"httpMethod"]] ?: @"GET";
303
+ [request setAllHTTPHeaderFields:[self dictionary:input[@"requestHeaders"]]];
304
+ NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc]
305
+ initWithURL:url
306
+ statusCode:[input[@"statusCode"] integerValue]
307
+ HTTPVersion:@"HTTP/1.1"
308
+ headerFields:[self dictionary:input[@"responseHeaders"]]];
309
+ NSData *body = [[self text:input[@"responseBody"]] dataUsingEncoding:NSUTF8StringEncoding];
310
+ FTResourceContentModel *content = [[FTResourceContentModel alloc] initWithRequest:request response:response data:body error:nil];
311
+
312
+ NSDictionary *timings = [self dictionary:arguments[@"metrics"]];
313
+ FTResourceMetricsModel *metrics = [[FTResourceMetricsModel alloc] init];
314
+ metrics.fetchStartNsTimeInterval = [timings[@"fetchStartTime"] longLongValue];
315
+ metrics.connectStartNsTimeInterval = [timings[@"tcpStartTime"] longLongValue];
316
+ metrics.connectEndNsTimeInterval = [timings[@"tcpEndTime"] longLongValue];
317
+ metrics.dnsStartNsTimeInterval = [timings[@"dnsStartTime"] longLongValue];
318
+ metrics.dnsEndNsTimeInterval = [timings[@"dnsEndTime"] longLongValue];
319
+ metrics.responseStartNsTimeInterval = [timings[@"responseStartTime"] longLongValue];
320
+ metrics.responseEndNsTimeInterval = [timings[@"responseEndTime"] longLongValue];
321
+ metrics.sslStartNsTimeInterval = [timings[@"sslStartTime"] longLongValue];
322
+ metrics.sslEndNsTimeInterval = [timings[@"sslEndTime"] longLongValue];
323
+ [manager addResourceWithKey:[self requiredText:arguments key:@"key"] metrics:metrics content:content];
324
+ }
325
+
326
+ + (id)invokeReplayObjectSelector:(SEL)selector {
327
+ id target = [FTRumSessionReplay sharedInstance];
328
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
329
+ return ((id (*)(id, SEL))objc_msgSend)(target, selector);
330
+ }
331
+
332
+ + (id)invokeReplayObjectSelector:(SEL)selector object:(id)object {
333
+ id target = [FTRumSessionReplay sharedInstance];
334
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
335
+ return ((id (*)(id, SEL, id))objc_msgSend)(target, selector, object);
336
+ }
337
+
338
+ + (id)invokeReplayObjectSelector:(SEL)selector first:(id)first second:(id)second {
339
+ id target = [FTRumSessionReplay sharedInstance];
340
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
341
+ return ((id (*)(id, SEL, id, id))objc_msgSend)(target, selector, first, second);
342
+ }
343
+
344
+ + (void)invokeReplayVoidSelector:(SEL)selector first:(id)first second:(id)second {
345
+ id target = [FTRumSessionReplay sharedInstance];
346
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
347
+ ((void (*)(id, SEL, id, id))objc_msgSend)(target, selector, first, second);
348
+ }
349
+
350
+ + (void)invokeReplayCountSelector:(SEL)selector viewID:(NSString *)viewID count:(NSUInteger)count {
351
+ id target = [FTRumSessionReplay sharedInstance];
352
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
353
+ ((void (*)(id, SEL, id, NSUInteger))objc_msgSend)(target, selector, viewID, count);
354
+ }
355
+
356
+ + (void)missingReplayAPI:(SEL)selector {
357
+ [self fail:[NSString stringWithFormat:@"FTMobileSDK lacks external Session Replay API %@; use the Cocos-compatible iOS SDK release", NSStringFromSelector(selector)]];
358
+ }
359
+
360
+ + (void)ensureExternalReplayAPIForConfig:(FTSessionReplayConfig *)config {
361
+ SEL externalMode = NSSelectorFromString(@"setExternalRecorderMode:");
362
+ if (![config respondsToSelector:externalMode]) [self missingReplayAPI:externalMode];
363
+
364
+ id target = [FTRumSessionReplay sharedInstance];
365
+ NSArray<NSString *> *selectorNames = @[
366
+ @"currentExternalRUMContext",
367
+ @"saveExternalImageResourceData:mimeType:",
368
+ @"writeExternalSegment:viewID:",
369
+ @"setExternalRecordCountForViewID:count:",
370
+ ];
371
+ for (NSString *name in selectorNames) {
372
+ SEL selector = NSSelectorFromString(name);
373
+ if (![target respondsToSelector:selector]) [self missingReplayAPI:selector];
374
+ }
375
+ }
376
+
377
+ + (NSData *)pngDataFromRGBA:(NSData *)rgba width:(NSUInteger)width height:(NSUInteger)height {
378
+ UIImage *image = [self imageFromRGBA:rgba width:width height:height];
379
+ return image ? UIImagePNGRepresentation(image) : nil;
380
+ }
381
+
382
+ + (UIImage *)imageFromRGBA:(NSData *)rgba width:(NSUInteger)width height:(NSUInteger)height {
383
+ if (width == 0 || height == 0 || width > NSUIntegerMax / height / 4 || rgba.length < width * height * 4) {
384
+ return nil;
385
+ }
386
+ NSMutableData *pixels = [rgba mutableCopy];
387
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
388
+ CGContextRef context = CGBitmapContextCreate(pixels.mutableBytes,
389
+ width,
390
+ height,
391
+ 8,
392
+ width * 4,
393
+ colorSpace,
394
+ (CGBitmapInfo)kCGBitmapByteOrderDefault |
395
+ (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
396
+ CGColorSpaceRelease(colorSpace);
397
+ if (!context) return nil;
398
+ CGImageRef imageRef = CGBitmapContextCreateImage(context);
399
+ CGContextRelease(context);
400
+ if (!imageRef) return nil;
401
+ UIImage *image = [UIImage imageWithCGImage:imageRef];
402
+ CGImageRelease(imageRef);
403
+ return image;
404
+ }
405
+
406
+ + (UIImage *)opaqueImageFromImage:(UIImage *)image size:(CGSize)size {
407
+ UIGraphicsBeginImageContextWithOptions(size, YES, 1.0);
408
+ [[UIColor whiteColor] setFill];
409
+ UIRectFill(CGRectMake(0, 0, size.width, size.height));
410
+ [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
411
+ UIImage *opaque = UIGraphicsGetImageFromCurrentImageContext();
412
+ UIGraphicsEndImageContext();
413
+ return opaque;
414
+ }
415
+
416
+ + (NSString *)responseWithValue:(id)value error:(NSString *)error {
417
+ NSMutableDictionary *response = [@{ @"ok": @(error == nil) } mutableCopy];
418
+ if (value) response[@"value"] = value;
419
+ if (error) response[@"error"] = error;
420
+ NSData *data = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
421
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"{\"ok\":false}";
422
+ }
423
+
424
+ + (NSDictionary *)dictionary:(id)value {
425
+ return [value isKindOfClass:NSDictionary.class] ? value : nil;
426
+ }
427
+
428
+ + (NSString *)text:(id)value {
429
+ return [value isKindOfClass:NSString.class] && [value length] > 0 ? value : nil;
430
+ }
431
+
432
+ + (NSString *)requiredText:(NSDictionary *)arguments key:(NSString *)key {
433
+ NSString *value = [self text:arguments[key]];
434
+ if (value.length == 0) [self fail:[NSString stringWithFormat:@"%@ is required", key]];
435
+ return value;
436
+ }
437
+
438
+ + (int)percent:(NSNumber *)number {
439
+ return (int)lround(MIN(1.0, MAX(0.0, number.doubleValue)) * 100.0);
440
+ }
441
+
442
+ + (FTLogStatus)logStatus:(NSString *)value {
443
+ if ([value isEqualToString:@"warning"]) return FTStatusWarning;
444
+ if ([value isEqualToString:@"error"]) return FTStatusError;
445
+ if ([value isEqualToString:@"critical"]) return FTStatusCritical;
446
+ if ([value isEqualToString:@"ok"]) return FTStatusOk;
447
+ return FTStatusInfo;
448
+ }
449
+
450
+ + (NSArray *)logLevelFilters:(NSArray *)values {
451
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:values.count];
452
+ for (id value in values) {
453
+ if (![value isKindOfClass:NSString.class]) continue;
454
+ NSString *level = value;
455
+ if ([level isEqualToString:@"info"] ||
456
+ [level isEqualToString:@"warning"] ||
457
+ [level isEqualToString:@"error"] ||
458
+ [level isEqualToString:@"critical"] ||
459
+ [level isEqualToString:@"ok"]) {
460
+ [result addObject:@([self logStatus:level])];
461
+ } else {
462
+ [result addObject:level];
463
+ }
464
+ }
465
+ return result;
466
+ }
467
+
468
+ + (FTNetworkTraceType)traceType:(NSString *)value {
469
+ if ([value isEqualToString:@"zipkinMultiHeader"]) return FTNetworkTraceTypeZipkinMultiHeader;
470
+ if ([value isEqualToString:@"zipkinSingleHeader"]) return FTNetworkTraceTypeZipkinSingleHeader;
471
+ if ([value isEqualToString:@"traceparent"]) return FTNetworkTraceTypeTraceparent;
472
+ if ([value isEqualToString:@"skywalking"]) return FTNetworkTraceTypeSkywalking;
473
+ if ([value isEqualToString:@"jaeger"]) return FTNetworkTraceTypeJaeger;
474
+ return FTNetworkTraceTypeDDtrace;
475
+ }
476
+
477
+ + (FTMonitorFrequency)monitorFrequency:(NSString *)value {
478
+ if ([value isEqualToString:@"frequent"]) return FTMonitorFrequencyFrequent;
479
+ if ([value isEqualToString:@"rare"]) return FTMonitorFrequencyRare;
480
+ return FTMonitorFrequencyDefault;
481
+ }
482
+
483
+ + (void)fail:(NSString *)message {
484
+ @throw [NSException exceptionWithName:@"FTCocosBridgeError" reason:message userInfo:nil];
485
+ }
486
+
487
+ @end
@@ -0,0 +1,18 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'FTCocosBridge'
3
+ s.version = '0.1.0-alpha.4'
4
+ s.summary = 'TrueWatch Mobile SDK native bridge for Cocos Creator.'
5
+ s.homepage = 'https://github.com/TrueWatchTech/datakit-cocos'
6
+ s.license = { :type => 'Apache-2.0' }
7
+ s.author = { 'TrueWatch' => 'support@truewatch.com' }
8
+ s.source = { :path => '.' }
9
+ s.platform = :ios, '12.0'
10
+ s.source_files = 'FTCocosBridge.{h,m}', 'FTCocosReplayImageJobs.{h,m}'
11
+ s.public_header_files = 'FTCocosBridge.h'
12
+ s.requires_arc = true
13
+ # Cocos resolves FTCocosBridge by class name at runtime, so no direct symbol
14
+ # reference would otherwise keep the Objective-C category/class in the app.
15
+ s.user_target_xcconfig = { 'OTHER_LDFLAGS' => '$(inherited) -ObjC' }
16
+ s.dependency 'TrueWatchSDK/Agent', '1.6.8-alpha.2'
17
+ s.dependency 'TrueWatchSDK/FTSessionReplay', '1.6.8-alpha.2'
18
+ end
@@ -0,0 +1,11 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /** One background image operation, with nonblocking result retrieval from JSB. */
6
+ @interface FTCocosReplayImageJobs : NSObject
7
+ + (NSString *)beginWithWork:(NSString *(^)(void))work;
8
+ + (NSDictionary *)poll:(NSString *)identifier;
9
+ @end
10
+
11
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,54 @@
1
+ #import "FTCocosReplayImageJobs.h"
2
+
3
+ @interface FTCocosReplayImageJob : NSObject
4
+ @property (nonatomic, copy) NSString *identifier;
5
+ @property (nonatomic, copy) NSString *response;
6
+ @end
7
+ @implementation FTCocosReplayImageJob
8
+ @end
9
+
10
+ static FTCocosReplayImageJob *currentJob;
11
+
12
+ @implementation FTCocosReplayImageJobs
13
+
14
+ + (NSString *)beginWithWork:(NSString *(^)(void))work {
15
+ FTCocosReplayImageJob *job = [FTCocosReplayImageJob new];
16
+ job.identifier = NSUUID.UUID.UUIDString;
17
+ @synchronized (self) {
18
+ if (currentJob && !currentJob.response) {
19
+ [NSException raise:NSInternalInconsistencyException format:@"Replay encoder is busy"];
20
+ }
21
+ currentJob = job;
22
+ }
23
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
24
+ @autoreleasepool {
25
+ NSString *response;
26
+ @try {
27
+ response = work();
28
+ } @catch (NSException *exception) {
29
+ NSData *data = [NSJSONSerialization dataWithJSONObject:@{
30
+ @"ok": @NO, @"error": exception.reason ?: @"Replay encoding failed"
31
+ } options:0 error:nil];
32
+ response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
33
+ }
34
+ @synchronized (self) {
35
+ job.response = response ?: @"{\"ok\":false,\"error\":\"Empty Replay encoding result\"}";
36
+ }
37
+ }
38
+ });
39
+ return job.identifier;
40
+ }
41
+
42
+ + (NSDictionary *)poll:(NSString *)identifier {
43
+ @synchronized (self) {
44
+ if (![currentJob.identifier isEqualToString:identifier]) {
45
+ [NSException raise:NSInvalidArgumentException format:@"Replay encoding job is unavailable"];
46
+ }
47
+ if (!currentJob.response) return @{ @"pending": @YES };
48
+ NSDictionary *result = @{ @"pending": @NO, @"response": currentJob.response };
49
+ currentJob = nil;
50
+ return result;
51
+ }
52
+ }
53
+
54
+ @end
@@ -0,0 +1,62 @@
1
+ #pragma once
2
+
3
+ #if __has_include("bindings/jswrapper/SeApi.h")
4
+ #define FT_COCOS_CREATOR3 1
5
+ #include "bindings/jswrapper/SeApi.h"
6
+ #else
7
+ #include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
8
+ #endif
9
+ #include "FTReplayFileWorker.h"
10
+
11
+ namespace ft_cocos {
12
+
13
+ static ReplayFileWorker replayFiles;
14
+
15
+ static bool writeReplayFile(se::State& state) {
16
+ const auto& args = state.args();
17
+ if (args.size() != 2 || !args[0].isObject() || !args[1].isString()) return false;
18
+ auto* object = args[0].toObject();
19
+ if (!object->isTypedArray()) return false;
20
+ uint8_t* bytes = nullptr;
21
+ size_t size = 0;
22
+ if (!object->getTypedArrayData(&bytes, &size)) return false;
23
+ state.rval().setBoolean(replayFiles.write(bytes, size, args[1].toString()));
24
+ return true;
25
+ }
26
+ SE_BIND_FUNC(writeReplayFile)
27
+
28
+ static bool pollReplayFile(se::State& state) {
29
+ state.rval().setString(replayFiles.poll());
30
+ return true;
31
+ }
32
+ SE_BIND_FUNC(pollReplayFile)
33
+
34
+ static bool removeReplayFile(se::State& state) {
35
+ const auto& args = state.args();
36
+ if (args.size() != 1 || !args[0].isString()) return false;
37
+ state.rval().setBoolean(replayFiles.remove(args[0].toString()));
38
+ return true;
39
+ }
40
+ SE_BIND_FUNC(removeReplayFile)
41
+
42
+ static bool registerReplayFileBridge(se::Object* global) {
43
+ replayFiles.cancel();
44
+ const se::HandleObject bridge(se::Object::createPlainObject());
45
+ bridge->defineFunction("write", _SE(writeReplayFile));
46
+ bridge->defineFunction("poll", _SE(pollReplayFile));
47
+ bridge->defineFunction("remove", _SE(removeReplayFile));
48
+ global->setProperty("__ftCocosReplayFile", se::Value(bridge));
49
+ se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { replayFiles.cancel(); });
50
+ return true;
51
+ }
52
+
53
+ static void installReplayFileBridge() {
54
+ #if FT_COCOS_CREATOR3
55
+ se::ScriptEngine::getInstance()->addPermanentRegisterCallback(registerReplayFileBridge);
56
+ #else
57
+ // Creator 2 calls AppDelegate again when restarting its script VM.
58
+ se::ScriptEngine::getInstance()->addRegisterCallback(registerReplayFileBridge);
59
+ #endif
60
+ }
61
+
62
+ } // namespace ft_cocos