sitepong 0.2.3 → 0.2.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 (57) hide show
  1. package/android/build.gradle +39 -0
  2. package/android/src/main/java/com/sitepong/deviceid/DeviceIdHelper.kt +45 -0
  3. package/android/src/main/java/com/sitepong/deviceid/DeviceIdModule.kt +45 -0
  4. package/android/src/main/java/com/sitepong/screenrecorder/ChunkUploader.kt +78 -0
  5. package/android/src/main/java/com/sitepong/screenrecorder/H264Encoder.kt +163 -0
  6. package/android/src/main/java/com/sitepong/screenrecorder/ScreenCapture.kt +103 -0
  7. package/android/src/main/java/com/sitepong/screenrecorder/ScreenRecorderModule.kt +80 -0
  8. package/android/src/main/java/com/sitepong/screenrecorder/SensitiveViewManager.kt +19 -0
  9. package/app.plugin.js +426 -0
  10. package/dist/cdn/sitepong.min.js +5 -5
  11. package/dist/cdn/sitepong.min.js.map +1 -1
  12. package/dist/entries/rn.d.ts +188 -16
  13. package/dist/entries/rn.js +188 -35
  14. package/dist/entries/rn.js.map +1 -1
  15. package/dist/entries/web.js +2 -1
  16. package/dist/entries/web.js.map +1 -1
  17. package/dist/entries/web.mjs +2 -1
  18. package/dist/entries/web.mjs.map +1 -1
  19. package/dist/index.d.mts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +2 -1
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/react/index.js +2 -1
  26. package/dist/react/index.js.map +1 -1
  27. package/dist/react/index.mjs +2 -1
  28. package/dist/react/index.mjs.map +1 -1
  29. package/expo-module.config.json +16 -0
  30. package/ios/DeviceId/DeviceIdModule.swift +55 -0
  31. package/ios/DeviceId/KeychainHelper.swift +68 -0
  32. package/ios/LiveActivity/ConfigStore.swift +43 -0
  33. package/ios/LiveActivity/DSLNode.swift +188 -0
  34. package/ios/LiveActivity/SitePongActivityAttributes.swift +112 -0
  35. package/ios/LiveActivity/SitePongLiveActivityModule.swift +250 -0
  36. package/ios/LiveActivity/widget/ColorHex.swift +30 -0
  37. package/ios/LiveActivity/widget/DSLAnimations.swift +128 -0
  38. package/ios/LiveActivity/widget/DSLRenderer.swift +538 -0
  39. package/ios/LiveActivity/widget/SitePongLiveActivityWidget.swift +393 -0
  40. package/ios/LiveActivity/widget/SitePongWidgetBundle.swift +84 -0
  41. package/ios/ScreenRecorder/ChunkUploader.swift +52 -0
  42. package/ios/ScreenRecorder/H264Encoder.swift +229 -0
  43. package/ios/ScreenRecorder/ScreenCapture.swift +149 -0
  44. package/ios/ScreenRecorder/ScreenRecorderModule.swift +179 -0
  45. package/ios/ScreenRecorder/SensitiveViewManager.swift +63 -0
  46. package/ios/Sitepong.podspec +57 -0
  47. package/ios/WatchtowerCore/Screenshotter.swift +163 -0
  48. package/ios/WatchtowerCore/SwiftUIModifiers.swift +78 -0
  49. package/ios/WatchtowerCore/Swizzling.swift +59 -0
  50. package/ios/WatchtowerCore/TapEvent.swift +61 -0
  51. package/ios/WatchtowerCore/Transport.swift +136 -0
  52. package/ios/WatchtowerCore/UIViewExtensions.swift +31 -0
  53. package/ios/WatchtowerCore/Watchtower.swift +80 -0
  54. package/ios/WatchtowerCore/WatchtowerEngine.swift +565 -0
  55. package/ios/WatchtowerCore/WatchtowerHash.swift +120 -0
  56. package/ios/WatchtowerCore/WatchtowerRegistry.swift +87 -0
  57. package/package.json +17 -10
@@ -0,0 +1,250 @@
1
+ import ActivityKit
2
+ import ExpoModulesCore
3
+ import Foundation
4
+ import WidgetKit
5
+
6
+ public class SitePongLiveActivityModule: Module {
7
+ /// Tasks listening for push-token rotations on each running activity.
8
+ private var tokenObservers: [String: Task<Void, Never>] = [:]
9
+ /// activityId → activityType so endActivity / token rotations can resolve type.
10
+ private var activityTypes: [String: String] = [:]
11
+
12
+ public func definition() -> ModuleDefinition {
13
+ Name("SitePongLiveActivity")
14
+
15
+ Events("liveActivityPushTokenUpdate")
16
+
17
+ AsyncFunction("areActivitiesEnabled") { () -> Bool in
18
+ if #available(iOS 16.2, *) {
19
+ return ActivityAuthorizationInfo().areActivitiesEnabled
20
+ }
21
+ return false
22
+ }
23
+
24
+ AsyncFunction("startActivity") { (options: [String: Any], promise: Promise) in
25
+ guard #available(iOS 16.2, *) else {
26
+ promise.reject("UNSUPPORTED", "Live Activities require iOS 16.2 or later")
27
+ return
28
+ }
29
+
30
+ guard ActivityAuthorizationInfo().areActivitiesEnabled else {
31
+ promise.reject("DISABLED", "Live Activities are disabled by the user")
32
+ return
33
+ }
34
+
35
+ let activityType = options["activityType"] as? String ?? "default"
36
+ let attributesDict = options["attributes"] as? [String: Any] ?? [:]
37
+ let contentStateDict = options["contentState"] as? [String: Any] ?? [:]
38
+ let alertDict = options["alert"] as? [String: Any]
39
+
40
+ do {
41
+ let attributes = try Self.decode(SitePongActivityAttributes.self, from: attributesDict)
42
+ let state = try Self.decode(SitePongActivityAttributes.ContentState.self, from: contentStateDict)
43
+
44
+ let content = ActivityContent(state: state, staleDate: nil)
45
+
46
+ var alertConfig: AlertConfiguration? = nil
47
+ if let alertDict = alertDict {
48
+ let title = alertDict["title"] as? String ?? ""
49
+ let body = alertDict["body"] as? String ?? ""
50
+ alertConfig = AlertConfiguration(
51
+ title: LocalizedStringResource(stringLiteral: title),
52
+ body: LocalizedStringResource(stringLiteral: body),
53
+ sound: .default
54
+ )
55
+ }
56
+
57
+ let activity: Activity<SitePongActivityAttributes>
58
+ if #available(iOS 16.2, *) {
59
+ activity = try Activity.request(
60
+ attributes: attributes,
61
+ content: content,
62
+ pushType: .token
63
+ )
64
+ } else {
65
+ promise.reject("UNSUPPORTED", "iOS 16.2 required")
66
+ return
67
+ }
68
+ _ = alertConfig // alertConfig only applies to update calls in iOS 16.2+
69
+
70
+ self.activityTypes[activity.id] = activityType
71
+ self.observePushToken(for: activity, activityType: activityType)
72
+
73
+ // Wait up to 2 seconds for the first push token before resolving.
74
+ // ActivityKit on the simulator never emits a token, so without this
75
+ // race the promise would hang the JS caller forever.
76
+ Task {
77
+ let token = await Self.firstPushToken(for: activity, timeoutSeconds: 2)
78
+ promise.resolve([
79
+ "activityId": activity.id,
80
+ "pushToken": token ?? "",
81
+ ])
82
+ }
83
+ } catch {
84
+ promise.reject("DECODE_FAILED", "Failed to start activity: \(error.localizedDescription)")
85
+ }
86
+ }
87
+
88
+ AsyncFunction("updateActivity") { (options: [String: Any], promise: Promise) in
89
+ guard #available(iOS 16.2, *) else {
90
+ promise.reject("UNSUPPORTED", "Live Activities require iOS 16.2 or later")
91
+ return
92
+ }
93
+
94
+ let activityId = options["activityId"] as? String ?? ""
95
+ let contentStateDict = options["contentState"] as? [String: Any] ?? [:]
96
+ let alertDict = options["alert"] as? [String: Any]
97
+
98
+ guard let activity = Activity<SitePongActivityAttributes>.activities.first(where: { $0.id == activityId }) else {
99
+ promise.reject("NOT_FOUND", "No running activity with id \(activityId)")
100
+ return
101
+ }
102
+
103
+ do {
104
+ let state = try Self.decode(SitePongActivityAttributes.ContentState.self, from: contentStateDict)
105
+ let content = ActivityContent(state: state, staleDate: nil)
106
+
107
+ Task {
108
+ if let alertDict = alertDict {
109
+ let title = alertDict["title"] as? String ?? ""
110
+ let body = alertDict["body"] as? String ?? ""
111
+ let alert = AlertConfiguration(
112
+ title: LocalizedStringResource(stringLiteral: title),
113
+ body: LocalizedStringResource(stringLiteral: body),
114
+ sound: .default
115
+ )
116
+ await activity.update(content, alertConfiguration: alert)
117
+ } else {
118
+ await activity.update(content)
119
+ }
120
+ promise.resolve(nil)
121
+ }
122
+ } catch {
123
+ promise.reject("DECODE_FAILED", "Failed to update activity: \(error.localizedDescription)")
124
+ }
125
+ }
126
+
127
+ // MARK: - Widget DSL functions
128
+
129
+ AsyncFunction("syncWidgetConfigs") { (configsJson: String, promise: Promise) in
130
+ guard let data = configsJson.data(using: .utf8) else {
131
+ promise.reject("INVALID", "Invalid JSON string")
132
+ return
133
+ }
134
+ WidgetConfigStore.saveConfigs(data)
135
+ if #available(iOS 14.0, *) {
136
+ WidgetCenter.shared.reloadAllTimelines()
137
+ }
138
+ promise.resolve(nil)
139
+ }
140
+
141
+ AsyncFunction("updateWidgetData") { (dataJson: String, promise: Promise) in
142
+ guard let data = dataJson.data(using: .utf8) else {
143
+ promise.reject("INVALID", "Invalid JSON string")
144
+ return
145
+ }
146
+ WidgetConfigStore.saveData(data)
147
+ if #available(iOS 14.0, *) {
148
+ WidgetCenter.shared.reloadAllTimelines()
149
+ }
150
+ promise.resolve(nil)
151
+ }
152
+
153
+ AsyncFunction("endActivity") { (options: [String: Any], promise: Promise) in
154
+ guard #available(iOS 16.2, *) else {
155
+ promise.reject("UNSUPPORTED", "Live Activities require iOS 16.2 or later")
156
+ return
157
+ }
158
+
159
+ let activityId = options["activityId"] as? String ?? ""
160
+ let contentStateDict = options["contentState"] as? [String: Any]
161
+ let dismissalDateMs = options["dismissalDate"] as? Double
162
+
163
+ guard let activity = Activity<SitePongActivityAttributes>.activities.first(where: { $0.id == activityId }) else {
164
+ promise.resolve(nil)
165
+ return
166
+ }
167
+
168
+ Task {
169
+ var finalContent: ActivityContent<SitePongActivityAttributes.ContentState>? = nil
170
+ if let contentStateDict = contentStateDict {
171
+ if let state = try? Self.decode(SitePongActivityAttributes.ContentState.self, from: contentStateDict) {
172
+ finalContent = ActivityContent(state: state, staleDate: nil)
173
+ }
174
+ }
175
+
176
+ let dismissalPolicy: ActivityUIDismissalPolicy
177
+ if let dismissalDateMs = dismissalDateMs {
178
+ let date = Date(timeIntervalSince1970: dismissalDateMs / 1000)
179
+ dismissalPolicy = .after(date)
180
+ } else {
181
+ dismissalPolicy = .immediate
182
+ }
183
+
184
+ await activity.end(finalContent, dismissalPolicy: dismissalPolicy)
185
+
186
+ self.tokenObservers[activityId]?.cancel()
187
+ self.tokenObservers.removeValue(forKey: activityId)
188
+ self.activityTypes.removeValue(forKey: activityId)
189
+
190
+ promise.resolve(nil)
191
+ }
192
+ }
193
+ }
194
+
195
+ // MARK: - Push token observation
196
+
197
+ @available(iOS 16.2, *)
198
+ private func observePushToken(for activity: Activity<SitePongActivityAttributes>, activityType: String) {
199
+ // Cancel any prior observer for this id (idempotent)
200
+ tokenObservers[activity.id]?.cancel()
201
+
202
+ let task = Task { [weak self] in
203
+ for await tokenData in activity.pushTokenUpdates {
204
+ let hex = tokenData.map { String(format: "%02x", $0) }.joined()
205
+ self?.sendEvent(
206
+ "liveActivityPushTokenUpdate",
207
+ [
208
+ "activityId": activity.id,
209
+ "activityType": activityType,
210
+ "pushToken": hex,
211
+ ]
212
+ )
213
+ }
214
+ }
215
+ tokenObservers[activity.id] = task
216
+ }
217
+
218
+ // MARK: - JSON helpers
219
+
220
+ private static func decode<T: Decodable>(_ type: T.Type, from dict: [String: Any]) throws -> T {
221
+ let data = try JSONSerialization.data(withJSONObject: dict, options: [])
222
+ let decoder = JSONDecoder()
223
+ return try decoder.decode(type, from: data)
224
+ }
225
+
226
+ /// Race the first emission of `activity.pushTokenUpdates` against a sleep.
227
+ /// Returns the hex-encoded token, or nil if the timeout fires first
228
+ /// (the common case on iOS Simulator).
229
+ @available(iOS 16.2, *)
230
+ private static func firstPushToken(
231
+ for activity: Activity<SitePongActivityAttributes>,
232
+ timeoutSeconds: Double
233
+ ) async -> String? {
234
+ await withTaskGroup(of: String?.self) { group in
235
+ group.addTask {
236
+ for await tokenData in activity.pushTokenUpdates {
237
+ return tokenData.map { String(format: "%02x", $0) }.joined()
238
+ }
239
+ return nil
240
+ }
241
+ group.addTask {
242
+ try? await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
243
+ return nil
244
+ }
245
+ let result = await group.next() ?? nil
246
+ group.cancelAll()
247
+ return result
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,30 @@
1
+ import SwiftUI
2
+
3
+ extension Color {
4
+ /// Parse a hex color string like "#3b82f6" or "#3b82f6cc" (RRGGBB or RRGGBBAA).
5
+ /// Returns nil for malformed input so the widget can fall back to defaults.
6
+ init?(hex: String?) {
7
+ guard let raw = hex else { return nil }
8
+ var s = raw.trimmingCharacters(in: .whitespacesAndNewlines)
9
+ if s.hasPrefix("#") { s.removeFirst() }
10
+ guard s.count == 6 || s.count == 8 else { return nil }
11
+
12
+ var rgba: UInt64 = 0
13
+ guard Scanner(string: s).scanHexInt64(&rgba) else { return nil }
14
+
15
+ let r, g, b, a: Double
16
+ if s.count == 8 {
17
+ r = Double((rgba >> 24) & 0xff) / 255
18
+ g = Double((rgba >> 16) & 0xff) / 255
19
+ b = Double((rgba >> 8) & 0xff) / 255
20
+ a = Double(rgba & 0xff) / 255
21
+ } else {
22
+ r = Double((rgba >> 16) & 0xff) / 255
23
+ g = Double((rgba >> 8) & 0xff) / 255
24
+ b = Double(rgba & 0xff) / 255
25
+ a = 1
26
+ }
27
+
28
+ self.init(.sRGB, red: r, green: g, blue: b, opacity: a)
29
+ }
30
+ }
@@ -0,0 +1,128 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+
4
+ // MARK: - Animation view modifier
5
+
6
+ @available(iOS 16.2, *)
7
+ struct DSLAnimationModifier: ViewModifier {
8
+ let animation: DSLAnimation?
9
+
10
+ func body(content: Content) -> some View {
11
+ var view = AnyView(content)
12
+
13
+ // Entrance transitions — in WidgetKit these only apply as content
14
+ // transitions between timeline entries. Full animations work in
15
+ // Live Activities.
16
+ if let entrance = animation?.entrance {
17
+ let transition = entranceTransition(entrance)
18
+ view = AnyView(view.transition(transition))
19
+ }
20
+
21
+ // Value transition — animate numeric content changes
22
+ if let valueTransition = animation?.valueTransition {
23
+ let anim = valueAnimation(valueTransition)
24
+ view = AnyView(view.animation(anim, value: 0))
25
+ }
26
+
27
+ return view
28
+ }
29
+
30
+ private func entranceTransition(_ type: String) -> AnyTransition {
31
+ switch type {
32
+ case "fadeIn":
33
+ return .opacity
34
+ case "slideUp":
35
+ return .move(edge: .bottom).combined(with: .opacity)
36
+ case "slideLeft":
37
+ return .move(edge: .trailing).combined(with: .opacity)
38
+ case "scaleIn":
39
+ return .scale.combined(with: .opacity)
40
+ default:
41
+ return .opacity
42
+ }
43
+ }
44
+
45
+ private func valueAnimation(_ type: String) -> Animation {
46
+ let duration = animation?.entranceDuration ?? 0.3
47
+ switch type {
48
+ case "spring":
49
+ return .spring(response: 0.5, dampingFraction: 0.7)
50
+ case "easeIn":
51
+ return .easeIn(duration: duration)
52
+ case "easeOut":
53
+ return .easeOut(duration: duration)
54
+ case "linear":
55
+ return .linear(duration: duration)
56
+ default:
57
+ return .easeInOut(duration: duration)
58
+ }
59
+ }
60
+ }
61
+
62
+ // MARK: - Ambient animation modifier
63
+ // Note: Ambient animations (pulse, shimmer, glow) only work in Live Activities,
64
+ // not in static Home Screen widgets where WidgetKit controls the timeline.
65
+
66
+ @available(iOS 16.2, *)
67
+ struct DSLAmbientModifier: ViewModifier {
68
+ let ambient: String?
69
+ let ambientDuration: Double?
70
+ @State private var isAnimating = false
71
+
72
+ func body(content: Content) -> some View {
73
+ guard let ambient = ambient else {
74
+ return AnyView(content)
75
+ }
76
+
77
+ let duration = ambientDuration ?? 2.0
78
+
79
+ switch ambient {
80
+ case "pulse":
81
+ return AnyView(
82
+ content
83
+ .opacity(isAnimating ? 0.6 : 1.0)
84
+ .onAppear {
85
+ withAnimation(.easeInOut(duration: duration).repeatForever(autoreverses: true)) {
86
+ isAnimating = true
87
+ }
88
+ }
89
+ )
90
+ case "shimmer":
91
+ return AnyView(
92
+ content
93
+ .opacity(isAnimating ? 0.8 : 1.0)
94
+ .onAppear {
95
+ withAnimation(.easeInOut(duration: duration).repeatForever(autoreverses: true)) {
96
+ isAnimating = true
97
+ }
98
+ }
99
+ )
100
+ case "glow":
101
+ return AnyView(
102
+ content
103
+ .shadow(
104
+ color: .white.opacity(isAnimating ? 0.4 : 0.0),
105
+ radius: isAnimating ? 8 : 2
106
+ )
107
+ .onAppear {
108
+ withAnimation(.easeInOut(duration: duration).repeatForever(autoreverses: true)) {
109
+ isAnimating = true
110
+ }
111
+ }
112
+ )
113
+ default:
114
+ return AnyView(content)
115
+ }
116
+ }
117
+ }
118
+
119
+ // MARK: - View extension
120
+
121
+ @available(iOS 16.2, *)
122
+ extension View {
123
+ func applyAnimation(_ animation: DSLAnimation?) -> some View {
124
+ self
125
+ .modifier(DSLAnimationModifier(animation: animation))
126
+ .modifier(DSLAmbientModifier(ambient: animation?.ambient, ambientDuration: animation?.ambientDuration))
127
+ }
128
+ }