react-native-webrtc-kaleidoscope 1.0.0 → 1.1.0

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 (110) hide show
  1. package/README.md +31 -4
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/java/com/simiancraft/kaleidoscope/Registration.kt +2 -2
  4. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectProcessor.kt +1 -1
  5. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Shaders.kt +13 -51
  6. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/ShadersGenerated.kt +64 -0
  7. package/app.plugin.js +254 -4
  8. package/dist/backgrounds/index.d.ts +3 -0
  9. package/dist/backgrounds/index.d.ts.map +1 -0
  10. package/dist/backgrounds/index.js +6 -0
  11. package/dist/backgrounds/index.js.map +1 -0
  12. package/dist/backgrounds/office-1.d.ts +3 -0
  13. package/dist/backgrounds/office-1.d.ts.map +1 -0
  14. package/dist/backgrounds/office-1.js +5 -0
  15. package/dist/backgrounds/office-1.js.map +1 -0
  16. package/dist/backgrounds/office-1.web.d.ts +3 -0
  17. package/dist/backgrounds/office-1.web.d.ts.map +1 -0
  18. package/dist/backgrounds/office-1.web.js +8 -0
  19. package/dist/backgrounds/office-1.web.js.map +1 -0
  20. package/dist/backgrounds/office-1.webp +0 -0
  21. package/dist/backgrounds/office-2.d.ts +3 -0
  22. package/dist/backgrounds/office-2.d.ts.map +1 -0
  23. package/dist/backgrounds/office-2.js +5 -0
  24. package/dist/backgrounds/office-2.js.map +1 -0
  25. package/dist/backgrounds/office-2.web.d.ts +3 -0
  26. package/dist/backgrounds/office-2.web.d.ts.map +1 -0
  27. package/dist/backgrounds/office-2.web.js +8 -0
  28. package/dist/backgrounds/office-2.web.js.map +1 -0
  29. package/dist/backgrounds/office-2.webp +0 -0
  30. package/dist/backgrounds/preset-source.types.d.ts +2 -0
  31. package/dist/backgrounds/preset-source.types.d.ts.map +1 -0
  32. package/dist/backgrounds/preset-source.types.js +2 -0
  33. package/dist/backgrounds/preset-source.types.js.map +1 -0
  34. package/dist/{backgrounds.d.ts → backgrounds/presets.d.ts} +1 -1
  35. package/dist/backgrounds/presets.d.ts.map +1 -0
  36. package/dist/{backgrounds.js → backgrounds/presets.js} +1 -1
  37. package/dist/backgrounds/presets.js.map +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +34 -12
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.web.d.ts +11 -1
  42. package/dist/index.web.d.ts.map +1 -1
  43. package/dist/index.web.js +30 -7
  44. package/dist/index.web.js.map +1 -1
  45. package/dist/livekit.d.ts +24 -0
  46. package/dist/livekit.d.ts.map +1 -0
  47. package/dist/livekit.js +57 -0
  48. package/dist/livekit.js.map +1 -0
  49. package/dist/web/effects/blur.d.ts.map +1 -1
  50. package/dist/web/effects/blur.js +34 -1
  51. package/dist/web/effects/blur.js.map +1 -1
  52. package/dist/web/effects/passthrough.d.ts.map +1 -1
  53. package/dist/web/effects/passthrough.js +2 -2
  54. package/dist/web/effects/passthrough.js.map +1 -1
  55. package/dist/web/insertable-streams.d.ts +11 -1
  56. package/dist/web/insertable-streams.d.ts.map +1 -1
  57. package/dist/web/insertable-streams.js +22 -4
  58. package/dist/web/insertable-streams.js.map +1 -1
  59. package/dist/web/shaders.d.ts +1 -3
  60. package/dist/web/shaders.d.ts.map +1 -1
  61. package/dist/web/shaders.generated.d.ts +4 -0
  62. package/dist/web/shaders.generated.d.ts.map +1 -0
  63. package/dist/web/shaders.generated.js +54 -0
  64. package/dist/web/shaders.generated.js.map +1 -0
  65. package/dist/web/shaders.js +29 -103
  66. package/dist/web/shaders.js.map +1 -1
  67. package/ios/Kaleidoscope.podspec +75 -12
  68. package/ios/KaleidoscopeModule/Registration.swift +35 -13
  69. package/ios/KaleidoscopeModule/effects/BackgroundImageProcessor.swift +226 -0
  70. package/ios/KaleidoscopeModule/effects/BlurProcessor.swift +172 -18
  71. package/ios/KaleidoscopeModule/effects/FrameBridge.swift +40 -0
  72. package/ios/KaleidoscopeModule/effects/MirrorProcessor.swift +129 -10
  73. package/ios/KaleidoscopeModule/gpu/MetalRenderer.swift +423 -0
  74. package/ios/KaleidoscopeModule/gpu/ShaderLibrary.swift +100 -0
  75. package/ios/KaleidoscopeModule/gpu/TextureBridge.swift +120 -0
  76. package/ios/KaleidoscopeModule/resources/backgrounds/office-1.png +0 -0
  77. package/ios/KaleidoscopeModule/resources/backgrounds/office-2.png +0 -0
  78. package/ios/KaleidoscopeModule/segmentation/MaskTuning.swift +23 -0
  79. package/ios/KaleidoscopeModule/segmentation/Segmenter.swift +120 -24
  80. package/ios/KaleidoscopeModule/shaders/SHADERS.txt +5 -0
  81. package/ios/KaleidoscopeModule/shaders/blur.metalsrc +72 -0
  82. package/ios/KaleidoscopeModule/shaders/composite.metalsrc +22 -0
  83. package/ios/KaleidoscopeModule/shaders/nebula.metalsrc +72 -0
  84. package/ios/KaleidoscopeModule/shaders/passthrough.metalsrc +20 -0
  85. package/ios/KaleidoscopeModule/shaders/simianlights.metalsrc +72 -0
  86. package/package.json +44 -9
  87. package/src/backgrounds/README.md +78 -0
  88. package/src/backgrounds/assets.d.ts +7 -0
  89. package/src/backgrounds/index.ts +7 -0
  90. package/src/backgrounds/office-1.ts +6 -0
  91. package/src/backgrounds/office-1.web.ts +9 -0
  92. package/src/backgrounds/office-1.webp +0 -0
  93. package/src/backgrounds/office-2.ts +6 -0
  94. package/src/backgrounds/office-2.web.ts +9 -0
  95. package/src/backgrounds/office-2.webp +0 -0
  96. package/src/backgrounds/preset-source.types.ts +5 -0
  97. package/src/index.ts +45 -17
  98. package/src/index.web.ts +40 -8
  99. package/src/livekit.ts +65 -0
  100. package/src/web/effects/blur.ts +36 -1
  101. package/src/web/effects/passthrough.ts +2 -2
  102. package/src/web/insertable-streams.ts +33 -5
  103. package/src/web/shaders.generated.ts +56 -0
  104. package/src/web/shaders.ts +29 -106
  105. package/dist/backgrounds.d.ts.map +0 -1
  106. package/dist/backgrounds.js.map +0 -1
  107. package/plugin/build/withKaleidoscope.d.ts +0 -3
  108. package/plugin/build/withKaleidoscope.js +0 -14
  109. package/plugin/build/withKaleidoscope.js.map +0 -1
  110. /package/src/{backgrounds.ts → backgrounds/presets.ts} +0 -0
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## Status
16
16
 
17
- **Active development; not yet production-ready.** Published to npm as `0.1.0-alpha.x` for name reservation and integration testing. The npm presentation, marketing, and release-quality polish will come in a later pass; right now the README's job is to tell the truth about what works.
17
+ **Active development; not yet production-ready.** Published to npm at `1.0.0` (semantic-release cut the first tag at 1.0.0; the number reflects release automation, not a maturity claim). The npm presentation, marketing, and release-quality polish will come in a later pass; right now the README's job is to tell the truth about what works.
18
18
 
19
19
  ### What works today
20
20
 
@@ -27,12 +27,11 @@
27
27
  |---|---|---|---|---|
28
28
  | Web (Chrome / Edge) | ✓ | ✓ | ✓ | MediaStreamTrackProcessor + MediaPipe Selfie Segmentation (WASM, CDN) |
29
29
  | Android (API 24+) | ✓ | ✓ | ✓ | OpenGL ES 3.0 + MLKit Selfie Segmentation |
30
- | iOS (≥ 15) | | | | Coming soon; transpilation pipeline in place, host implementation pending |
30
+ | iOS (≥ 15) | | | | Metal + Vision (person segmentation), verified on device. Older A11 devices (iPhone X) run at a lower frame rate |
31
31
  | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a typed error |
32
32
 
33
33
  ### Coming soon
34
34
 
35
- - **iOS support**, via the canonical GLSL transpiled to Metal Shading Language (`glslangValidator` → `spirv-cross --msl`). The pipeline is in the repo at `scripts/transpile-shaders.ts` and the GLSL canonical source lives in `shaders/`. The Swift host code that loads the metallib and runs the Metal pipeline is the next chunk of work.
36
35
  - **Procedural backgrounds** (animated shaders behind the person, not just still images). Same composite path; the only new piece is each effect's background producer.
37
36
  - A careful pass over the npm presentation, install docs, and demo polish before any "we recommend you use this" framing.
38
37
 
@@ -54,6 +53,24 @@ bun add @livekit/react-native @livekit/react-native-webrtc react-native-webrtc-k
54
53
 
55
54
  Pick one fork. Installing both upstream `react-native-webrtc` and `@livekit/react-native-webrtc` in the same app will cause native class collisions; that's the consumer's problem to resolve.
56
55
 
56
+ **Native wiring.** `@livekit/react-native` hands you a `LocalVideoTrack`; apply effects to its underlying `MediaStreamTrack`:
57
+
58
+ ```ts
59
+ import { applyVideoEffects } from 'react-native-webrtc-kaleidoscope';
60
+
61
+ applyVideoEffects(localCameraTrack.mediaStreamTrack, ['blur']);
62
+ ```
63
+
64
+ **Web wiring.** On web, LiveKit owns the `RTCRtpSender`, so you cannot swap the track yourself; go through LiveKit's processor API instead. The opt-in `/livekit` subpath ships a ready-made processor (it needs `livekit-client`, which a LiveKit app already has):
65
+
66
+ ```ts
67
+ import { KaleidoscopeProcessor } from 'react-native-webrtc-kaleidoscope/livekit';
68
+
69
+ await localVideoTrack.setProcessor(new KaleidoscopeProcessor(['blur']), true);
70
+ ```
71
+
72
+ The second argument shows the processed stream in your local preview. The processor tears down its Insertable-Streams pipeline on camera flip (`restart`) and unpublish (`destroy`), so repeated flips do not leak generators.
73
+
57
74
  ## Configure
58
75
 
59
76
  Add the config plugin to `app.config.ts`:
@@ -110,6 +127,16 @@ Effects chain in array order.
110
127
 
111
128
  The library ships neutral defaults (8, 0.5, 0.5) and consumers tune at runtime via the API above; whether to ship the dialed-in values as platform-specific defaults is an open question waiting on iOS data.
112
129
 
130
+ ## Web and native differences
131
+
132
+ The API surface is the same across platforms, but the runtimes differ in ways worth knowing before you wire effects in:
133
+
134
+ - **Effect parameters.** Web reads tuning from the global setters (`setBlurSigma`, `setMaskHardness`, `setMaskThreshold`) on the next frame. Native currently ignores per-call `EffectSpec` parameters such as `{ name: 'blur', sigma: 12 }`; tuning is global through the same setters. Per-call uniforms through the native registry are a follow-up.
135
+ - **Background source.** `background-image.source` is a bundled preset name on native (the upstream `_setVideoEffects` registry is keyed by flat strings, not URIs), but on web it accepts either a preset name or an arbitrary image URL or data URI.
136
+ - **Background presets ship as tree-shakeable files.** Two example backgrounds (`office-1`, `office-2`) are importable per preset: `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`. Each preset is its own file behind its own subpath export, and the package sets `sideEffects: false`, so an unused preset is dropped by web bundlers — and, since Metro doesn't tree-shake, simply never imported on native. Web resolves the bundled WebP to a URL; native loads its own bundled copy by name. Web also still accepts an arbitrary image URL or data URI. See [`src/backgrounds/README.md`](./src/backgrounds/README.md).
137
+ - **Segmentation model on web.** The web blur and background-image effects load MediaPipe Selfie Segmentation from the jsdelivr CDN (`cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation`) on first use. A strict Content-Security-Policy must allow that origin for `script-src`, `connect-src`, and the WASM fetch, and the effects do not work offline. Mirror needs no model.
138
+ - **Browser support on web.** Effects use Insertable Streams (`MediaStreamTrackProcessor` and `MediaStreamTrackGenerator`), which ship in Chromium-based browsers; Safari and Firefox throw a typed capability error.
139
+
113
140
  ## What this isn't
114
141
 
115
142
  - **Not a fork of `react-native-webrtc`.** A thin layer over its undocumented `_setVideoEffects` registry on native, and `MediaStreamTrackProcessor` on web. Install alongside `react-native-webrtc`.
@@ -124,7 +151,7 @@ The codebase lives across four surfaces:
124
151
  - `src/` — JS facade and shared types. `applyVideoEffects(track, effects)` plus runtime tuning setters.
125
152
  - `src/web/` — WebGL2 pipeline. MediaPipe segmentation + GLSL composite. One shader file per stage in `src/web/shaders.ts`.
126
153
  - `android/` — OpenGL ES 3.0 pipeline. MLKit segmentation (async, worker-thread, last-known-mask cache) + GLSL composite. Shaders inline in `gpu/Shaders.kt` as `const val` strings.
127
- - `ios/` — Scaffold only; the canonical GLSL in `shaders/` will transpile to Metal Shading Language for the iOS path.
154
+ - `ios/` — Metal pipeline (Swift) with Vision person segmentation. The canonical GLSL in `shaders/` transpiles to Metal Shading Language via `scripts/build-shaders.ts`. Implemented and verified on device.
128
155
 
129
156
  The composite shader (`shaders/composite.frag`) is the same GLSL source for every effect category (blur, background-image, future procedural backgrounds). Per-effect difference is upstream of the composite: how the `uBackground` texture gets produced.
130
157
 
@@ -4,7 +4,9 @@ apply plugin: 'com.android.library'
4
4
  apply plugin: 'kotlin-android'
5
5
 
6
6
  group = 'com.simiancraft.kaleidoscope'
7
- version = '0.1.0-alpha.1'
7
+ // Single source of truth for the version is package.json (the iOS podspec reads
8
+ // it the same way), so the three platforms cannot drift apart.
9
+ version = new groovy.json.JsonSlurper().parseText(file("../package.json").text).version
8
10
 
9
11
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
12
  if (expoModulesCorePlugin.exists()) {
@@ -32,8 +32,8 @@ object Registration {
32
32
  BackgroundImageFactory(context, "office-2"),
33
33
  )
34
34
 
35
- // Architecture-proof hook from PLAN.md Commit 3. Removed in the cleanup
36
- // pass before v0.1 ships.
35
+ // Temporary architecture-proof passthrough hook from the GPU bring-up.
36
+ // Removed in the cleanup pass before v0.1 ships.
37
37
  ProcessorProvider.addProcessor("gpu-passthrough", GpuEffectFactory())
38
38
  }
39
39
  }
@@ -2,7 +2,7 @@
2
2
  // invokes process() per frame on the SurfaceTextureHelper's GL thread, so
3
3
  // every GL call is implicitly on the correct context.
4
4
  //
5
- // Commit 3 of PLAN.md: this version is a passthrough — sample the OES
5
+ // Initial GPU bring-up: this version is a passthrough — sample the OES
6
6
  // camera texture and emit it as a 2D-backed VideoFrame, no other math.
7
7
  // Architecture proof for the OES -> shader -> TextureBufferImpl -> renderer
8
8
  // round-trip. Effect-specific math (blur, composite, image background) lands
@@ -15,15 +15,10 @@ internal object Shaders {
15
15
  // 1 -> (2,0) -> ( 1,-1), uv (1,0)
16
16
  // 2 -> (0,2) -> (-1, 1), uv (0,1)
17
17
  // 3 -> (2,2) -> ( 1, 1), uv (1,1)
18
- const val PASSTHROUGH_VERT = """#version 300 es
19
- precision highp float;
20
- out vec2 vUv;
21
- void main() {
22
- vec2 p = vec2(float((gl_VertexID & 1) << 1), float(gl_VertexID & 2));
23
- vUv = p * 0.5;
24
- gl_Position = vec4(p - 1.0, 0.0, 1.0);
25
- }
26
- """
18
+ // Canonical source: shaders/passthrough.vert. Generated into
19
+ // ShadersGenerated by `bun run build:shaders`; delegated here so call sites
20
+ // keep using Shaders.PASSTHROUGH_VERT.
21
+ val PASSTHROUGH_VERT = ShadersGenerated.PASSTHROUGH_VERT
27
22
 
28
23
  // Sample the OES external camera texture and emit as 2D RGBA. The first
29
24
  // pass of every effect runs this so subsequent passes can use sampler2D.
@@ -56,24 +51,10 @@ void main() {
56
51
  // uAxis is (1/width, 0) for the horizontal pass and (0, 1/height) for the
57
52
  // vertical pass. uOffsets[0] is the center tap (zero); offsets are in
58
53
  // pixel units, so the shader multiplies by uAxis to convert to UV space.
59
- const val BLUR_FRAG = """#version 300 es
60
- precision mediump float;
61
- uniform sampler2D uTex;
62
- uniform vec2 uAxis;
63
- uniform float uWeights[9];
64
- uniform float uOffsets[9];
65
- in highp vec2 vUv;
66
- out vec4 oColor;
67
- void main() {
68
- vec4 color = texture(uTex, vUv) * uWeights[0];
69
- for (int i = 1; i < 9; i++) {
70
- vec2 off = uAxis * uOffsets[i];
71
- color += texture(uTex, vUv + off) * uWeights[i];
72
- color += texture(uTex, vUv - off) * uWeights[i];
73
- }
74
- oColor = color;
75
- }
76
- """
54
+ // Canonical source: shaders/blur.frag. Generated into ShadersGenerated by
55
+ // `bun run build:shaders`; delegated here. BlurFactory computes the 9 weights
56
+ // and offsets from sigma on the CPU and uploads the arrays each program use.
57
+ val BLUR_FRAG = ShadersGenerated.BLUR_FRAG
77
58
 
78
59
  // Composite: mix(background, original, mask). One shader, byte-identical
79
60
  // to src/web/shaders.ts's COMPOSITE_FRAG_SRC.
@@ -102,28 +83,9 @@ void main() {
102
83
  // blurred copy. For background-image, the caller computes them to perform
103
84
  // a cover-fit center crop so an arbitrarily-shaped image fills the output
104
85
  // without distortion.
105
- const val COMPOSITE_FRAG = """#version 300 es
106
- precision mediump float;
107
- uniform sampler2D uOriginal;
108
- uniform sampler2D uBackground;
109
- uniform sampler2D uMask;
110
- uniform vec2 uBgUvScale;
111
- uniform vec2 uBgUvOffset;
112
- uniform vec2 uMaskUvScale;
113
- uniform vec2 uMaskUvOffset;
114
- uniform float uMaskLo;
115
- uniform float uMaskHi;
116
- in highp vec2 vUv;
117
- out vec4 oColor;
118
- void main() {
119
- vec2 maskUv = vUv * uMaskUvScale + uMaskUvOffset;
120
- float raw = texture(uMask, maskUv).r;
121
- float safeHi = max(uMaskHi, uMaskLo + 0.001);
122
- float m = smoothstep(uMaskLo, safeHi, raw);
123
- vec3 orig = texture(uOriginal, vUv).rgb;
124
- vec2 bgUv = clamp(vUv * uBgUvScale + uBgUvOffset, 0.0, 1.0);
125
- vec3 bg = texture(uBackground, bgUv).rgb;
126
- oColor = vec4(mix(bg, orig, m), 1.0);
127
- }
128
- """
86
+ // Canonical source: shaders/composite.frag. Generated into ShadersGenerated
87
+ // by `bun run build:shaders`; delegated here. The per-platform mask/bg
88
+ // orientation differences live in the uniforms the host sets (see above),
89
+ // not in the shader, so the same generated body serves every runtime.
90
+ val COMPOSITE_FRAG = ShadersGenerated.COMPOSITE_FRAG
129
91
  }
@@ -0,0 +1,64 @@
1
+ // @generated by scripts/build-shaders.ts from shaders/. DO NOT EDIT.
2
+ // Run `bun run build:shaders` to regenerate.
3
+ //
4
+ // Cross-runtime shared shaders. Platform-local shaders (OES external texture,
5
+ // 2D passthrough) stay hand-written in Shaders.kt, which delegates the consts
6
+ // below.
7
+
8
+ package com.simiancraft.kaleidoscope.gpu
9
+
10
+ internal object ShadersGenerated {
11
+ const val PASSTHROUGH_VERT = """#version 300 es
12
+ precision highp float;
13
+ out highp vec2 vUv;
14
+ void main() {
15
+ vec2 p = vec2(float((gl_VertexID & 1) << 1), float(gl_VertexID & 2));
16
+ vUv = p * 0.5;
17
+ gl_Position = vec4(p - 1.0, 0.0, 1.0);
18
+ }
19
+ """
20
+
21
+ const val COMPOSITE_FRAG = """#version 300 es
22
+ precision mediump float;
23
+ uniform sampler2D uOriginal;
24
+ uniform sampler2D uBackground;
25
+ uniform sampler2D uMask;
26
+ uniform vec2 uBgUvScale;
27
+ uniform vec2 uBgUvOffset;
28
+ uniform vec2 uMaskUvScale;
29
+ uniform vec2 uMaskUvOffset;
30
+ uniform float uMaskLo;
31
+ uniform float uMaskHi;
32
+ in highp vec2 vUv;
33
+ out vec4 oColor;
34
+ void main() {
35
+ vec2 maskUv = vUv * uMaskUvScale + uMaskUvOffset;
36
+ float raw = texture(uMask, maskUv).r;
37
+ float safeHi = max(uMaskHi, uMaskLo + 0.001);
38
+ float m = smoothstep(uMaskLo, safeHi, raw);
39
+ vec3 orig = texture(uOriginal, vUv).rgb;
40
+ vec2 bgUv = clamp(vUv * uBgUvScale + uBgUvOffset, 0.0, 1.0);
41
+ vec3 bg = texture(uBackground, bgUv).rgb;
42
+ oColor = vec4(mix(bg, orig, m), 1.0);
43
+ }
44
+ """
45
+
46
+ const val BLUR_FRAG = """#version 300 es
47
+ precision mediump float;
48
+ uniform sampler2D uTex;
49
+ uniform vec2 uAxis;
50
+ uniform float uWeights[9];
51
+ uniform float uOffsets[9];
52
+ in highp vec2 vUv;
53
+ out vec4 oColor;
54
+ void main() {
55
+ vec4 color = texture(uTex, vUv) * uWeights[0];
56
+ for (int i = 1; i < 9; i++) {
57
+ vec2 off = uAxis * uOffsets[i];
58
+ color += texture(uTex, vUv + off) * uWeights[i];
59
+ color += texture(uTex, vUv - off) * uWeights[i];
60
+ }
61
+ oColor = color;
62
+ }
63
+ """
64
+ }
package/app.plugin.js CHANGED
@@ -1,5 +1,255 @@
1
- // Expo config plugin entry. Compiled output lives in plugin/build/.
2
- // See plugin/src/withKaleidoscope.ts for the plugin implementation.
3
- import withKaleidoscope from './plugin/build/withKaleidoscope.js';
1
+ // Expo config plugin. Native registration happens via the Expo Module's
2
+ // OnCreate hook (see android/.../KaleidoscopeModule.kt and
3
+ // ios/.../KaleidoscopeModule.swift), not through a plugin-time mod.
4
+ //
5
+ // This plugin's job is narrowly scoped: patch the consumer's iOS build to
6
+ // satisfy this library's hard requirements that cannot be expressed in the
7
+ // Expo Module manifest or the podspec alone. Today that means two patches,
8
+ // both installed by a single iOS `dangerous` mod:
9
+ //
10
+ // 1. Declare `pod 'react-native-webrtc', :modular_headers => true` in the
11
+ // generated Podfile so Swift sources can `import react_native_webrtc`
12
+ // (see modular-headers detail below).
13
+ // 2. Raise `ios.deploymentTarget` in `Podfile.properties.json` to the value
14
+ // required by `ios/Kaleidoscope.podspec`. The default Expo Podfile
15
+ // platform (iOS 13.4) is lower than this library's minimum (iOS 15.0
16
+ // for Apple Vision person segmentation), and `expo-modules-autolinking`
17
+ // silently drops pods whose declared minimum exceeds the Podfile
18
+ // platform — the library would build, install, and then be absent from
19
+ // the generated `ExpoModulesProvider.swift` at runtime.
20
+ //
21
+ // Bounding invariant for future patches in this mod:
22
+ // Every patch this mod installs must be (a) idempotent across re-prebuilds,
23
+ // (b) non-downgrading (never reduce a value the consumer or another plugin
24
+ // set higher), and (c) recoverable via a logged manual instruction on I/O
25
+ // failure (no throwing — prebuild must keep going).
26
+ //
27
+ // Ordering note: this mod must run AFTER any consumer plugin that writes the
28
+ // same files (notably `expo-build-properties`). Expo executes plugins in
29
+ // declaration order from `plugins[]`, so consumers must list this plugin
30
+ // AFTER `expo-build-properties` in their `app.config.js`. The demo's
31
+ // `app.config.js` already does so.
32
+ //
33
+ // Modular-headers detail: our Swift sources do `import react_native_webrtc`
34
+ // to reach the Obj-C `ProcessorProvider` class and `VideoFrameProcessorDelegate`
35
+ // protocol that live inside the react-native-webrtc pod. For a Swift target to
36
+ // `import` an Objective-C CocoaPod as a Clang module, that pod must be built with
37
+ // modular headers. react-native-webrtc is NOT built with modular headers by
38
+ // default in a React Native / Expo app, so a default prebuild produces Swift
39
+ // that fails to compile with "no such module 'react_native_webrtc'".
40
+ //
41
+ // We deliberately do NOT emit a global `use_modular_headers!`: that flips every
42
+ // pod to build as a Clang module, which regularly breaks React Native core
43
+ // pods that ship non-modular umbrella headers. A single per-pod opt-in is the
44
+ // narrow, supported fix that react-native-webrtc's own docs recommend.
45
+ //
46
+ // WHY this file requires nothing but Node builtins (no @expo/config-plugins):
47
+ // Expo's plugin resolver hardcodes the entry filename to `app.plugin.js` and
48
+ // loads it with `require()` from the file's REAL path. In the demo we consume
49
+ // this library via `file:..`, so on EAS the realpath is the repo root, where
50
+ // there is no node_modules (EAS only installs the demo subdirectory). A
51
+ // top-level `require('@expo/config-plugins')` therefore throws
52
+ // "Cannot find module '@expo/config-plugins'" on the EAS worker. Registering
53
+ // the dangerous mod by mutating `config.mods.ios.dangerous` directly removes
54
+ // that dependency, so the plugin loads identically from the symlinked demo, a
55
+ // normally-installed external consumer, and the EAS worker. The mod contract is
56
+ // the one @expo/config-plugins' own dangerous base provider calls: it invokes
57
+ // our mod as `nextMod({ ...config, modResults, modRequest })` and only requires
58
+ // the returned value to be the config object (it asserts `.mods` exists).
59
+ //
60
+ // This file is CommonJS, and the package is deliberately `type: commonjs`:
61
+ // Expo's plugin resolver loads app.plugin.js with `require()`, and a CommonJS
62
+ // entry sidesteps ESM-interop variance across the Node versions EAS workers run
63
+ // (older SDK images run Node 18, which cannot `require()` an ESM module at all;
64
+ // newer images run Node 20/22). The ESM-authored library source lives in `src/`
65
+ // and is consumed by Metro via the `react-native` export condition, never
66
+ // loaded by Node.
4
67
 
5
- export default withKaleidoscope;
68
+ const fs = require('node:fs');
69
+ const path = require('node:path');
70
+
71
+ // A sentinel comment lets us find our own injection on re-prebuilds and stay
72
+ // idempotent regardless of how Expo regenerates the surrounding Podfile.
73
+ const SENTINEL = '# react-native-webrtc-kaleidoscope: modular headers (managed)';
74
+
75
+ // Mirrors ios/Kaleidoscope.podspec :ios => '15.0'. Bumping the podspec
76
+ // requires bumping this constant; the drift cost is a silent pod-install
77
+ // drop (the bug this plan fixes).
78
+ const IOS_DEPLOYMENT_TARGET = '15.0';
79
+
80
+ // Compare two dotted version strings element-wise. Returns true iff `existing`
81
+ // is strictly less than `target`. Missing/empty existing counts as "less than".
82
+ // We avoid `semver` so this file stays dependency-free for EAS workers that
83
+ // don't install the library's node_modules. The library's iOS deployment
84
+ // target is a plain three-part version like '15.0' (or '15.0.1' if Apple ever
85
+ // requires it); pre-release/build metadata is not part of the contract.
86
+ function isVersionLessThan(existing, target) {
87
+ if (typeof existing !== 'string' || existing.length === 0) {
88
+ return true;
89
+ }
90
+ const toParts = (s) => s.split('.').map((part) => Number(part) || 0);
91
+ const a = toParts(existing);
92
+ const b = toParts(target);
93
+ const max = Math.max(a.length, b.length);
94
+ for (let i = 0; i < max; i += 1) {
95
+ const av = a[i] || 0;
96
+ const bv = b[i] || 0;
97
+ if (av < bv) return true;
98
+ if (av > bv) return false;
99
+ }
100
+ return false; // equal
101
+ }
102
+
103
+ // Resolve which react-native-webrtc fork the consumer installed, and return its
104
+ // CocoaPods pod name + npm package directory (used to build the `:path` for
105
+ // the Podfile declaration). Two forks ship the same JS/native surface under
106
+ // different names (mirrors the dual probe in android/build.gradle):
107
+ // - @livekit/react-native-webrtc -> pod `livekit-react-native-webrtc`
108
+ // - react-native-webrtc -> pod `react-native-webrtc`
109
+ // We prefer the fork when both are present, matching the Swift import order
110
+ // (`#if canImport(livekit_react_native_webrtc)` first). Declaring a pod for a
111
+ // package that is not installed would break `pod install`, so we return null
112
+ // (and skip patching) when neither is found.
113
+ function resolveWebrtcPod(projectRoot) {
114
+ if (!projectRoot) {
115
+ return { podName: 'react-native-webrtc', packageDir: 'react-native-webrtc' };
116
+ }
117
+ const fork = path.join(projectRoot, 'node_modules', '@livekit', 'react-native-webrtc');
118
+ const upstream = path.join(projectRoot, 'node_modules', 'react-native-webrtc');
119
+ if (fs.existsSync(fork)) {
120
+ return { podName: 'livekit-react-native-webrtc', packageDir: '@livekit/react-native-webrtc' };
121
+ }
122
+ if (fs.existsSync(upstream)) {
123
+ return { podName: 'react-native-webrtc', packageDir: 'react-native-webrtc' };
124
+ }
125
+ return null;
126
+ }
127
+
128
+ // Ensure the Podfile builds the resolved react-native-webrtc pod with modular
129
+ // headers so our Swift can `import` it as a Clang module. Declare with an
130
+ // explicit `:path` (instead of a bare `pod 'name'`) so the build works even
131
+ // when RN autolinking does not register the pod for us — e.g. react-native-webrtc's
132
+ // own `react-native.config.js` only sets `module.exports` under
133
+ // `--use-react-native-macos`, and on EAS workers `use_native_modules!` has been
134
+ // observed to skip the pod entirely as a result. With `:path` provided here,
135
+ // CocoaPods resolves the local podspec directly; when autolinking also picks
136
+ // it up, the two identical-path declarations are merged and modular_headers
137
+ // is applied. Idempotent: running prebuild twice neither duplicates the line
138
+ // nor corrupts the Podfile.
139
+ function patchPodfile(contents, pod) {
140
+ if (contents.includes(SENTINEL)) {
141
+ return contents;
142
+ }
143
+
144
+ const block = `${SENTINEL}\n pod '${pod.podName}', :path => '../node_modules/${pod.packageDir}', :modular_headers => true`;
145
+ const lines = contents.split('\n');
146
+
147
+ // Insert just inside the first `target ... do` block so the per-pod
148
+ // declaration sits in the same scope as the autolinked React Native pods.
149
+ const targetIndex = lines.findIndex((line) => /^\s*target\s+['"].*['"]\s+do\b/.test(line));
150
+ if (targetIndex !== -1) {
151
+ lines.splice(targetIndex + 1, 0, block);
152
+ return lines.join('\n');
153
+ }
154
+
155
+ // No `target` block found (unexpected for an Expo-generated Podfile); append
156
+ // the declaration so the build requirement is at least present.
157
+ return `${contents.trimEnd()}\n${block}\n`;
158
+ }
159
+
160
+ const withKaleidoscope = (config) => {
161
+ if (!config.mods) {
162
+ config.mods = {};
163
+ }
164
+ if (!config.mods.ios) {
165
+ config.mods.ios = {};
166
+ }
167
+
168
+ // Chain any previously registered iOS dangerous mod so we cooperate with
169
+ // other plugins instead of clobbering them.
170
+ const previousMod = config.mods.ios.dangerous;
171
+
172
+ config.mods.ios.dangerous = async (modConfig) => {
173
+ const result = typeof previousMod === 'function' ? await previousMod(modConfig) : modConfig;
174
+ const modRequest = result.modRequest || {};
175
+ const platformProjectRoot = modRequest.platformProjectRoot;
176
+ const pod = resolveWebrtcPod(modRequest.projectRoot);
177
+ if (platformProjectRoot && pod) {
178
+ const podfilePath = path.join(platformProjectRoot, 'Podfile');
179
+ try {
180
+ const original = fs.readFileSync(podfilePath, 'utf8');
181
+ const patched = patchPodfile(original, pod);
182
+ if (patched !== original) {
183
+ fs.writeFileSync(podfilePath, patched);
184
+ }
185
+ } catch (error) {
186
+ // Non-fatal: surface a clear instruction rather than failing prebuild.
187
+ const manualLine = `pod '${pod.podName}', :path => '../node_modules/${pod.packageDir}', :modular_headers => true`;
188
+ console.warn(
189
+ `[react-native-webrtc-kaleidoscope] Could not patch the Podfile to build ${pod.podName} with modular headers; add "${manualLine}" inside your app target manually. ${String(error)}`,
190
+ );
191
+ }
192
+ }
193
+ if (platformProjectRoot) {
194
+ // Raise the consumer's iOS deployment target via Podfile.properties.json.
195
+ // The generated Podfile reads `podfile_properties['ios.deploymentTarget']`
196
+ // at the top, so this is the canonical, no-Podfile-edit override seam.
197
+ // `expo-modules-autolinking`'s package-filter silently drops any pod
198
+ // whose declared minimum platform exceeds the Podfile platform; without
199
+ // this bump the Kaleidoscope pod is dropped and the runtime module
200
+ // lookup throws.
201
+ const propsPath = path.join(platformProjectRoot, 'Podfile.properties.json');
202
+ try {
203
+ let parsed = {};
204
+ try {
205
+ const raw = fs.readFileSync(propsPath, 'utf8');
206
+ try {
207
+ const candidate = JSON.parse(raw);
208
+ if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
209
+ parsed = candidate;
210
+ }
211
+ } catch (jsonError) {
212
+ // Corrupt JSON: warn but proceed with `{}` so the build still gets
213
+ // the bump it needs. The next prebuild will fully regenerate the
214
+ // file anyway.
215
+ console.warn(
216
+ `[react-native-webrtc-kaleidoscope] Could not parse ${propsPath}; rewriting with defaults. ${String(jsonError)}`,
217
+ );
218
+ parsed = {};
219
+ }
220
+ } catch (readError) {
221
+ if (readError && readError.code === 'ENOENT') {
222
+ parsed = {};
223
+ } else {
224
+ // Transient I/O failure (EACCES, EMFILE, ...). Don't clobber the
225
+ // file when we couldn't actually read it; warn and bail.
226
+ console.warn(
227
+ `[react-native-webrtc-kaleidoscope] Could not read ${propsPath}; skipping iOS deployment-target bump. ${String(readError)}`,
228
+ );
229
+ return result;
230
+ }
231
+ }
232
+ // Explicit own-key copy into a fresh object to foreclose
233
+ // `__proto__`-as-literal-key surprises from JSON.parse.
234
+ const next = {};
235
+ for (const key of Object.keys(parsed)) {
236
+ next[key] = parsed[key];
237
+ }
238
+ const existing = next['ios.deploymentTarget'];
239
+ if (isVersionLessThan(existing, IOS_DEPLOYMENT_TARGET)) {
240
+ next['ios.deploymentTarget'] = IOS_DEPLOYMENT_TARGET;
241
+ }
242
+ fs.writeFileSync(propsPath, `${JSON.stringify(next, null, 2)}\n`);
243
+ } catch (error) {
244
+ console.warn(
245
+ `[react-native-webrtc-kaleidoscope] Could not patch ${propsPath} to raise iOS deployment target; add "ios.deploymentTarget": "${IOS_DEPLOYMENT_TARGET}" to Podfile.properties.json under your demo/ios directory manually. ${String(error)}`,
246
+ );
247
+ }
248
+ }
249
+ return result;
250
+ };
251
+
252
+ return config;
253
+ };
254
+
255
+ module.exports = withKaleidoscope;
@@ -0,0 +1,3 @@
1
+ export type { BackgroundPresetName } from './presets';
2
+ export { BACKGROUND_PRESETS } from './presets';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backgrounds/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC","sourcesContent":["// Barrel for the backgrounds feature: the platform-agnostic preset catalog.\n// Preset *sources* are not re-exported here; import them per preset so each\n// pulls only its own WebP, e.g.\n// `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`.\n\nexport type { BackgroundPresetName } from './presets';\nexport { BACKGROUND_PRESETS } from './presets';\n"]}
@@ -0,0 +1,6 @@
1
+ // Barrel for the backgrounds feature: the platform-agnostic preset catalog.
2
+ // Preset *sources* are not re-exported here; import them per preset so each
3
+ // pulls only its own WebP, e.g.
4
+ // `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`.
5
+ export { BACKGROUND_PRESETS } from './presets';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backgrounds/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4EAA4E;AAC5E,gCAAgC;AAChC,qFAAqF;AAGrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC","sourcesContent":["// Barrel for the backgrounds feature: the platform-agnostic preset catalog.\n// Preset *sources* are not re-exported here; import them per preset so each\n// pulls only its own WebP, e.g.\n// `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`.\n\nexport type { BackgroundPresetName } from './presets';\nexport { BACKGROUND_PRESETS } from './presets';\n"]}
@@ -0,0 +1,3 @@
1
+ import type { PresetSource } from './preset-source.types';
2
+ export declare const office1: PresetSource;
3
+ //# sourceMappingURL=office-1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-1.d.ts","sourceRoot":"","sources":["../../src/backgrounds/office-1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,OAAO,EAAE,YAAyB,CAAC","sourcesContent":["import type { PresetSource } from './preset-source.types';\n\n// Native variant. The native module loads its own bundled resource by name, so\n// the source is just the preset name; no WebP import, no expo-asset on native.\n// Web is handled by office-1.web.ts.\nexport const office1: PresetSource = 'office-1';\n"]}
@@ -0,0 +1,5 @@
1
+ // Native variant. The native module loads its own bundled resource by name, so
2
+ // the source is just the preset name; no WebP import, no expo-asset on native.
3
+ // Web is handled by office-1.web.ts.
4
+ export const office1 = 'office-1';
5
+ //# sourceMappingURL=office-1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-1.js","sourceRoot":"","sources":["../../src/backgrounds/office-1.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,+EAA+E;AAC/E,qCAAqC;AACrC,MAAM,CAAC,MAAM,OAAO,GAAiB,UAAU,CAAC","sourcesContent":["import type { PresetSource } from './preset-source.types';\n\n// Native variant. The native module loads its own bundled resource by name, so\n// the source is just the preset name; no WebP import, no expo-asset on native.\n// Web is handled by office-1.web.ts.\nexport const office1: PresetSource = 'office-1';\n"]}
@@ -0,0 +1,3 @@
1
+ import type { PresetSource } from './preset-source.types';
2
+ export declare const office1: PresetSource;
3
+ //# sourceMappingURL=office-1.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-1.web.d.ts","sourceRoot":"","sources":["../../src/backgrounds/office-1.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,OAAO,EAAE,YAAiD,CAAC","sourcesContent":["/// <reference path=\"./assets.d.ts\" />\nimport { Asset } from 'expo-asset';\nimport office1Asset from './office-1.webp';\nimport type { PresetSource } from './preset-source.types';\n\n// Web variant. The bundled WebP's URL, which the background-image effect\n// fetches. Resolved with expo-asset because react-native-web has no\n// Image.resolveAssetSource; `.uri` is set synchronously by fromModule.\nexport const office1: PresetSource = Asset.fromModule(office1Asset).uri;\n"]}
@@ -0,0 +1,8 @@
1
+ /// <reference path="./assets.d.ts" />
2
+ import { Asset } from 'expo-asset';
3
+ import office1Asset from './office-1.webp';
4
+ // Web variant. The bundled WebP's URL, which the background-image effect
5
+ // fetches. Resolved with expo-asset because react-native-web has no
6
+ // Image.resolveAssetSource; `.uri` is set synchronously by fromModule.
7
+ export const office1 = Asset.fromModule(office1Asset).uri;
8
+ //# sourceMappingURL=office-1.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-1.web.js","sourceRoot":"","sources":["../../src/backgrounds/office-1.web.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAG3C,yEAAyE;AACzE,oEAAoE;AACpE,uEAAuE;AACvE,MAAM,CAAC,MAAM,OAAO,GAAiB,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC","sourcesContent":["/// <reference path=\"./assets.d.ts\" />\nimport { Asset } from 'expo-asset';\nimport office1Asset from './office-1.webp';\nimport type { PresetSource } from './preset-source.types';\n\n// Web variant. The bundled WebP's URL, which the background-image effect\n// fetches. Resolved with expo-asset because react-native-web has no\n// Image.resolveAssetSource; `.uri` is set synchronously by fromModule.\nexport const office1: PresetSource = Asset.fromModule(office1Asset).uri;\n"]}
Binary file
@@ -0,0 +1,3 @@
1
+ import type { PresetSource } from './preset-source.types';
2
+ export declare const office2: PresetSource;
3
+ //# sourceMappingURL=office-2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-2.d.ts","sourceRoot":"","sources":["../../src/backgrounds/office-2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,OAAO,EAAE,YAAyB,CAAC","sourcesContent":["import type { PresetSource } from './preset-source.types';\n\n// Native variant. The native module loads its own bundled resource by name, so\n// the source is just the preset name; no WebP import, no expo-asset on native.\n// Web is handled by office-2.web.ts.\nexport const office2: PresetSource = 'office-2';\n"]}
@@ -0,0 +1,5 @@
1
+ // Native variant. The native module loads its own bundled resource by name, so
2
+ // the source is just the preset name; no WebP import, no expo-asset on native.
3
+ // Web is handled by office-2.web.ts.
4
+ export const office2 = 'office-2';
5
+ //# sourceMappingURL=office-2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-2.js","sourceRoot":"","sources":["../../src/backgrounds/office-2.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,+EAA+E;AAC/E,qCAAqC;AACrC,MAAM,CAAC,MAAM,OAAO,GAAiB,UAAU,CAAC","sourcesContent":["import type { PresetSource } from './preset-source.types';\n\n// Native variant. The native module loads its own bundled resource by name, so\n// the source is just the preset name; no WebP import, no expo-asset on native.\n// Web is handled by office-2.web.ts.\nexport const office2: PresetSource = 'office-2';\n"]}
@@ -0,0 +1,3 @@
1
+ import type { PresetSource } from './preset-source.types';
2
+ export declare const office2: PresetSource;
3
+ //# sourceMappingURL=office-2.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-2.web.d.ts","sourceRoot":"","sources":["../../src/backgrounds/office-2.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,eAAO,MAAM,OAAO,EAAE,YAAiD,CAAC","sourcesContent":["/// <reference path=\"./assets.d.ts\" />\nimport { Asset } from 'expo-asset';\nimport office2Asset from './office-2.webp';\nimport type { PresetSource } from './preset-source.types';\n\n// Web variant. The bundled WebP's URL, which the background-image effect\n// fetches. Resolved with expo-asset because react-native-web has no\n// Image.resolveAssetSource; `.uri` is set synchronously by fromModule.\nexport const office2: PresetSource = Asset.fromModule(office2Asset).uri;\n"]}
@@ -0,0 +1,8 @@
1
+ /// <reference path="./assets.d.ts" />
2
+ import { Asset } from 'expo-asset';
3
+ import office2Asset from './office-2.webp';
4
+ // Web variant. The bundled WebP's URL, which the background-image effect
5
+ // fetches. Resolved with expo-asset because react-native-web has no
6
+ // Image.resolveAssetSource; `.uri` is set synchronously by fromModule.
7
+ export const office2 = Asset.fromModule(office2Asset).uri;
8
+ //# sourceMappingURL=office-2.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"office-2.web.js","sourceRoot":"","sources":["../../src/backgrounds/office-2.web.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAG3C,yEAAyE;AACzE,oEAAoE;AACpE,uEAAuE;AACvE,MAAM,CAAC,MAAM,OAAO,GAAiB,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC","sourcesContent":["/// <reference path=\"./assets.d.ts\" />\nimport { Asset } from 'expo-asset';\nimport office2Asset from './office-2.webp';\nimport type { PresetSource } from './preset-source.types';\n\n// Web variant. The bundled WebP's URL, which the background-image effect\n// fetches. Resolved with expo-asset because react-native-web has no\n// Image.resolveAssetSource; `.uri` is set synchronously by fromModule.\nexport const office2: PresetSource = Asset.fromModule(office2Asset).uri;\n"]}
Binary file