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,538 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+
4
+ // MARK: - DSLView
5
+
6
+ @available(iOS 16.2, *)
7
+ struct DSLView: View {
8
+ let node: DSLNode
9
+ let data: [String: AnyCodable]
10
+
11
+ var body: some View {
12
+ applyStyle(to: renderNode())
13
+ }
14
+
15
+ // MARK: - Node routing
16
+
17
+ @ViewBuilder
18
+ private func renderNode() -> some View {
19
+ switch node.type {
20
+ case "vstack":
21
+ renderVStack()
22
+ case "hstack":
23
+ renderHStack()
24
+ case "zstack":
25
+ renderZStack()
26
+ case "text":
27
+ renderText()
28
+ case "image":
29
+ renderImage()
30
+ case "spacer":
31
+ renderSpacer()
32
+ case "gauge":
33
+ renderGauge()
34
+ case "progress":
35
+ renderProgress()
36
+ case "shape":
37
+ renderShape()
38
+ case "container":
39
+ renderContainer()
40
+ case "conditional":
41
+ renderConditional()
42
+ default:
43
+ EmptyView()
44
+ }
45
+ }
46
+
47
+ // MARK: - Stack views
48
+
49
+ @ViewBuilder
50
+ private func renderVStack() -> some View {
51
+ let alignment = horizontalAlignment(from: prop("alignment")?.stringValue)
52
+ let spacing = prop("spacing")?.doubleValue
53
+
54
+ VStack(alignment: alignment, spacing: spacing.map { CGFloat($0) }) {
55
+ renderChildren()
56
+ }
57
+ }
58
+
59
+ @ViewBuilder
60
+ private func renderHStack() -> some View {
61
+ let alignment = verticalAlignment(from: prop("alignment")?.stringValue)
62
+ let spacing = prop("spacing")?.doubleValue
63
+
64
+ HStack(alignment: alignment, spacing: spacing.map { CGFloat($0) }) {
65
+ renderChildren()
66
+ }
67
+ }
68
+
69
+ @ViewBuilder
70
+ private func renderZStack() -> some View {
71
+ let alignment = compositeAlignment(from: prop("alignment")?.stringValue)
72
+
73
+ ZStack(alignment: alignment) {
74
+ renderChildren()
75
+ }
76
+ }
77
+
78
+ // MARK: - Text
79
+
80
+ @ViewBuilder
81
+ private func renderText() -> some View {
82
+ let content = interpolate(prop("value")?.stringValue ?? "")
83
+ let fontPreset = prop("font")?.stringValue
84
+ let fontSize = prop("fontSize")?.doubleValue
85
+ let fontWeightName = prop("fontWeight")?.stringValue
86
+ let color = prop("color")?.stringValue
87
+ let italic = prop("italic")?.boolValue ?? false
88
+ let monoDigit = prop("monospacedDigit")?.boolValue ?? false
89
+ let lineLimit = prop("lineLimit")?.intValue
90
+
91
+ let weight = symbolWeight(fontWeightName)
92
+ let size = fontSize ?? fontPresetSize(fontPreset) ?? 14
93
+
94
+ var font = Font.system(size: size, weight: weight)
95
+ if monoDigit {
96
+ font = font.monospacedDigit()
97
+ }
98
+
99
+ let textView = Text(content)
100
+ .font(font)
101
+ .italic(italic)
102
+
103
+ if let lineLimit = lineLimit {
104
+ if let color = Color(hex: color) {
105
+ textView.foregroundStyle(color).lineLimit(lineLimit)
106
+ } else {
107
+ textView.lineLimit(lineLimit)
108
+ }
109
+ } else {
110
+ if let color = Color(hex: color) {
111
+ textView.foregroundStyle(color)
112
+ } else {
113
+ textView
114
+ }
115
+ }
116
+ }
117
+
118
+ // MARK: - Image (SF Symbols only)
119
+
120
+ @ViewBuilder
121
+ private func renderImage() -> some View {
122
+ let symbolName = interpolate(prop("symbol")?.stringValue ?? prop("name")?.stringValue ?? "questionmark")
123
+ let color = prop("color")?.stringValue
124
+ let weightName = prop("weight")?.stringValue
125
+ let size = prop("size")?.doubleValue ?? 16
126
+
127
+ let weight = symbolWeight(weightName)
128
+
129
+ let img = Image(systemName: symbolName)
130
+ .font(.system(size: size, weight: weight))
131
+
132
+ if let color = Color(hex: color) {
133
+ img.foregroundStyle(color)
134
+ } else {
135
+ img
136
+ }
137
+ }
138
+
139
+ // MARK: - Spacer
140
+
141
+ @ViewBuilder
142
+ private func renderSpacer() -> some View {
143
+ if let minLength = prop("minLength")?.doubleValue {
144
+ Spacer(minLength: CGFloat(minLength))
145
+ } else {
146
+ Spacer()
147
+ }
148
+ }
149
+
150
+ // MARK: - Gauge (iOS 16+)
151
+
152
+ @ViewBuilder
153
+ private func renderGauge() -> some View {
154
+ let value = prop("value")?.doubleValue ?? 0
155
+ let minVal = prop("min")?.doubleValue ?? 0
156
+ let maxVal = prop("max")?.doubleValue ?? 1
157
+ let label = interpolate(prop("label")?.stringValue ?? "")
158
+ let gaugeStyle = prop("style")?.stringValue ?? "linear"
159
+ let tintColor = prop("tint")?.stringValue
160
+ let trackColor = prop("trackColor")?.stringValue
161
+
162
+ let clamped = max(minVal, min(maxVal, value))
163
+
164
+ if gaugeStyle == "circular" {
165
+ Gauge(value: clamped, in: minVal...maxVal) {
166
+ Text(label)
167
+ }
168
+ .gaugeStyle(.accessoryCircular)
169
+ .tint(Color(hex: tintColor) ?? .accentColor)
170
+ } else {
171
+ Gauge(value: clamped, in: minVal...maxVal) {
172
+ Text(label)
173
+ }
174
+ .gaugeStyle(.accessoryLinear)
175
+ .tint(Color(hex: tintColor) ?? .accentColor)
176
+ }
177
+ }
178
+
179
+ // MARK: - ProgressView
180
+
181
+ @ViewBuilder
182
+ private func renderProgress() -> some View {
183
+ let value = prop("value")?.doubleValue ?? 0
184
+ let tintColor = prop("tint")?.stringValue
185
+
186
+ ProgressView(value: max(0, min(1, value)))
187
+ .tint(Color(hex: tintColor) ?? .accentColor)
188
+ }
189
+
190
+ // MARK: - Shape
191
+
192
+ @ViewBuilder
193
+ private func renderShape() -> some View {
194
+ let shapeType = prop("shape")?.stringValue ?? "rectangle"
195
+ let fillColor = prop("fill")?.stringValue
196
+ let strokeColor = prop("stroke")?.stringValue
197
+ let strokeWidth = prop("strokeWidth")?.doubleValue ?? 1
198
+
199
+ switch shapeType {
200
+ case "circle":
201
+ if let strokeColor = Color(hex: strokeColor) {
202
+ Circle().stroke(strokeColor, lineWidth: strokeWidth)
203
+ .background(Circle().fill(Color(hex: fillColor) ?? .clear))
204
+ } else {
205
+ Circle().fill(Color(hex: fillColor) ?? .clear)
206
+ }
207
+ case "capsule":
208
+ if let strokeColor = Color(hex: strokeColor) {
209
+ Capsule().stroke(strokeColor, lineWidth: strokeWidth)
210
+ .background(Capsule().fill(Color(hex: fillColor) ?? .clear))
211
+ } else {
212
+ Capsule().fill(Color(hex: fillColor) ?? .clear)
213
+ }
214
+ case "roundedRectangle":
215
+ let radius = prop("radius")?.doubleValue ?? 8
216
+ if let strokeColor = Color(hex: strokeColor) {
217
+ RoundedRectangle(cornerRadius: radius).stroke(strokeColor, lineWidth: strokeWidth)
218
+ .background(RoundedRectangle(cornerRadius: radius).fill(Color(hex: fillColor) ?? .clear))
219
+ } else {
220
+ RoundedRectangle(cornerRadius: radius).fill(Color(hex: fillColor) ?? .clear)
221
+ }
222
+ default: // rectangle
223
+ if let strokeColor = Color(hex: strokeColor) {
224
+ Rectangle().stroke(strokeColor, lineWidth: strokeWidth)
225
+ .background(Rectangle().fill(Color(hex: fillColor) ?? .clear))
226
+ } else {
227
+ Rectangle().fill(Color(hex: fillColor) ?? .clear)
228
+ }
229
+ }
230
+ }
231
+
232
+ // MARK: - Container
233
+
234
+ @ViewBuilder
235
+ private func renderContainer() -> some View {
236
+ // Just renders children; style is applied by applyStyle
237
+ VStack(spacing: 0) {
238
+ renderChildren()
239
+ }
240
+ }
241
+
242
+ // MARK: - Conditional
243
+
244
+ @ViewBuilder
245
+ private func renderConditional() -> some View {
246
+ let condition = prop("condition")?.stringValue ?? ""
247
+ if evaluateCondition(condition) {
248
+ renderChildren()
249
+ }
250
+ }
251
+
252
+ // MARK: - Children rendering
253
+
254
+ @ViewBuilder
255
+ private func renderChildren() -> some View {
256
+ if let children = node.children {
257
+ ForEach(children) { child in
258
+ DSLView(node: child, data: data)
259
+ .applyAnimation(child.animation)
260
+ }
261
+ }
262
+ }
263
+
264
+ // MARK: - Style application
265
+
266
+ @ViewBuilder
267
+ private func applyStyle<V: View>(to view: V) -> some View {
268
+ let styled = view
269
+ .modifier(DSLPaddingModifier(padding: node.style?.padding))
270
+ .modifier(DSLFrameModifier(frame: node.style?.frame))
271
+ .modifier(DSLBackgroundModifier(
272
+ backgroundColor: node.style?.backgroundColor,
273
+ backgroundGradient: node.style?.backgroundGradient,
274
+ cornerRadius: node.style?.cornerRadius
275
+ ))
276
+ .modifier(DSLForegroundModifier(foregroundColor: node.style?.foregroundColor))
277
+ .modifier(DSLOpacityModifier(opacity: node.style?.opacity))
278
+ .modifier(DSLShadowModifier(shadow: node.style?.shadow))
279
+
280
+ if node.style?.cornerRadius != nil && node.style?.backgroundColor == nil && node.style?.backgroundGradient == nil {
281
+ styled.clipShape(RoundedRectangle(cornerRadius: node.style!.cornerRadius!))
282
+ } else {
283
+ styled
284
+ }
285
+ }
286
+
287
+ // MARK: - Helpers
288
+
289
+ private func prop(_ key: String) -> AnyCodable? {
290
+ node.props?[key]
291
+ }
292
+
293
+ /// Replace `{{varName}}` with data dictionary values.
294
+ private func interpolate(_ text: String) -> String {
295
+ var result = text
296
+ let pattern = "\\{\\{(\\w+)\\}\\}"
297
+ guard let regex = try? NSRegularExpression(pattern: pattern) else { return text }
298
+ let matches = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
299
+
300
+ for match in matches.reversed() {
301
+ guard let keyRange = Range(match.range(at: 1), in: text),
302
+ let fullRange = Range(match.range, in: text) else { continue }
303
+ let key = String(text[keyRange])
304
+ if let replacement = data[key]?.stringValue ?? data[key]?.doubleValue.map(String.init) ?? data[key]?.intValue.map(String.init) {
305
+ result = result.replacingCharacters(in: fullRange, with: replacement)
306
+ }
307
+ }
308
+ return result
309
+ }
310
+
311
+ /// Evaluate simple conditions like `{{status}} == 'active'`
312
+ private func evaluateCondition(_ condition: String) -> Bool {
313
+ let interpolated = interpolate(condition)
314
+ // Support: `value == 'literal'` or `value != 'literal'`
315
+ if interpolated.contains("!=") {
316
+ let parts = interpolated.components(separatedBy: "!=").map { $0.trimmingCharacters(in: .whitespaces) }
317
+ guard parts.count == 2 else { return false }
318
+ return stripQuotes(parts[0]) != stripQuotes(parts[1])
319
+ } else if interpolated.contains("==") {
320
+ let parts = interpolated.components(separatedBy: "==").map { $0.trimmingCharacters(in: .whitespaces) }
321
+ guard parts.count == 2 else { return false }
322
+ return stripQuotes(parts[0]) == stripQuotes(parts[1])
323
+ }
324
+ // Truthy check: non-empty string
325
+ return !interpolated.trimmingCharacters(in: .whitespaces).isEmpty
326
+ }
327
+
328
+ private func stripQuotes(_ s: String) -> String {
329
+ var result = s
330
+ if (result.hasPrefix("'") && result.hasSuffix("'")) ||
331
+ (result.hasPrefix("\"") && result.hasSuffix("\"")) {
332
+ result.removeFirst()
333
+ result.removeLast()
334
+ }
335
+ return result
336
+ }
337
+
338
+ /// Map font preset names to sizes.
339
+ private func fontPresetSize(_ preset: String?) -> Double? {
340
+ switch preset {
341
+ case "largeTitle": return 34
342
+ case "title": return 28
343
+ case "title2": return 22
344
+ case "title3": return 20
345
+ case "headline": return 17
346
+ case "body": return 17
347
+ case "callout": return 16
348
+ case "subheadline": return 15
349
+ case "footnote": return 13
350
+ case "caption": return 12
351
+ case "caption2": return 11
352
+ default: return nil
353
+ }
354
+ }
355
+ }
356
+
357
+ // MARK: - Alignment helpers
358
+
359
+ private func horizontalAlignment(from name: String?) -> HorizontalAlignment {
360
+ switch name {
361
+ case "center": return .center
362
+ case "trailing": return .trailing
363
+ case "leading": return .leading
364
+ default: return .leading
365
+ }
366
+ }
367
+
368
+ private func verticalAlignment(from name: String?) -> VerticalAlignment {
369
+ switch name {
370
+ case "top": return .top
371
+ case "bottom": return .bottom
372
+ case "center": return .center
373
+ default: return .center
374
+ }
375
+ }
376
+
377
+ private func compositeAlignment(from name: String?) -> Alignment {
378
+ switch name {
379
+ case "topLeading": return .topLeading
380
+ case "top": return .top
381
+ case "topTrailing": return .topTrailing
382
+ case "leading": return .leading
383
+ case "center": return .center
384
+ case "trailing": return .trailing
385
+ case "bottomLeading": return .bottomLeading
386
+ case "bottom": return .bottom
387
+ case "bottomTrailing": return .bottomTrailing
388
+ default: return .center
389
+ }
390
+ }
391
+
392
+ // MARK: - Style modifiers
393
+
394
+ @available(iOS 16.2, *)
395
+ struct DSLPaddingModifier: ViewModifier {
396
+ let padding: DSLPadding?
397
+
398
+ func body(content: Content) -> some View {
399
+ guard let p = padding else { return AnyView(content) }
400
+
401
+ if let all = p.all {
402
+ return AnyView(content.padding(all))
403
+ }
404
+
405
+ var view = AnyView(content)
406
+ if let h = p.horizontal {
407
+ view = AnyView(view.padding(.horizontal, h))
408
+ }
409
+ if let v = p.vertical {
410
+ view = AnyView(view.padding(.vertical, v))
411
+ }
412
+ if let t = p.top {
413
+ view = AnyView(view.padding(.top, t))
414
+ }
415
+ if let b = p.bottom {
416
+ view = AnyView(view.padding(.bottom, b))
417
+ }
418
+ if let l = p.leading {
419
+ view = AnyView(view.padding(.leading, l))
420
+ }
421
+ if let tr = p.trailing {
422
+ view = AnyView(view.padding(.trailing, tr))
423
+ }
424
+ return view
425
+ }
426
+ }
427
+
428
+ @available(iOS 16.2, *)
429
+ struct DSLFrameModifier: ViewModifier {
430
+ let frame: DSLFrame?
431
+
432
+ func body(content: Content) -> some View {
433
+ guard let f = frame else { return AnyView(content) }
434
+
435
+ if f.width != nil || f.height != nil {
436
+ return AnyView(content.frame(
437
+ width: f.width.map { CGFloat($0) },
438
+ height: f.height.map { CGFloat($0) }
439
+ ))
440
+ }
441
+
442
+ return AnyView(content.frame(
443
+ minWidth: f.minWidth.map { CGFloat($0) },
444
+ maxWidth: f.maxWidth.map { CGFloat($0) },
445
+ minHeight: f.minHeight.map { CGFloat($0) },
446
+ maxHeight: f.maxHeight.map { CGFloat($0) }
447
+ ))
448
+ }
449
+ }
450
+
451
+ @available(iOS 16.2, *)
452
+ struct DSLBackgroundModifier: ViewModifier {
453
+ let backgroundColor: String?
454
+ let backgroundGradient: DSLGradient?
455
+ let cornerRadius: Double?
456
+
457
+ func body(content: Content) -> some View {
458
+ let radius = cornerRadius ?? 0
459
+
460
+ if let gradient = backgroundGradient, !gradient.colors.isEmpty {
461
+ let colors = gradient.colors.compactMap { Color(hex: $0) }
462
+ let start = unitPoint(from: gradient.startPoint ?? "top")
463
+ let end = unitPoint(from: gradient.endPoint ?? "bottom")
464
+ return AnyView(
465
+ content.background(
466
+ LinearGradient(colors: colors, startPoint: start, endPoint: end)
467
+ .clipShape(RoundedRectangle(cornerRadius: radius))
468
+ )
469
+ )
470
+ } else if let bgColor = Color(hex: backgroundColor) {
471
+ return AnyView(
472
+ content.background(
473
+ bgColor.clipShape(RoundedRectangle(cornerRadius: radius))
474
+ )
475
+ )
476
+ }
477
+ return AnyView(content)
478
+ }
479
+
480
+ private func unitPoint(from name: String) -> UnitPoint {
481
+ switch name {
482
+ case "top": return .top
483
+ case "bottom": return .bottom
484
+ case "leading": return .leading
485
+ case "trailing": return .trailing
486
+ case "topLeading": return .topLeading
487
+ case "topTrailing": return .topTrailing
488
+ case "bottomLeading": return .bottomLeading
489
+ case "bottomTrailing": return .bottomTrailing
490
+ case "center": return .center
491
+ default: return .top
492
+ }
493
+ }
494
+ }
495
+
496
+ @available(iOS 16.2, *)
497
+ struct DSLForegroundModifier: ViewModifier {
498
+ let foregroundColor: String?
499
+
500
+ func body(content: Content) -> some View {
501
+ if let color = Color(hex: foregroundColor) {
502
+ AnyView(content.foregroundStyle(color))
503
+ } else {
504
+ AnyView(content)
505
+ }
506
+ }
507
+ }
508
+
509
+ @available(iOS 16.2, *)
510
+ struct DSLOpacityModifier: ViewModifier {
511
+ let opacity: Double?
512
+
513
+ func body(content: Content) -> some View {
514
+ if let opacity = opacity {
515
+ content.opacity(opacity)
516
+ } else {
517
+ content
518
+ }
519
+ }
520
+ }
521
+
522
+ @available(iOS 16.2, *)
523
+ struct DSLShadowModifier: ViewModifier {
524
+ let shadow: DSLShadow?
525
+
526
+ func body(content: Content) -> some View {
527
+ if let s = shadow {
528
+ content.shadow(
529
+ color: Color(hex: s.color) ?? .black.opacity(0.2),
530
+ radius: s.radius ?? 4,
531
+ x: s.x ?? 0,
532
+ y: s.y ?? 2
533
+ )
534
+ } else {
535
+ content
536
+ }
537
+ }
538
+ }