@lynxship/expo 0.1.7 → 0.1.8
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.
- package/README.md +41 -1
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/lynxship/expo/LynxShipExpoModule.kt +51 -3
- package/android/src/main/java/com/lynxship/expo/LynxShipExpoView.kt +259 -5
- package/dist/index.d.ts +72 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -11
- package/ios/LynxShipExpoModule.swift +39 -3
- package/ios/LynxShipExpoView.swift +234 -7
- package/package.json +8 -3
- package/src/externals.d.ts +3 -0
- package/src/index.ts +103 -10
package/README.md
CHANGED
|
@@ -134,12 +134,47 @@ export function LynxScreen() {
|
|
|
134
134
|
style={{ flex: 1 }}
|
|
135
135
|
bundle="main.lynx.bundle"
|
|
136
136
|
initialData="{}"
|
|
137
|
+
globalProps={{ theme: "dark" }}
|
|
138
|
+
autoGlobalProps
|
|
137
139
|
reloadOnUpdate
|
|
138
140
|
/>
|
|
139
141
|
);
|
|
140
142
|
}
|
|
141
143
|
```
|
|
142
144
|
|
|
145
|
+
`globalProps` is passed to `lynx.__globalProps` during the native template
|
|
146
|
+
load. It can also be changed without remounting the React Native screen. The
|
|
147
|
+
native ref exposes the same host controls on Android and iOS:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
const viewRef = useRef<LynxViewRef>(null);
|
|
151
|
+
|
|
152
|
+
viewRef.current?.updateData('{"screen":"inbox"}');
|
|
153
|
+
viewRef.current?.updateGlobalProps({ theme: "light" });
|
|
154
|
+
viewRef.current?.updateGlobalPropsByIncrement({ unreadCount: 3 });
|
|
155
|
+
viewRef.current?.sendGlobalEvent("accountChanged", [{ id: "user-1" }]);
|
|
156
|
+
await viewRef.current?.reload();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`updateData` uses Lynx's official host-data update API and accepts a JSON
|
|
160
|
+
string up to 8 MiB without remounting the view. Its optional second argument
|
|
161
|
+
selects a registered Lynx data processor. `show`, `hide` and
|
|
162
|
+
`updateViewport({ width, height })` are also available.
|
|
163
|
+
`updateGlobalPropsByIncrement` applies only the supplied keys and is useful for
|
|
164
|
+
small host-state changes; `updateGlobalProps` remains the full replacement API.
|
|
165
|
+
The ref also exposes `getContainerId()`, `getLoadState()` and
|
|
166
|
+
`isLoadSuccess()`, matching the native container state contract without
|
|
167
|
+
requiring a React remount.
|
|
168
|
+
The viewport is updated automatically from the native view layout, so the
|
|
169
|
+
usual `style={{ flex: 1 }}` remains the recommended layout. The native view
|
|
170
|
+
owns the Lynx lifecycle and is destroyed when Expo releases the view; this
|
|
171
|
+
prevents an old page/runtime from surviving React reconciliation.
|
|
172
|
+
|
|
173
|
+
The view also exposes lifecycle events that can drive application-owned UI:
|
|
174
|
+
`onLoadStart`, `onResourceFetchStart`, `onLoadSuccess`, `onReady` (first screen), `onError`, `onUpdate` (OTA or non-remounting data update),
|
|
175
|
+
`onShow` and `onHide`. The events are notifications only; they do not replace
|
|
176
|
+
the native permission, navigation or OTA policy owned by the host.
|
|
177
|
+
|
|
143
178
|
The native module initializes Lynx, creates the official `LynxView`, and
|
|
144
179
|
provides a template provider. The provider first reads the verified active OTA
|
|
145
180
|
asset and falls back to the embedded bundle. An OTA release is accepted only
|
|
@@ -190,8 +225,13 @@ the provider is the integration point for the LynxShip OTA cache.
|
|
|
190
225
|
|
|
191
226
|
References:
|
|
192
227
|
|
|
193
|
-
- [Lynx
|
|
228
|
+
- [Lynx/Rspeedy existing-app integration](https://lynxjs.org/next/rspeedy/start/integrate-with-existing-apps)
|
|
229
|
+
- [Lynx native `LynxView` API](https://lynxjs.org/next/api/lynx-native-api/lynx-view/lynx-view.html)
|
|
230
|
+
- [Lynx global props](https://lynxjs.org/next/api/lynx-api/lynx/lynx-global-props.html)
|
|
231
|
+
- [Lynx global events](https://lynxjs.org/next/api/lynx-native-api/lynx-view/send-global-event.html)
|
|
232
|
+
- [Lynx native viewport API](https://lynxjs.org/next/api/lynx-native-api/lynx-view/update-viewport.html)
|
|
194
233
|
- [Expo native view modules](https://docs.expo.dev/modules/native-view-tutorial/)
|
|
234
|
+
- [Expo view refs and async functions](https://docs.expo.dev/modules/module-api/)
|
|
195
235
|
- [Expo module configuration](https://docs.expo.dev/modules/module-config/)
|
|
196
236
|
- [LynxShip OTA security and compatibility](../../docs/compatibility.md)
|
|
197
237
|
|
package/android/build.gradle
CHANGED
|
@@ -42,6 +42,7 @@ android {
|
|
|
42
42
|
|
|
43
43
|
dependencies {
|
|
44
44
|
implementation "org.lynxsdk.lynx:lynx:${lynxVersion}"
|
|
45
|
+
implementation "androidx.annotation:annotation:1.10.0"
|
|
45
46
|
implementation "org.lynxsdk.lynx:lynx-jssdk:${lynxVersion}"
|
|
46
47
|
implementation "org.lynxsdk.lynx:lynx-trace:${lynxVersion}"
|
|
47
48
|
implementation "org.lynxsdk.lynx:primjs:${lynxVersion}"
|
|
@@ -8,14 +8,22 @@ class LynxShipExpoModule : Module() {
|
|
|
8
8
|
Name("LynxShip")
|
|
9
9
|
|
|
10
10
|
View(LynxShipExpoView::class) {
|
|
11
|
-
Events("onReady", "onError", "onUpdate")
|
|
11
|
+
Events("onReady", "onLoadStart", "onResourceFetchStart", "onLoadSuccess", "onError", "onUpdate", "onShow", "onHide")
|
|
12
12
|
|
|
13
13
|
Prop("bundle") { view: LynxShipExpoView, value: String? ->
|
|
14
|
-
view.
|
|
14
|
+
view.setBundleName(value ?: "main.lynx.bundle")
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
Prop("initialData") { view: LynxShipExpoView, value: String? ->
|
|
18
|
-
view.
|
|
18
|
+
view.setInitialData(value ?: "")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Prop("globalProps") { view: LynxShipExpoView, value: Map<String, Any?>? ->
|
|
22
|
+
view.updateGlobalProps(value ?: emptyMap())
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Prop("autoGlobalProps") { view: LynxShipExpoView, value: Boolean? ->
|
|
26
|
+
view.autoGlobalProps = value ?: true
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
Prop("reloadOnUpdate") { view: LynxShipExpoView, value: Boolean? ->
|
|
@@ -25,6 +33,46 @@ class LynxShipExpoModule : Module() {
|
|
|
25
33
|
AsyncFunction("reload") { view: LynxShipExpoView ->
|
|
26
34
|
view.reload()
|
|
27
35
|
}
|
|
36
|
+
|
|
37
|
+
AsyncFunction("getContainerId") { view: LynxShipExpoView ->
|
|
38
|
+
view.getContainerId()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AsyncFunction("getLoadState") { view: LynxShipExpoView ->
|
|
42
|
+
view.getLoadState()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
AsyncFunction("isLoadSuccess") { view: LynxShipExpoView ->
|
|
46
|
+
view.isLoadSuccess()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
AsyncFunction("updateData") { view: LynxShipExpoView, data: String, processorName: String? ->
|
|
50
|
+
view.updateData(data, processorName)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
AsyncFunction("updateGlobalProps") { view: LynxShipExpoView, props: Map<String, Any?> ->
|
|
54
|
+
view.updateGlobalProps(props)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
AsyncFunction("updateGlobalPropsByIncrement") { view: LynxShipExpoView, props: Map<String, Any?> ->
|
|
58
|
+
view.updateGlobalPropsByIncrement(props)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
AsyncFunction("sendGlobalEvent") { view: LynxShipExpoView, eventName: String, params: List<Any?> ->
|
|
62
|
+
view.sendGlobalEvent(eventName, params)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
AsyncFunction("show") { view: LynxShipExpoView -> view.show() }
|
|
66
|
+
|
|
67
|
+
AsyncFunction("hide") { view: LynxShipExpoView -> view.hide() }
|
|
68
|
+
|
|
69
|
+
AsyncFunction("updateViewport") { view: LynxShipExpoView, viewport: Map<String, Double> ->
|
|
70
|
+
view.updateViewport(viewport["width"] ?: 0.0, viewport["height"] ?: 0.0)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
OnViewDestroys { view: LynxShipExpoView ->
|
|
74
|
+
view.release()
|
|
75
|
+
}
|
|
28
76
|
}
|
|
29
77
|
}
|
|
30
78
|
}
|
|
@@ -3,8 +3,16 @@ package com.lynxship.expo
|
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.SharedPreferences
|
|
6
|
+
import android.content.res.Configuration
|
|
6
7
|
import android.os.Bundle
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.os.PowerManager
|
|
7
10
|
import android.provider.Settings
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.View.MeasureSpec
|
|
13
|
+
import android.view.WindowInsets
|
|
14
|
+
import android.view.accessibility.AccessibilityManager
|
|
15
|
+
import com.lynx.react.bridge.JavaOnlyArray
|
|
8
16
|
import com.facebook.drawee.backends.pipeline.Fresco
|
|
9
17
|
import com.facebook.imagepipeline.core.ImagePipelineConfig
|
|
10
18
|
import com.facebook.imagepipeline.memory.PoolConfig
|
|
@@ -13,6 +21,10 @@ import com.lynx.service.http.LynxHttpService
|
|
|
13
21
|
import com.lynx.service.image.LynxImageService
|
|
14
22
|
import com.lynx.service.log.LynxLogService
|
|
15
23
|
import com.lynx.tasm.LynxEnv
|
|
24
|
+
import com.lynx.tasm.LynxError
|
|
25
|
+
import com.lynx.tasm.LynxLoadMeta
|
|
26
|
+
import com.lynx.tasm.LynxUpdateMeta
|
|
27
|
+
import com.lynx.tasm.TemplateData
|
|
16
28
|
import com.lynx.tasm.LynxView
|
|
17
29
|
import com.lynx.tasm.LynxViewClient
|
|
18
30
|
import com.lynx.tasm.LynxViewBuilder
|
|
@@ -30,13 +42,19 @@ import java.util.UUID
|
|
|
30
42
|
class LynxShipExpoView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
31
43
|
private val applicationContext = context.applicationContext
|
|
32
44
|
private val onReady by EventDispatcher()
|
|
45
|
+
private val onLoadStart by EventDispatcher()
|
|
46
|
+
private val onResourceFetchStart by EventDispatcher()
|
|
47
|
+
private val onLoadSuccess by EventDispatcher()
|
|
33
48
|
private val onError by EventDispatcher()
|
|
34
49
|
private val onUpdate by EventDispatcher()
|
|
50
|
+
private val onShow by EventDispatcher()
|
|
51
|
+
private val onHide by EventDispatcher()
|
|
35
52
|
private val preferences: SharedPreferences = applicationContext.getSharedPreferences("lynxship-expo", Context.MODE_PRIVATE)
|
|
36
53
|
private val metadata: Bundle = applicationContext.applicationInfo.metaData ?: Bundle()
|
|
37
54
|
private val otaClient = createOtaClient(applicationContext)
|
|
38
55
|
private val templateProvider = object : AbsTemplateProvider() {
|
|
39
56
|
override fun loadTemplate(uri: String, callback: Callback) {
|
|
57
|
+
post { onResourceFetchStart(mapOf("bundle" to uri)) }
|
|
40
58
|
Thread {
|
|
41
59
|
try {
|
|
42
60
|
val bytes = otaClient?.openActiveAsset(uri) ?: readEmbeddedAsset(uri)
|
|
@@ -49,9 +67,21 @@ class LynxShipExpoView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
private val lynxView: LynxView
|
|
52
|
-
var
|
|
53
|
-
var
|
|
70
|
+
private var bundleNameValue: String = metadata.getString("com.lynxship.expo.embeddedBundle", "main.lynx.bundle")
|
|
71
|
+
private var initialDataValue: String = ""
|
|
72
|
+
private var globalProps: Map<String, Any?> = emptyMap()
|
|
54
73
|
var reloadOnUpdate: Boolean = true
|
|
74
|
+
var autoGlobalProps: Boolean = true
|
|
75
|
+
set(value) {
|
|
76
|
+
field = value
|
|
77
|
+
if (hasRendered) pushGlobalProps()
|
|
78
|
+
}
|
|
79
|
+
private var hasRendered = false
|
|
80
|
+
private var loadState = "idle"
|
|
81
|
+
private var started = false
|
|
82
|
+
private val containerId = UUID.randomUUID().toString()
|
|
83
|
+
private val containerInitTime = System.currentTimeMillis().toString()
|
|
84
|
+
private var appInBackground = false
|
|
55
85
|
|
|
56
86
|
init {
|
|
57
87
|
initializeLynx(context)
|
|
@@ -59,13 +89,26 @@ class LynxShipExpoView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
59
89
|
// application context only for process-wide services and storage.
|
|
60
90
|
lynxView = LynxViewBuilder().setTemplateProvider(templateProvider).build(context)
|
|
61
91
|
lynxView.addLynxViewClient(object : LynxViewClient() {
|
|
92
|
+
override fun onPageStart(url: String?) {
|
|
93
|
+
loadState = "loading"
|
|
94
|
+
onLoadStart(mapOf("bundle" to (url ?: bundleNameValue)))
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
override fun onFirstScreen() {
|
|
98
|
+
loadState = "loaded"
|
|
63
99
|
try {
|
|
64
100
|
otaClient?.markLaunchSuccess()
|
|
65
101
|
} catch (error: IOException) {
|
|
66
102
|
onError(mapOf("message" to (error.message ?: "Could not record Lynx launch"), "recoverable" to true))
|
|
67
103
|
}
|
|
68
|
-
|
|
104
|
+
val event = mapOf("bundle" to bundleNameValue, "sequence" to (otaClient?.activeSequence() ?: 0))
|
|
105
|
+
onLoadSuccess(event)
|
|
106
|
+
onReady(event)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun onReceivedError(error: LynxError) {
|
|
110
|
+
loadState = "failed"
|
|
111
|
+
onError(mapOf("message" to error.toString(), "recoverable" to false))
|
|
69
112
|
}
|
|
70
113
|
})
|
|
71
114
|
addView(lynxView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
@@ -73,27 +116,238 @@ class LynxShipExpoView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
73
116
|
|
|
74
117
|
override fun onAttachedToWindow() {
|
|
75
118
|
super.onAttachedToWindow()
|
|
119
|
+
lynxView.onEnterForeground()
|
|
120
|
+
onShow(emptyMap<String, Any>())
|
|
121
|
+
if (started) return
|
|
122
|
+
started = true
|
|
76
123
|
try {
|
|
77
124
|
otaClient?.beginLaunch()
|
|
78
125
|
installUpdateInBackground()
|
|
79
|
-
render()
|
|
126
|
+
if (!hasRendered) render()
|
|
80
127
|
} catch (error: Exception) {
|
|
81
128
|
onError(mapOf("message" to (error.message ?: "Lynx startup failed")))
|
|
82
129
|
}
|
|
83
130
|
}
|
|
84
131
|
|
|
132
|
+
override fun onDetachedFromWindow() {
|
|
133
|
+
lynxView.onEnterBackground()
|
|
134
|
+
onHide(emptyMap<String, Any>())
|
|
135
|
+
super.onDetachedFromWindow()
|
|
136
|
+
}
|
|
137
|
+
|
|
85
138
|
fun reload() {
|
|
86
139
|
render()
|
|
87
140
|
}
|
|
88
141
|
|
|
142
|
+
fun getContainerId(): String = containerId
|
|
143
|
+
|
|
144
|
+
fun getLoadState(): String = loadState
|
|
145
|
+
|
|
146
|
+
fun isLoadSuccess(): Boolean = loadState == "loaded"
|
|
147
|
+
|
|
148
|
+
fun updateData(data: String, processorName: String? = null) {
|
|
149
|
+
require(data.length <= 8 * 1024 * 1024) { "Lynx update data is larger than 8 MiB" }
|
|
150
|
+
check(hasRendered) { "Lynx view has not loaded a bundle" }
|
|
151
|
+
if (processorName != null) {
|
|
152
|
+
require(processorName.isNotBlank() && processorName.length <= 256 && processorName.none { it.isISOControl() }) {
|
|
153
|
+
"Lynx data processor name is invalid"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
val templateData = TemplateData.fromString(data)
|
|
157
|
+
processorName?.let(templateData::markState)
|
|
158
|
+
val builder = LynxUpdateMeta.Builder()
|
|
159
|
+
builder.setUpdatedData(templateData)
|
|
160
|
+
lynxView.updateMetaData(builder.build())
|
|
161
|
+
initialDataValue = data
|
|
162
|
+
onUpdate(mapOf("bundle" to bundleNameValue, "reason" to "data"))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fun setBundleName(value: String) {
|
|
166
|
+
if (value == bundleNameValue) return
|
|
167
|
+
bundleNameValue = value
|
|
168
|
+
if (started) render()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fun setInitialData(value: String) {
|
|
172
|
+
if (value == initialDataValue) return
|
|
173
|
+
initialDataValue = value
|
|
174
|
+
if (started) render()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fun updateGlobalProps(props: Map<String, Any?>) {
|
|
178
|
+
globalProps = props.toMap()
|
|
179
|
+
pushGlobalProps()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fun updateGlobalPropsByIncrement(props: Map<String, Any?>) {
|
|
183
|
+
if (props.isEmpty()) return
|
|
184
|
+
globalProps = buildMap {
|
|
185
|
+
putAll(globalProps)
|
|
186
|
+
putAll(props)
|
|
187
|
+
}
|
|
188
|
+
lynxView.updateGlobalProps(TemplateData.fromMap(props))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fun sendGlobalEvent(eventName: String, params: List<Any?>) {
|
|
192
|
+
require(eventName.isNotBlank()) { "Lynx global event name cannot be blank" }
|
|
193
|
+
require(eventName.length <= 256) { "Lynx global event name is too long" }
|
|
194
|
+
require(params.size <= 256) { "Lynx global event payload is too large" }
|
|
195
|
+
lynxView.sendGlobalEvent(
|
|
196
|
+
eventName,
|
|
197
|
+
JavaOnlyArray.from(params),
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun show() {
|
|
202
|
+
visibility = View.VISIBLE
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fun hide() {
|
|
206
|
+
visibility = View.INVISIBLE
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun updateViewport(width: Double, height: Double) {
|
|
210
|
+
require(width.isFinite() && height.isFinite() && width >= 0 && height >= 0) {
|
|
211
|
+
"Lynx viewport dimensions must be finite and non-negative"
|
|
212
|
+
}
|
|
213
|
+
lynxView.updateViewport(
|
|
214
|
+
MeasureSpec.makeMeasureSpec(width.toInt(), MeasureSpec.EXACTLY),
|
|
215
|
+
MeasureSpec.makeMeasureSpec(height.toInt(), MeasureSpec.EXACTLY),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
89
219
|
private fun render() {
|
|
90
220
|
try {
|
|
91
|
-
|
|
221
|
+
loadState = "loading"
|
|
222
|
+
val builder = LynxLoadMeta.Builder()
|
|
223
|
+
builder.setUrl(bundleNameValue)
|
|
224
|
+
if (initialDataValue.isNotEmpty()) {
|
|
225
|
+
builder.setInitialData(TemplateData.fromString(initialDataValue))
|
|
226
|
+
}
|
|
227
|
+
if (autoGlobalProps || globalProps.isNotEmpty()) {
|
|
228
|
+
builder.setGlobalProps(TemplateData.fromMap(effectiveGlobalProps()))
|
|
229
|
+
}
|
|
230
|
+
lynxView.loadTemplate(builder.build())
|
|
231
|
+
hasRendered = true
|
|
92
232
|
} catch (error: Exception) {
|
|
233
|
+
loadState = "failed"
|
|
93
234
|
onError(mapOf("message" to (error.message ?: "Lynx render failed")))
|
|
94
235
|
}
|
|
95
236
|
}
|
|
96
237
|
|
|
238
|
+
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
|
|
239
|
+
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
240
|
+
if (width > 0 && height > 0) {
|
|
241
|
+
lynxView.updateViewport(
|
|
242
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
243
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
|
244
|
+
)
|
|
245
|
+
if (hasRendered && autoGlobalProps) pushGlobalProps()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override fun onWindowVisibilityChanged(visibility: Int) {
|
|
250
|
+
super.onWindowVisibilityChanged(visibility)
|
|
251
|
+
val nextBackground = visibility != View.VISIBLE
|
|
252
|
+
if (nextBackground == appInBackground) return
|
|
253
|
+
appInBackground = nextBackground
|
|
254
|
+
if (hasRendered && autoGlobalProps) pushGlobalProps()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private fun pushGlobalProps() {
|
|
258
|
+
if (!autoGlobalProps && globalProps.isEmpty()) return
|
|
259
|
+
lynxView.updateGlobalProps(TemplateData.fromMap(effectiveGlobalProps()))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private fun effectiveGlobalProps(): Map<String, Any?> {
|
|
263
|
+
if (!autoGlobalProps) return globalProps
|
|
264
|
+
val metrics = resources.displayMetrics
|
|
265
|
+
val density = metrics.density.takeIf { it > 0f } ?: 1f
|
|
266
|
+
val screenWidth = metrics.widthPixels.toDouble() / density
|
|
267
|
+
val screenHeight = metrics.heightPixels.toDouble() / density
|
|
268
|
+
val contentWidth = width.toDouble() / density
|
|
269
|
+
val contentHeight = height.toDouble() / density
|
|
270
|
+
val insets = systemInsets()
|
|
271
|
+
val orientation = if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
272
|
+
"landscape"
|
|
273
|
+
} else {
|
|
274
|
+
"portrait"
|
|
275
|
+
}
|
|
276
|
+
val night = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
277
|
+
val theme = if (night == Configuration.UI_MODE_NIGHT_YES) "dark" else "light"
|
|
278
|
+
val locale = resources.configuration.locales[0]?.toLanguageTag() ?: "en-US"
|
|
279
|
+
val powerManager = getContext().getSystemService(Context.POWER_SERVICE) as? PowerManager
|
|
280
|
+
val hasCutout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && rootWindowInsets?.displayCutout != null
|
|
281
|
+
val isTablet = resources.configuration.smallestScreenWidthDp >= 600
|
|
282
|
+
return buildMap {
|
|
283
|
+
putAll(globalProps)
|
|
284
|
+
put("os", "android")
|
|
285
|
+
put("osVersion", Build.VERSION.RELEASE ?: "unknown")
|
|
286
|
+
put("deviceModel", Build.MODEL ?: "unknown")
|
|
287
|
+
put("containerID", containerId)
|
|
288
|
+
put("containerInitTime", containerInitTime)
|
|
289
|
+
put("screenWidth", screenWidth)
|
|
290
|
+
put("screenHeight", screenHeight)
|
|
291
|
+
put("contentWidth", contentWidth.coerceAtLeast(0.0))
|
|
292
|
+
put("contentHeight", contentHeight.coerceAtLeast(0.0))
|
|
293
|
+
put("safeAreaInsets", insets)
|
|
294
|
+
put("pixelRatio", density.toDouble())
|
|
295
|
+
put("accessibleMode", accessibilityMode())
|
|
296
|
+
put("isIPhoneX", 0)
|
|
297
|
+
put("isIPhoneXMax", 0)
|
|
298
|
+
put("isPad", if (isTablet) 1 else 0)
|
|
299
|
+
put("isNotchScreen", hasCutout)
|
|
300
|
+
put("isLowPowerMode", if (powerManager?.isPowerSaveMode == true) 1 else 0)
|
|
301
|
+
put("orientation", orientation)
|
|
302
|
+
put("screenOrientation", orientation)
|
|
303
|
+
put("theme", theme)
|
|
304
|
+
put("appLanguage", locale.substringBefore('-'))
|
|
305
|
+
put("appLocale", locale)
|
|
306
|
+
put("isAppBackground", appInBackground)
|
|
307
|
+
put("queryItems", emptyMap<String, String>())
|
|
308
|
+
put("statusBarHeight", insets["top"] ?: 0.0)
|
|
309
|
+
put("navigationBarHeight", insets["bottom"] ?: 0.0)
|
|
310
|
+
put("safeAreaHeight", insets["top"] ?: 0.0)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private fun systemInsets(): Map<String, Double> {
|
|
315
|
+
val insets = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
316
|
+
rootWindowInsets?.getInsets(WindowInsets.Type.systemBars())
|
|
317
|
+
} else {
|
|
318
|
+
null
|
|
319
|
+
}
|
|
320
|
+
val density = resources.displayMetrics.density.takeIf { it > 0f } ?: 1f
|
|
321
|
+
if (insets != null) {
|
|
322
|
+
return mapOf(
|
|
323
|
+
"top" to (insets.top / density).toDouble(),
|
|
324
|
+
"right" to (insets.right / density).toDouble(),
|
|
325
|
+
"bottom" to (insets.bottom / density).toDouble(),
|
|
326
|
+
"left" to (insets.left / density).toDouble(),
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
val legacy = rootWindowInsets
|
|
330
|
+
return mapOf(
|
|
331
|
+
"top" to (((legacy?.systemWindowInsetTop ?: 0) / density).toDouble()),
|
|
332
|
+
"right" to (((legacy?.systemWindowInsetRight ?: 0) / density).toDouble()),
|
|
333
|
+
"bottom" to (((legacy?.systemWindowInsetBottom ?: 0) / density).toDouble()),
|
|
334
|
+
"left" to (((legacy?.systemWindowInsetLeft ?: 0) / density).toDouble()),
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private fun accessibilityMode(): Int {
|
|
339
|
+
val manager = getContext().getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
|
|
340
|
+
return if (manager?.isTouchExplorationEnabled == true) 1 else 0
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
fun release() {
|
|
344
|
+
if (hasRendered) {
|
|
345
|
+
lynxView.destroy()
|
|
346
|
+
hasRendered = false
|
|
347
|
+
}
|
|
348
|
+
loadState = "released"
|
|
349
|
+
}
|
|
350
|
+
|
|
97
351
|
private fun installUpdateInBackground() {
|
|
98
352
|
otaClient?.checkAndInstallAsync(object : LynxShipOtaClient.Listener {
|
|
99
353
|
override fun onSuccess(updateAvailable: Boolean) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentType
|
|
1
|
+
import { type ComponentType } from "react";
|
|
2
2
|
import type { ViewProps } from "react-native";
|
|
3
3
|
import { type LynxShipExpoConfig } from "./config.js";
|
|
4
4
|
export type { LynxShipExpoConfig } from "./config.js";
|
|
@@ -8,14 +8,84 @@ export interface LynxViewProps extends ViewProps {
|
|
|
8
8
|
bundle?: string;
|
|
9
9
|
/** Optional initial data passed to Lynx when the view is rendered. */
|
|
10
10
|
initialData?: string;
|
|
11
|
+
/** Global props exposed as `lynx.__globalProps` in the loaded Lynx page. */
|
|
12
|
+
globalProps?: Readonly<Record<string, unknown>>;
|
|
13
|
+
/** Injects the standard OS, size, safe-area and lifecycle host props. */
|
|
14
|
+
autoGlobalProps?: boolean;
|
|
11
15
|
/** Requests a fresh render after an OTA candidate has been activated. */
|
|
12
16
|
reloadOnUpdate?: boolean;
|
|
17
|
+
/** Emitted after Lynx reports that the first screen layout completed. */
|
|
18
|
+
onReady?: (event: LynxViewReadyEvent) => void;
|
|
19
|
+
/** Emitted when Lynx begins loading a bundle. */
|
|
20
|
+
onLoadStart?: (event: LynxViewLoadStartEvent) => void;
|
|
21
|
+
/** Emitted when the native provider starts fetching bundle bytes. */
|
|
22
|
+
onResourceFetchStart?: (event: LynxViewResourceFetchStartEvent) => void;
|
|
23
|
+
/** Emitted when the first rendered screen is available. */
|
|
24
|
+
onLoadSuccess?: (event: LynxViewReadyEvent) => void;
|
|
25
|
+
/** Emitted when the native host or bundle provider reports an error. */
|
|
26
|
+
onError?: (event: LynxViewErrorEvent) => void;
|
|
27
|
+
/** Emitted after a verified OTA candidate has been activated. */
|
|
28
|
+
onUpdate?: (event: LynxViewUpdateEvent) => void;
|
|
29
|
+
/** Emitted when the native Lynx view becomes visible. */
|
|
30
|
+
onShow?: () => void;
|
|
31
|
+
/** Emitted when the native Lynx view leaves the window. */
|
|
32
|
+
onHide?: () => void;
|
|
33
|
+
}
|
|
34
|
+
export interface LynxViewLoadStartEvent {
|
|
35
|
+
readonly nativeEvent: {
|
|
36
|
+
readonly bundle: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface LynxViewResourceFetchStartEvent {
|
|
40
|
+
readonly nativeEvent: {
|
|
41
|
+
readonly bundle: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface LynxViewReadyEvent {
|
|
45
|
+
readonly nativeEvent: {
|
|
46
|
+
readonly bundle: string;
|
|
47
|
+
readonly sequence: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface LynxViewErrorEvent {
|
|
51
|
+
readonly nativeEvent: {
|
|
52
|
+
readonly message: string;
|
|
53
|
+
readonly recoverable?: boolean;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface LynxViewUpdateEvent {
|
|
57
|
+
readonly nativeEvent: {
|
|
58
|
+
readonly sequence: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface LynxViewViewport {
|
|
62
|
+
readonly width: number;
|
|
63
|
+
readonly height: number;
|
|
64
|
+
}
|
|
65
|
+
export type LynxViewLoadState = "idle" | "loading" | "loaded" | "failed" | "released";
|
|
66
|
+
/** Imperative controls implemented by the native LynxView ref. */
|
|
67
|
+
export interface LynxViewRef {
|
|
68
|
+
getContainerId(): Promise<string>;
|
|
69
|
+
getLoadState(): Promise<LynxViewLoadState>;
|
|
70
|
+
isLoadSuccess(): Promise<boolean>;
|
|
71
|
+
reload(): Promise<void>;
|
|
72
|
+
/** Updates Lynx initData without remounting the native view. */
|
|
73
|
+
updateData(data: string, processorName?: string): Promise<void>;
|
|
74
|
+
updateGlobalProps(props: Readonly<Record<string, unknown>>): Promise<void>;
|
|
75
|
+
/** Merges a partial global-props patch without remounting the Lynx page. */
|
|
76
|
+
updateGlobalPropsByIncrement(props: Readonly<Record<string, unknown>>): Promise<void>;
|
|
77
|
+
sendGlobalEvent(eventName: string, params?: readonly unknown[]): Promise<void>;
|
|
78
|
+
show(): Promise<void>;
|
|
79
|
+
hide(): Promise<void>;
|
|
80
|
+
updateViewport(viewport: LynxViewViewport): Promise<void>;
|
|
13
81
|
}
|
|
14
82
|
/**
|
|
15
83
|
* A LynxView that can be placed anywhere in an Expo/React Native view tree.
|
|
16
84
|
* Native OTA configuration is supplied by the LynxShip config plugin.
|
|
17
85
|
*/
|
|
18
|
-
export declare
|
|
86
|
+
export declare const LynxView: ComponentType<LynxViewProps & {
|
|
87
|
+
ref?: LynxViewRef | null | undefined;
|
|
88
|
+
}>;
|
|
19
89
|
export declare const LynxShipView: ComponentType<LynxViewProps>;
|
|
20
90
|
export declare function defineLynxShipExpoConfig(config: LynxShipExpoConfig): LynxShipExpoConfig;
|
|
21
91
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LynxShipView = exports.validateLynxShipExpoConfig = void 0;
|
|
4
|
-
exports.LynxView = LynxView;
|
|
3
|
+
exports.LynxShipView = exports.LynxView = exports.validateLynxShipExpoConfig = void 0;
|
|
5
4
|
exports.defineLynxShipExpoConfig = defineLynxShipExpoConfig;
|
|
6
5
|
const react_1 = require("react");
|
|
7
6
|
const expo_modules_core_1 = require("expo-modules-core");
|
|
@@ -13,15 +12,16 @@ const NativeLynxView = (0, expo_modules_core_1.requireNativeViewManager)("LynxSh
|
|
|
13
12
|
* A LynxView that can be placed anywhere in an Expo/React Native view tree.
|
|
14
13
|
* Native OTA configuration is supplied by the LynxShip config plugin.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
exports.LynxView = (0, react_1.forwardRef)((props, ref) => (0, react_1.createElement)(NativeLynxView, {
|
|
16
|
+
bundle: props.bundle ?? "main.lynx.bundle",
|
|
17
|
+
initialData: props.initialData ?? "",
|
|
18
|
+
globalProps: props.globalProps ?? {},
|
|
19
|
+
autoGlobalProps: props.autoGlobalProps ?? true,
|
|
20
|
+
reloadOnUpdate: props.reloadOnUpdate ?? true,
|
|
21
|
+
...props,
|
|
22
|
+
ref,
|
|
23
|
+
}));
|
|
24
|
+
exports.LynxShipView = exports.LynxView;
|
|
25
25
|
function defineLynxShipExpoConfig(config) {
|
|
26
26
|
return (0, config_js_1.validateLynxShipExpoConfig)(config);
|
|
27
27
|
}
|
|
@@ -4,12 +4,18 @@ public class LynxShipExpoModule: Module {
|
|
|
4
4
|
public func definition() -> ModuleDefinition {
|
|
5
5
|
Name("LynxShip")
|
|
6
6
|
View(LynxShipExpoView.self) {
|
|
7
|
-
Events("onReady", "onError", "onUpdate")
|
|
7
|
+
Events("onReady", "onLoadStart", "onResourceFetchStart", "onLoadSuccess", "onError", "onUpdate", "onShow", "onHide")
|
|
8
8
|
Prop("bundle") { (view: LynxShipExpoView, value: String?) in
|
|
9
|
-
view.
|
|
9
|
+
view.setBundleName(value ?? "main.lynx.bundle")
|
|
10
10
|
}
|
|
11
11
|
Prop("initialData") { (view: LynxShipExpoView, value: String?) in
|
|
12
|
-
view.
|
|
12
|
+
view.setInitialData(value ?? "")
|
|
13
|
+
}
|
|
14
|
+
Prop("globalProps") { (view: LynxShipExpoView, value: [String: Any]?) in
|
|
15
|
+
view.updateGlobalProps(value ?? [:])
|
|
16
|
+
}
|
|
17
|
+
Prop("autoGlobalProps") { (view: LynxShipExpoView, value: Bool?) in
|
|
18
|
+
view.autoGlobalProps = value ?? true
|
|
13
19
|
}
|
|
14
20
|
Prop("reloadOnUpdate") { (view: LynxShipExpoView, value: Bool?) in
|
|
15
21
|
view.reloadOnUpdate = value ?? true
|
|
@@ -17,6 +23,36 @@ public class LynxShipExpoModule: Module {
|
|
|
17
23
|
AsyncFunction("reload") { (view: LynxShipExpoView) in
|
|
18
24
|
try view.reload()
|
|
19
25
|
}
|
|
26
|
+
AsyncFunction("getContainerId") { (view: LynxShipExpoView) in
|
|
27
|
+
view.getContainerId()
|
|
28
|
+
}
|
|
29
|
+
AsyncFunction("getLoadState") { (view: LynxShipExpoView) in
|
|
30
|
+
view.getLoadState()
|
|
31
|
+
}
|
|
32
|
+
AsyncFunction("isLoadSuccess") { (view: LynxShipExpoView) in
|
|
33
|
+
view.isLoadSuccess()
|
|
34
|
+
}
|
|
35
|
+
AsyncFunction("updateData") { (view: LynxShipExpoView, data: String, processorName: String?) in
|
|
36
|
+
try view.updateData(data, processorName: processorName)
|
|
37
|
+
}
|
|
38
|
+
AsyncFunction("updateGlobalProps") { (view: LynxShipExpoView, props: [String: Any]) in
|
|
39
|
+
view.updateGlobalProps(props)
|
|
40
|
+
}
|
|
41
|
+
AsyncFunction("updateGlobalPropsByIncrement") { (view: LynxShipExpoView, props: [String: Any]) in
|
|
42
|
+
view.updateGlobalPropsByIncrement(props)
|
|
43
|
+
}
|
|
44
|
+
AsyncFunction("sendGlobalEvent") { (view: LynxShipExpoView, eventName: String, params: [Any]) in
|
|
45
|
+
try view.sendGlobalEvent(eventName, params: params)
|
|
46
|
+
}
|
|
47
|
+
AsyncFunction("show") { (view: LynxShipExpoView) in
|
|
48
|
+
view.show()
|
|
49
|
+
}
|
|
50
|
+
AsyncFunction("hide") { (view: LynxShipExpoView) in
|
|
51
|
+
view.hide()
|
|
52
|
+
}
|
|
53
|
+
AsyncFunction("updateViewport") { (view: LynxShipExpoView, viewport: [String: Double]) in
|
|
54
|
+
try view.updateViewport(viewport)
|
|
55
|
+
}
|
|
20
56
|
}
|
|
21
57
|
}
|
|
22
58
|
}
|
|
@@ -6,38 +6,79 @@ import UIKit
|
|
|
6
6
|
|
|
7
7
|
private final class LynxShipTemplateProvider: NSObject, LynxTemplateProvider {
|
|
8
8
|
private let load: (String) throws -> Data
|
|
9
|
-
private let
|
|
9
|
+
private let onStart: (String) -> Void
|
|
10
10
|
|
|
11
|
-
init(load: @escaping (String) throws -> Data,
|
|
11
|
+
init(load: @escaping (String) throws -> Data, onStart: @escaping (String) -> Void) {
|
|
12
12
|
self.load = load
|
|
13
|
-
self.
|
|
13
|
+
self.onStart = onStart
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
func loadTemplate(withUrl url: String!, onComplete callback: LynxTemplateLoadBlock!) {
|
|
17
|
+
onStart(url ?? "")
|
|
17
18
|
do {
|
|
18
19
|
callback(load(url), nil)
|
|
19
|
-
onLoaded()
|
|
20
20
|
} catch {
|
|
21
21
|
callback(nil, error)
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
private final class LynxShipLifecycleClient: NSObject, LynxViewLifecycle {
|
|
27
|
+
private let onStart: () -> Void
|
|
28
|
+
private let onFirstScreen: () -> Void
|
|
29
|
+
|
|
30
|
+
init(onStart: @escaping () -> Void, onFirstScreen: @escaping () -> Void) {
|
|
31
|
+
self.onStart = onStart
|
|
32
|
+
self.onFirstScreen = onFirstScreen
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func lynxViewDidStartLoading(_ view: LynxView) {
|
|
36
|
+
onStart()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func lynxViewDidFirstScreen(_ view: LynxView) {
|
|
40
|
+
onFirstScreen()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
public final class LynxShipExpoView: ExpoView {
|
|
27
45
|
let onReady = EventDispatcher()
|
|
46
|
+
let onLoadStart = EventDispatcher()
|
|
47
|
+
let onResourceFetchStart = EventDispatcher()
|
|
48
|
+
let onLoadSuccess = EventDispatcher()
|
|
28
49
|
let onError = EventDispatcher()
|
|
29
50
|
let onUpdate = EventDispatcher()
|
|
51
|
+
let onShow = EventDispatcher()
|
|
52
|
+
let onHide = EventDispatcher()
|
|
30
53
|
|
|
31
54
|
private lazy var templateProvider = LynxShipTemplateProvider(
|
|
32
55
|
load: { [weak self] path in
|
|
33
56
|
guard let self else { throw NSError(domain: "LynxShip", code: 1, userInfo: [NSLocalizedDescriptionKey: "Lynx view was released"]) }
|
|
34
57
|
return try self.otaClient?.openActiveAsset(path) ?? self.readEmbeddedAsset(path)
|
|
35
58
|
},
|
|
36
|
-
|
|
59
|
+
onStart: { [weak self] bundle in
|
|
60
|
+
DispatchQueue.main.async { self?.onResourceFetchStart(["bundle": bundle]) }
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
private lazy var lifecycleClient = LynxShipLifecycleClient(
|
|
65
|
+
onStart: { [weak self] in
|
|
37
66
|
guard let self else { return }
|
|
38
|
-
|
|
67
|
+
self.loadState = "loading"
|
|
68
|
+
self.onLoadStart(["bundle": self.bundleName])
|
|
69
|
+
},
|
|
70
|
+
onFirstScreen: { [weak self] in
|
|
71
|
+
guard let self else { return }
|
|
72
|
+
self.loadState = "loaded"
|
|
73
|
+
do {
|
|
74
|
+
try self.otaClient?.markLaunchSuccess()
|
|
75
|
+
} catch {
|
|
76
|
+
self.onError(["message": error.localizedDescription, "recoverable": true])
|
|
77
|
+
}
|
|
39
78
|
DispatchQueue.main.async {
|
|
40
|
-
|
|
79
|
+
let event: [String: Any] = ["bundle": self.bundleName, "sequence": self.otaClient?.activeSequence ?? 0]
|
|
80
|
+
self.onLoadSuccess(event)
|
|
81
|
+
self.onReady(event)
|
|
41
82
|
}
|
|
42
83
|
}
|
|
43
84
|
)
|
|
@@ -51,15 +92,26 @@ public final class LynxShipExpoView: ExpoView {
|
|
|
51
92
|
|
|
52
93
|
private var otaClient: LynxShipOtaClient?
|
|
53
94
|
private var hasRendered = false
|
|
95
|
+
private var loadState = "idle"
|
|
54
96
|
var bundleName = "main.lynx.bundle"
|
|
55
97
|
var initialData = ""
|
|
98
|
+
private var globalProps: [String: Any] = [:]
|
|
56
99
|
var reloadOnUpdate = true
|
|
100
|
+
var autoGlobalProps = true {
|
|
101
|
+
didSet {
|
|
102
|
+
if hasRendered { pushGlobalProps() }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
private let containerID = UUID().uuidString
|
|
106
|
+
private let containerInitTime = ISO8601DateFormatter().string(from: Date())
|
|
107
|
+
private var appInBackground = false
|
|
57
108
|
|
|
58
109
|
public required init(appContext: AppContext? = nil) {
|
|
59
110
|
super.init(appContext: appContext)
|
|
60
111
|
LynxEnv.sharedInstance()
|
|
61
112
|
otaClient = makeOtaClient()
|
|
62
113
|
addSubview(lynxView)
|
|
114
|
+
lynxView.addLifecycleClient(lifecycleClient)
|
|
63
115
|
}
|
|
64
116
|
|
|
65
117
|
public override func layoutSubviews() {
|
|
@@ -67,16 +119,30 @@ public final class LynxShipExpoView: ExpoView {
|
|
|
67
119
|
lynxView.frame = bounds
|
|
68
120
|
lynxView.preferredLayoutWidth = bounds.width
|
|
69
121
|
lynxView.preferredLayoutHeight = bounds.height
|
|
122
|
+
lynxView.updateViewport(withPreferredLayoutWidth: bounds.width,
|
|
123
|
+
preferredLayoutHeight: bounds.height,
|
|
124
|
+
needLayout: true)
|
|
125
|
+
if hasRendered && autoGlobalProps { pushGlobalProps() }
|
|
70
126
|
}
|
|
71
127
|
|
|
72
128
|
public override func didMoveToWindow() {
|
|
73
129
|
super.didMoveToWindow()
|
|
130
|
+
appInBackground = window == nil
|
|
131
|
+
if window != nil {
|
|
132
|
+
lynxView.onEnterForeground()
|
|
133
|
+
onShow([:])
|
|
134
|
+
} else {
|
|
135
|
+
lynxView.onEnterBackground()
|
|
136
|
+
onHide([:])
|
|
137
|
+
}
|
|
138
|
+
if hasRendered && autoGlobalProps { pushGlobalProps() }
|
|
74
139
|
guard window != nil, !hasRendered else { return }
|
|
75
140
|
do {
|
|
76
141
|
try otaClient?.beginLaunch()
|
|
77
142
|
render()
|
|
78
143
|
checkForUpdate()
|
|
79
144
|
} catch {
|
|
145
|
+
loadState = "failed"
|
|
80
146
|
onError(["message": error.localizedDescription])
|
|
81
147
|
}
|
|
82
148
|
}
|
|
@@ -85,11 +151,167 @@ public final class LynxShipExpoView: ExpoView {
|
|
|
85
151
|
render()
|
|
86
152
|
}
|
|
87
153
|
|
|
154
|
+
func getContainerId() -> String { containerID }
|
|
155
|
+
|
|
156
|
+
func getLoadState() -> String { loadState }
|
|
157
|
+
|
|
158
|
+
func isLoadSuccess() -> Bool { loadState == "loaded" }
|
|
159
|
+
|
|
160
|
+
func updateData(_ data: String, processorName: String? = nil) throws {
|
|
161
|
+
guard data.utf8.count <= 8 * 1024 * 1024 else {
|
|
162
|
+
throw NSError(domain: "LynxShip", code: 8, userInfo: [NSLocalizedDescriptionKey: "Lynx update data is larger than 8 MiB"])
|
|
163
|
+
}
|
|
164
|
+
guard hasRendered else {
|
|
165
|
+
throw NSError(domain: "LynxShip", code: 9, userInfo: [NSLocalizedDescriptionKey: "Lynx view has not loaded a bundle"])
|
|
166
|
+
}
|
|
167
|
+
if let processorName {
|
|
168
|
+
guard !processorName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
|
169
|
+
processorName.count <= 256,
|
|
170
|
+
processorName.rangeOfCharacter(from: .controlCharacters) == nil else {
|
|
171
|
+
throw NSError(domain: "LynxShip", code: 10, userInfo: [NSLocalizedDescriptionKey: "Lynx data processor name is invalid"])
|
|
172
|
+
}
|
|
173
|
+
let templateData = LynxTemplateData(json: data)
|
|
174
|
+
templateData.markState(processorName)
|
|
175
|
+
lynxView.updateData(withTemplateData: templateData)
|
|
176
|
+
} else {
|
|
177
|
+
lynxView.updateData(withString: data)
|
|
178
|
+
}
|
|
179
|
+
initialData = data
|
|
180
|
+
onUpdate(["bundle": bundleName, "reason": "data"])
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
func setBundleName(_ value: String) {
|
|
184
|
+
guard value != bundleName else { return }
|
|
185
|
+
bundleName = value
|
|
186
|
+
if hasRendered { render() }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func setInitialData(_ value: String) {
|
|
190
|
+
guard value != initialData else { return }
|
|
191
|
+
initialData = value
|
|
192
|
+
if hasRendered { render() }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func updateGlobalProps(_ props: [String: Any]) {
|
|
196
|
+
globalProps = props
|
|
197
|
+
pushGlobalProps()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func updateGlobalPropsByIncrement(_ props: [String: Any]) {
|
|
201
|
+
guard !props.isEmpty else { return }
|
|
202
|
+
globalProps.merge(props) { _, newValue in newValue }
|
|
203
|
+
lynxView.updateGlobalProps(withDictionary: props)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func sendGlobalEvent(_ eventName: String, params: [Any]) throws {
|
|
207
|
+
guard !eventName.isEmpty else {
|
|
208
|
+
throw NSError(domain: "LynxShip", code: 4, userInfo: [NSLocalizedDescriptionKey: "Lynx global event name cannot be empty"])
|
|
209
|
+
}
|
|
210
|
+
guard eventName.count <= 256 else {
|
|
211
|
+
throw NSError(domain: "LynxShip", code: 5, userInfo: [NSLocalizedDescriptionKey: "Lynx global event name is too long"])
|
|
212
|
+
}
|
|
213
|
+
guard params.count <= 256 else {
|
|
214
|
+
throw NSError(domain: "LynxShip", code: 6, userInfo: [NSLocalizedDescriptionKey: "Lynx global event payload is too large"])
|
|
215
|
+
}
|
|
216
|
+
lynxView.sendGlobalEvent(eventName, withParams: params)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func show() {
|
|
220
|
+
isHidden = false
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func hide() {
|
|
224
|
+
isHidden = true
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
func updateViewport(_ viewport: [String: Double]) throws {
|
|
228
|
+
let width = viewport["width"] ?? 0
|
|
229
|
+
let height = viewport["height"] ?? 0
|
|
230
|
+
guard width.isFinite && height.isFinite && width >= 0 && height >= 0 else {
|
|
231
|
+
throw NSError(domain: "LynxShip", code: 7, userInfo: [NSLocalizedDescriptionKey: "Lynx viewport dimensions must be finite and non-negative"])
|
|
232
|
+
}
|
|
233
|
+
lynxView.updateViewport(withPreferredLayoutWidth: CGFloat(width),
|
|
234
|
+
preferredLayoutHeight: CGFloat(height),
|
|
235
|
+
needLayout: true)
|
|
236
|
+
}
|
|
237
|
+
|
|
88
238
|
private func render() {
|
|
239
|
+
loadState = "loading"
|
|
240
|
+
if autoGlobalProps || !globalProps.isEmpty {
|
|
241
|
+
lynxView.updateGlobalProps(withDictionary: effectiveGlobalProps())
|
|
242
|
+
}
|
|
89
243
|
lynxView.loadTemplate(fromURL: bundleName, initData: initialData)
|
|
90
244
|
hasRendered = true
|
|
91
245
|
}
|
|
92
246
|
|
|
247
|
+
private func pushGlobalProps() {
|
|
248
|
+
guard autoGlobalProps || !globalProps.isEmpty else { return }
|
|
249
|
+
lynxView.updateGlobalProps(withDictionary: effectiveGlobalProps())
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private func effectiveGlobalProps() -> [String: Any] {
|
|
253
|
+
guard autoGlobalProps else { return globalProps }
|
|
254
|
+
let screen = window?.screen ?? UIScreen.main
|
|
255
|
+
let screenSize = screen.bounds.size
|
|
256
|
+
let insets = safeAreaInsets
|
|
257
|
+
let width = max(0, bounds.width)
|
|
258
|
+
let height = max(0, bounds.height)
|
|
259
|
+
let contentWidth = max(0, width - insets.left - insets.right)
|
|
260
|
+
let contentHeight = max(0, height - insets.top - insets.bottom)
|
|
261
|
+
let orientation: String
|
|
262
|
+
switch window?.windowScene?.interfaceOrientation {
|
|
263
|
+
case .landscapeLeft: orientation = "landscape-left"
|
|
264
|
+
case .landscapeRight: orientation = "landscape-right"
|
|
265
|
+
case .portraitUpsideDown: orientation = "portrait-upside-down"
|
|
266
|
+
case .portrait: orientation = "portrait"
|
|
267
|
+
default: orientation = width > height ? "landscape" : "portrait"
|
|
268
|
+
}
|
|
269
|
+
let theme: String
|
|
270
|
+
switch traitCollection.userInterfaceStyle {
|
|
271
|
+
case .dark: theme = "dark"
|
|
272
|
+
case .light: theme = "light"
|
|
273
|
+
default: theme = "system"
|
|
274
|
+
}
|
|
275
|
+
let locale = Locale.current.identifier
|
|
276
|
+
let language = Locale.current.languageCode ?? locale.split(separator: "_").first.map(String.init) ?? locale
|
|
277
|
+
let isTablet = UIDevice.current.userInterfaceIdiom == .pad
|
|
278
|
+
let isNotchScreen = UIDevice.current.userInterfaceIdiom == .phone && insets.top > 20
|
|
279
|
+
var props = globalProps
|
|
280
|
+
props["os"] = "ios"
|
|
281
|
+
props["osVersion"] = UIDevice.current.systemVersion
|
|
282
|
+
props["deviceModel"] = UIDevice.current.model
|
|
283
|
+
props["containerID"] = containerID
|
|
284
|
+
props["containerInitTime"] = containerInitTime
|
|
285
|
+
props["screenWidth"] = screenSize.width
|
|
286
|
+
props["screenHeight"] = screenSize.height
|
|
287
|
+
props["contentWidth"] = contentWidth
|
|
288
|
+
props["contentHeight"] = contentHeight
|
|
289
|
+
props["safeAreaInsets"] = [
|
|
290
|
+
"top": insets.top,
|
|
291
|
+
"right": insets.right,
|
|
292
|
+
"bottom": insets.bottom,
|
|
293
|
+
"left": insets.left,
|
|
294
|
+
]
|
|
295
|
+
props["pixelRatio"] = screen.scale
|
|
296
|
+
props["accessibleMode"] = UIAccessibility.isVoiceOverRunning ? 1 : 0
|
|
297
|
+
props["isIPhoneX"] = isNotchScreen ? 1 : 0
|
|
298
|
+
props["isIPhoneXMax"] = isNotchScreen ? 1 : 0
|
|
299
|
+
props["isPad"] = isTablet ? 1 : 0
|
|
300
|
+
props["isNotchScreen"] = isNotchScreen
|
|
301
|
+
props["isLowPowerMode"] = ProcessInfo.processInfo.isLowPowerModeEnabled ? 1 : 0
|
|
302
|
+
props["orientation"] = orientation
|
|
303
|
+
props["screenOrientation"] = orientation
|
|
304
|
+
props["theme"] = theme
|
|
305
|
+
props["appLanguage"] = language
|
|
306
|
+
props["appLocale"] = locale
|
|
307
|
+
props["isAppBackground"] = appInBackground
|
|
308
|
+
props["queryItems"] = [String: String]()
|
|
309
|
+
props["topHeight"] = insets.top
|
|
310
|
+
props["bottomHeight"] = insets.bottom
|
|
311
|
+
props["safeAreaHeight"] = insets.top
|
|
312
|
+
return props
|
|
313
|
+
}
|
|
314
|
+
|
|
93
315
|
private func checkForUpdate() {
|
|
94
316
|
Task { [weak self] in
|
|
95
317
|
guard let self else { return }
|
|
@@ -150,4 +372,9 @@ public final class LynxShipExpoView: ExpoView {
|
|
|
150
372
|
return nil
|
|
151
373
|
}
|
|
152
374
|
}
|
|
375
|
+
|
|
376
|
+
deinit {
|
|
377
|
+
loadState = "released"
|
|
378
|
+
lynxView.destroy()
|
|
379
|
+
}
|
|
153
380
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/expo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Expo native LynxView with LynxShip OTA delivery and cache support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
|
-
"android",
|
|
11
|
-
"
|
|
10
|
+
"android/build.gradle",
|
|
11
|
+
"android/consumer-rules.pro",
|
|
12
|
+
"android/src",
|
|
13
|
+
"ios/*.podspec",
|
|
14
|
+
"ios/*.h",
|
|
15
|
+
"ios/*.m",
|
|
16
|
+
"ios/*.swift",
|
|
12
17
|
"src",
|
|
13
18
|
"app.plugin.js",
|
|
14
19
|
"app.plugin.cjs",
|
package/src/externals.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ declare module "react" {
|
|
|
17
17
|
type: unknown,
|
|
18
18
|
props: Record<string, unknown> | null,
|
|
19
19
|
): ReactNode;
|
|
20
|
+
export function forwardRef<T, Props>(
|
|
21
|
+
render: (props: Props, ref: T | null) => ReactNode,
|
|
22
|
+
): ComponentType<Props & { ref?: T | null }>;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
declare module "react-native" {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createElement,
|
|
3
|
+
forwardRef,
|
|
4
|
+
type ComponentType,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
2
7
|
import { requireNativeViewManager } from "expo-modules-core";
|
|
3
8
|
import type { ViewProps } from "react-native";
|
|
4
9
|
import {
|
|
@@ -15,24 +20,112 @@ export interface LynxViewProps extends ViewProps {
|
|
|
15
20
|
bundle?: string;
|
|
16
21
|
/** Optional initial data passed to Lynx when the view is rendered. */
|
|
17
22
|
initialData?: string;
|
|
23
|
+
/** Global props exposed as `lynx.__globalProps` in the loaded Lynx page. */
|
|
24
|
+
globalProps?: Readonly<Record<string, unknown>>;
|
|
25
|
+
/** Injects the standard OS, size, safe-area and lifecycle host props. */
|
|
26
|
+
autoGlobalProps?: boolean;
|
|
18
27
|
/** Requests a fresh render after an OTA candidate has been activated. */
|
|
19
28
|
reloadOnUpdate?: boolean;
|
|
29
|
+
/** Emitted after Lynx reports that the first screen layout completed. */
|
|
30
|
+
onReady?: (event: LynxViewReadyEvent) => void;
|
|
31
|
+
/** Emitted when Lynx begins loading a bundle. */
|
|
32
|
+
onLoadStart?: (event: LynxViewLoadStartEvent) => void;
|
|
33
|
+
/** Emitted when the native provider starts fetching bundle bytes. */
|
|
34
|
+
onResourceFetchStart?: (event: LynxViewResourceFetchStartEvent) => void;
|
|
35
|
+
/** Emitted when the first rendered screen is available. */
|
|
36
|
+
onLoadSuccess?: (event: LynxViewReadyEvent) => void;
|
|
37
|
+
/** Emitted when the native host or bundle provider reports an error. */
|
|
38
|
+
onError?: (event: LynxViewErrorEvent) => void;
|
|
39
|
+
/** Emitted after a verified OTA candidate has been activated. */
|
|
40
|
+
onUpdate?: (event: LynxViewUpdateEvent) => void;
|
|
41
|
+
/** Emitted when the native Lynx view becomes visible. */
|
|
42
|
+
onShow?: () => void;
|
|
43
|
+
/** Emitted when the native Lynx view leaves the window. */
|
|
44
|
+
onHide?: () => void;
|
|
20
45
|
}
|
|
21
46
|
|
|
22
|
-
|
|
47
|
+
export interface LynxViewLoadStartEvent {
|
|
48
|
+
readonly nativeEvent: { readonly bundle: string };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LynxViewResourceFetchStartEvent {
|
|
52
|
+
readonly nativeEvent: { readonly bundle: string };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface LynxViewReadyEvent {
|
|
56
|
+
readonly nativeEvent: {
|
|
57
|
+
readonly bundle: string;
|
|
58
|
+
readonly sequence: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LynxViewErrorEvent {
|
|
63
|
+
readonly nativeEvent: {
|
|
64
|
+
readonly message: string;
|
|
65
|
+
readonly recoverable?: boolean;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface LynxViewUpdateEvent {
|
|
70
|
+
readonly nativeEvent: {
|
|
71
|
+
readonly sequence: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface LynxViewViewport {
|
|
76
|
+
readonly width: number;
|
|
77
|
+
readonly height: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type LynxViewLoadState =
|
|
81
|
+
| "idle"
|
|
82
|
+
| "loading"
|
|
83
|
+
| "loaded"
|
|
84
|
+
| "failed"
|
|
85
|
+
| "released";
|
|
86
|
+
|
|
87
|
+
/** Imperative controls implemented by the native LynxView ref. */
|
|
88
|
+
export interface LynxViewRef {
|
|
89
|
+
getContainerId(): Promise<string>;
|
|
90
|
+
getLoadState(): Promise<LynxViewLoadState>;
|
|
91
|
+
isLoadSuccess(): Promise<boolean>;
|
|
92
|
+
reload(): Promise<void>;
|
|
93
|
+
/** Updates Lynx initData without remounting the native view. */
|
|
94
|
+
updateData(data: string, processorName?: string): Promise<void>;
|
|
95
|
+
updateGlobalProps(props: Readonly<Record<string, unknown>>): Promise<void>;
|
|
96
|
+
/** Merges a partial global-props patch without remounting the Lynx page. */
|
|
97
|
+
updateGlobalPropsByIncrement(
|
|
98
|
+
props: Readonly<Record<string, unknown>>,
|
|
99
|
+
): Promise<void>;
|
|
100
|
+
sendGlobalEvent(
|
|
101
|
+
eventName: string,
|
|
102
|
+
params?: readonly unknown[],
|
|
103
|
+
): Promise<void>;
|
|
104
|
+
show(): Promise<void>;
|
|
105
|
+
hide(): Promise<void>;
|
|
106
|
+
updateViewport(viewport: LynxViewViewport): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const NativeLynxView = requireNativeViewManager<
|
|
110
|
+
LynxViewProps & { ref?: LynxViewRef | null }
|
|
111
|
+
>("LynxShip");
|
|
23
112
|
|
|
24
113
|
/**
|
|
25
114
|
* A LynxView that can be placed anywhere in an Expo/React Native view tree.
|
|
26
115
|
* Native OTA configuration is supplied by the LynxShip config plugin.
|
|
27
116
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
117
|
+
export const LynxView = forwardRef<LynxViewRef, LynxViewProps>(
|
|
118
|
+
(props, ref): ReactNode =>
|
|
119
|
+
createElement(NativeLynxView, {
|
|
120
|
+
bundle: props.bundle ?? "main.lynx.bundle",
|
|
121
|
+
initialData: props.initialData ?? "",
|
|
122
|
+
globalProps: props.globalProps ?? {},
|
|
123
|
+
autoGlobalProps: props.autoGlobalProps ?? true,
|
|
124
|
+
reloadOnUpdate: props.reloadOnUpdate ?? true,
|
|
125
|
+
...props,
|
|
126
|
+
ref,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
36
129
|
|
|
37
130
|
export const LynxShipView: ComponentType<LynxViewProps> = LynxView;
|
|
38
131
|
|