@nebula-rn/cli 0.0.1 → 0.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.
- package/README.md +65 -0
- package/dist/create.js +12 -3
- package/package.json +20 -7
- package/templates/host/.eslintrc.js +7 -0
- package/templates/host/App.tsx +118 -0
- package/templates/host/Gemfile +16 -0
- package/templates/host/__tests__/App.test.tsx +13 -0
- package/templates/host/android/app/build.gradle +55 -0
- package/templates/host/android/app/debug.keystore +0 -0
- package/templates/host/android/app/proguard-rules.pro +10 -0
- package/templates/host/android/app/src/main/AndroidManifest.xml +54 -0
- package/templates/host/android/app/src/main/java/com/SuperApp/MainActivity.kt +28 -0
- package/templates/host/android/app/src/main/java/com/SuperApp/MainApplication.kt +35 -0
- package/templates/host/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/templates/host/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/host/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/templates/host/android/app/src/main/res/values/strings.xml +3 -0
- package/templates/host/android/app/src/main/res/values/styles.xml +16 -0
- package/templates/host/android/build.gradle +21 -0
- package/templates/host/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/host/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/host/android/gradle.properties +44 -0
- package/templates/host/android/gradlew +251 -0
- package/templates/host/android/gradlew.bat +99 -0
- package/templates/host/android/settings.gradle +6 -0
- package/templates/host/app.json +4 -0
- package/templates/host/babel.config.js +4 -0
- package/templates/host/index.js +9 -0
- package/templates/host/ios/.xcode.env +11 -0
- package/templates/host/ios/Podfile +38 -0
- package/templates/host/ios/SuperApp/AppDelegate.swift +95 -0
- package/templates/host/ios/SuperApp/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/templates/host/ios/SuperApp/Images.xcassets/Contents.json +6 -0
- package/templates/host/ios/SuperApp/Info.plist +76 -0
- package/templates/host/ios/SuperApp/LaunchScreen.storyboard +47 -0
- package/templates/host/ios/SuperApp/PrivacyInfo.xcprivacy +48 -0
- package/templates/host/ios/SuperApp/SceneDelegate.swift +47 -0
- package/templates/host/ios/SuperApp/SuperApp-Bridging-Header.h +18 -0
- package/templates/host/ios/SuperApp-Bridging-Header.h +21 -0
- package/templates/host/ios/SuperApp.xcodeproj/project.pbxproj +500 -0
- package/templates/host/ios/SuperApp.xcodeproj/xcshareddata/xcschemes/RNTest.xcscheme +88 -0
- package/templates/host/jest.config.js +3 -0
- package/templates/host/metro.config.js +49 -0
- package/templates/host/package.json +66 -0
- package/templates/host/tsconfig.json +8 -0
- package/templates/miniapp/.watchmanconfig +1 -0
- package/templates/miniapp/app.json +12 -0
- package/templates/miniapp/babel.config.js +3 -0
- package/templates/miniapp/package.json +34 -0
- package/templates/miniapp/src/pages/home/index.tsx +122 -0
- package/templates/miniapp/src/pages/home/page.config.ts +10 -0
- package/templates/miniapp/tsconfig.json +11 -0
- package/templates/runner/.eslintrc.js +7 -0
- package/templates/runner/App.tsx +515 -0
- package/templates/runner/Gemfile +16 -0
- package/templates/runner/__tests__/App.test.tsx +13 -0
- package/templates/runner/android/app/build.gradle +55 -0
- package/templates/runner/android/app/debug.keystore +0 -0
- package/templates/runner/android/app/proguard-rules.pro +10 -0
- package/templates/runner/android/app/src/main/AndroidManifest.xml +60 -0
- package/templates/runner/android/app/src/main/java/com/devrunner/MainActivity.kt +14 -0
- package/templates/runner/android/app/src/main/java/com/devrunner/MainApplication.kt +35 -0
- package/templates/runner/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/templates/runner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/templates/runner/android/app/src/main/res/values/strings.xml +3 -0
- package/templates/runner/android/app/src/main/res/values/styles.xml +16 -0
- package/templates/runner/android/build.gradle +21 -0
- package/templates/runner/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/runner/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/runner/android/gradle.properties +44 -0
- package/templates/runner/android/gradlew +251 -0
- package/templates/runner/android/gradlew.bat +99 -0
- package/templates/runner/android/settings.gradle +6 -0
- package/templates/runner/app.json +4 -0
- package/templates/runner/babel.config.js +4 -0
- package/templates/runner/index.js +9 -0
- package/templates/runner/ios/.xcode.env +11 -0
- package/templates/runner/ios/DevRunner/AppDelegate.swift +53 -0
- package/templates/runner/ios/DevRunner/DevRunner-Bridging-Header.h +19 -0
- package/templates/runner/ios/DevRunner/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/templates/runner/ios/DevRunner/Images.xcassets/Contents.json +6 -0
- package/templates/runner/ios/DevRunner/Info.plist +51 -0
- package/templates/runner/ios/DevRunner/LaunchScreen.storyboard +47 -0
- package/templates/runner/ios/DevRunner/PrivacyInfo.xcprivacy +48 -0
- package/templates/runner/ios/DevRunner/SceneDelegate.swift +21 -0
- package/templates/runner/ios/DevRunner-Bridging-Header.h +22 -0
- package/templates/runner/ios/DevRunner.xcodeproj/project.pbxproj +500 -0
- package/templates/runner/ios/DevRunner.xcodeproj/xcshareddata/xcschemes/DevRunner.xcscheme +88 -0
- package/templates/runner/ios/Podfile +38 -0
- package/templates/runner/jest.config.js +3 -0
- package/templates/runner/metro.config.js +3 -0
- package/templates/runner/package.json +38 -0
- package/templates/runner/src/RunnerHomeScreen.tsx +499 -0
- package/templates/runner/src/assets/icon_close.png +0 -0
- package/templates/runner/src/assets/icon_pic.png +0 -0
- package/templates/runner/src/runnerTypes.ts +31 -0
- package/templates/runner/tsconfig.json +8 -0
- package/templates/versions.json +6 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Alert,
|
|
5
|
+
Image,
|
|
6
|
+
Linking,
|
|
7
|
+
Platform,
|
|
8
|
+
Pressable,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
Text,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import Animated, {
|
|
13
|
+
Easing,
|
|
14
|
+
FadeInUp,
|
|
15
|
+
FadeOut,
|
|
16
|
+
FadeOutDown,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
|
19
|
+
import { defaultHostApis } from '@nebula-rn/host-apis';
|
|
20
|
+
import type { MiniappLoadingResolveContext } from '@nebula-rn/sdk';
|
|
21
|
+
import { NebulaAPI } from '@nebula-rn/sdk';
|
|
22
|
+
import { RunnerHomeScreen } from './src/RunnerHomeScreen';
|
|
23
|
+
import type { RunnerHistoryItem, RuntimeManifest } from './src/runnerTypes';
|
|
24
|
+
|
|
25
|
+
const DEFAULT_RUNNER_BASE_URL = 'http://localhost:8082';
|
|
26
|
+
|
|
27
|
+
type RunnerSessionTarget = {
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
bundleUrl?: string | null;
|
|
30
|
+
manifestUrl?: string | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function normalizeRunnerBaseUrl(value: string): string {
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
if (!trimmed) {
|
|
36
|
+
return DEFAULT_RUNNER_BASE_URL;
|
|
37
|
+
}
|
|
38
|
+
return trimmed.replace(/\/$/, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseRunnerInput(value: string): RunnerSessionTarget {
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
if (!trimmed) {
|
|
44
|
+
return { baseUrl: DEFAULT_RUNNER_BASE_URL };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const parsed = new URL(trimmed);
|
|
49
|
+
if (parsed.pathname.endsWith('.bundle')) {
|
|
50
|
+
return {
|
|
51
|
+
baseUrl: normalizeRunnerBaseUrl(parsed.origin),
|
|
52
|
+
bundleUrl: trimmed,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (parsed.pathname.endsWith('.json')) {
|
|
56
|
+
return {
|
|
57
|
+
baseUrl: normalizeRunnerBaseUrl(parsed.origin),
|
|
58
|
+
manifestUrl: trimmed,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
return { baseUrl: normalizeRunnerBaseUrl(trimmed) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { baseUrl: normalizeRunnerBaseUrl(trimmed) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseRunnerLaunchUrl(url: string): RunnerSessionTarget | null {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = new URL(url);
|
|
71
|
+
|
|
72
|
+
if (parsed.protocol !== 'devrunner:' || parsed.hostname !== 'open') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const manifestUrl = parsed.searchParams.get('manifestUrl');
|
|
77
|
+
const bundleUrl = parsed.searchParams.get('bundleUrl');
|
|
78
|
+
const baseUrl = parsed.searchParams.get('baseUrl');
|
|
79
|
+
|
|
80
|
+
if (manifestUrl) {
|
|
81
|
+
const manifest = new URL(manifestUrl);
|
|
82
|
+
return {
|
|
83
|
+
baseUrl: normalizeRunnerBaseUrl(manifest.origin),
|
|
84
|
+
manifestUrl,
|
|
85
|
+
bundleUrl,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (bundleUrl) {
|
|
90
|
+
const bundle = new URL(bundleUrl);
|
|
91
|
+
return {
|
|
92
|
+
baseUrl: normalizeRunnerBaseUrl(bundle.origin),
|
|
93
|
+
bundleUrl,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (baseUrl) {
|
|
98
|
+
return { baseUrl: normalizeRunnerBaseUrl(baseUrl) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getMiniappBundleUrl(
|
|
108
|
+
sessionTarget: RunnerSessionTarget,
|
|
109
|
+
manifest: RuntimeManifest | null,
|
|
110
|
+
): string {
|
|
111
|
+
if (sessionTarget.bundleUrl) {
|
|
112
|
+
return sessionTarget.bundleUrl;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const baseUrl = normalizeRunnerBaseUrl(sessionTarget.baseUrl);
|
|
116
|
+
const bundlePath = manifest?.bundlePath || '/index.bundle';
|
|
117
|
+
return `${baseUrl}${bundlePath}?platform=${Platform.OS}&dev=true&minify=false`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildHistoryItem(
|
|
121
|
+
sessionTarget: RunnerSessionTarget,
|
|
122
|
+
manifest: RuntimeManifest,
|
|
123
|
+
): RunnerHistoryItem {
|
|
124
|
+
return {
|
|
125
|
+
appId: manifest.appId,
|
|
126
|
+
baseUrl: normalizeRunnerBaseUrl(sessionTarget.baseUrl),
|
|
127
|
+
bundleUrl: sessionTarget.bundleUrl ?? null,
|
|
128
|
+
manifestUrl: sessionTarget.manifestUrl ?? null,
|
|
129
|
+
title: manifest.window?.navigationBarTitleText || manifest.appId,
|
|
130
|
+
version: manifest.version,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function mergeHistory(
|
|
135
|
+
history: RunnerHistoryItem[],
|
|
136
|
+
item: RunnerHistoryItem,
|
|
137
|
+
): RunnerHistoryItem[] {
|
|
138
|
+
const deduped = history.filter(
|
|
139
|
+
entry =>
|
|
140
|
+
!(
|
|
141
|
+
entry.appId === item.appId &&
|
|
142
|
+
entry.baseUrl === item.baseUrl &&
|
|
143
|
+
entry.bundleUrl === item.bundleUrl &&
|
|
144
|
+
entry.manifestUrl === item.manifestUrl
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
return [item, ...deduped].slice(0, 6);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function showActionError(error: unknown) {
|
|
151
|
+
console.error('[NebulaRunner] Action failed', error);
|
|
152
|
+
Alert.alert(
|
|
153
|
+
'Action Failed',
|
|
154
|
+
error instanceof Error ? error.message : 'Unexpected runner error.',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function DefaultMiniappLoadingScreen({
|
|
159
|
+
appId,
|
|
160
|
+
title,
|
|
161
|
+
status,
|
|
162
|
+
iconUrl,
|
|
163
|
+
}: MiniappLoadingResolveContext) {
|
|
164
|
+
const [closing, setClosing] = useState(false);
|
|
165
|
+
const message =
|
|
166
|
+
status === 'installing'
|
|
167
|
+
? 'Installing miniapp...'
|
|
168
|
+
: status === 'error'
|
|
169
|
+
? 'Failed to load miniapp.'
|
|
170
|
+
: 'Preparing miniapp...';
|
|
171
|
+
|
|
172
|
+
const closeMiniapp = async () => {
|
|
173
|
+
if (closing) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
setClosing(true);
|
|
179
|
+
await NebulaAPI.closeMiniApp(appId);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('[NebulaRunner] Failed to close miniapp', error);
|
|
182
|
+
Alert.alert('Close Failed', 'Unable to close the miniapp right now.');
|
|
183
|
+
} finally {
|
|
184
|
+
setClosing(false);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Animated.View
|
|
190
|
+
exiting={FadeOut.duration(160)}
|
|
191
|
+
style={styles.loadingOverlay}
|
|
192
|
+
>
|
|
193
|
+
<Animated.View
|
|
194
|
+
entering={FadeInUp.duration(520)
|
|
195
|
+
.delay(40)
|
|
196
|
+
.easing(Easing.out(Easing.cubic))
|
|
197
|
+
.withInitialValues({
|
|
198
|
+
transform: [{ translateY: 96 }, { scale: 0.96 }],
|
|
199
|
+
})}
|
|
200
|
+
exiting={FadeOutDown.duration(220).withInitialValues({
|
|
201
|
+
opacity: 1,
|
|
202
|
+
transform: [{ translateY: 0 }, { scale: 1 }],
|
|
203
|
+
})}
|
|
204
|
+
style={styles.loadingCard}
|
|
205
|
+
>
|
|
206
|
+
<Text style={styles.loadingEyebrow}>Nebula Runner</Text>
|
|
207
|
+
<ActivityIndicator
|
|
208
|
+
size="large"
|
|
209
|
+
color="#2563eb"
|
|
210
|
+
style={styles.loadingIndicator}
|
|
211
|
+
/>
|
|
212
|
+
{iconUrl ? (
|
|
213
|
+
<Image source={{ uri: iconUrl }} style={styles.loadingIcon} />
|
|
214
|
+
) : null}
|
|
215
|
+
<Text style={styles.loadingTitle}>{title || appId}</Text>
|
|
216
|
+
<Text style={styles.loadingMessage}>{message}</Text>
|
|
217
|
+
<Pressable
|
|
218
|
+
accessibilityRole="button"
|
|
219
|
+
disabled={closing}
|
|
220
|
+
onPress={closeMiniapp}
|
|
221
|
+
style={({ pressed }) => [
|
|
222
|
+
styles.loadingCloseButton,
|
|
223
|
+
pressed && !closing ? styles.loadingCloseButtonPressed : null,
|
|
224
|
+
closing ? styles.loadingCloseButtonDisabled : null,
|
|
225
|
+
]}
|
|
226
|
+
>
|
|
227
|
+
<Text style={styles.loadingCloseButtonText}>
|
|
228
|
+
{closing ? 'Closing...' : 'Close miniapp'}
|
|
229
|
+
</Text>
|
|
230
|
+
</Pressable>
|
|
231
|
+
</Animated.View>
|
|
232
|
+
</Animated.View>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function RunnerContent() {
|
|
237
|
+
const [baseUrl, setBaseUrl] = useState(DEFAULT_RUNNER_BASE_URL);
|
|
238
|
+
const [sessionTarget, setSessionTarget] = useState<RunnerSessionTarget>({
|
|
239
|
+
baseUrl: DEFAULT_RUNNER_BASE_URL,
|
|
240
|
+
});
|
|
241
|
+
const [manifest, setManifest] = useState<RuntimeManifest | null>(null);
|
|
242
|
+
const [status, setStatus] = useState('Ready to connect to a miniapp.');
|
|
243
|
+
const [loading, setLoading] = useState(false);
|
|
244
|
+
const [recentHistory, setRecentHistory] = useState<RunnerHistoryItem[]>([]);
|
|
245
|
+
const [urlExpanded, setUrlExpanded] = useState(true);
|
|
246
|
+
const bundleUrl = manifest
|
|
247
|
+
? getMiniappBundleUrl(sessionTarget, manifest)
|
|
248
|
+
: '';
|
|
249
|
+
|
|
250
|
+
const loadManifest = async (
|
|
251
|
+
nextSessionTarget: RunnerSessionTarget,
|
|
252
|
+
): Promise<RuntimeManifest> => {
|
|
253
|
+
const normalizedBaseUrl = normalizeRunnerBaseUrl(nextSessionTarget.baseUrl);
|
|
254
|
+
const manifestUrl =
|
|
255
|
+
nextSessionTarget.manifestUrl ??
|
|
256
|
+
`${normalizedBaseUrl}/.nebula/generated/app.json`;
|
|
257
|
+
|
|
258
|
+
const manifestResponse = await fetch(manifestUrl, {
|
|
259
|
+
headers: { 'Cache-Control': 'no-cache' },
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (!manifestResponse.ok) {
|
|
263
|
+
throw new Error(`Failed to load manifest (${manifestResponse.status})`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return (await manifestResponse.json()) as RuntimeManifest;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const applySession = (
|
|
270
|
+
nextSessionTarget: RunnerSessionTarget,
|
|
271
|
+
nextManifest: RuntimeManifest,
|
|
272
|
+
) => {
|
|
273
|
+
const normalizedBaseUrl = normalizeRunnerBaseUrl(nextSessionTarget.baseUrl);
|
|
274
|
+
const normalizedSession = {
|
|
275
|
+
baseUrl: normalizedBaseUrl,
|
|
276
|
+
bundleUrl: nextSessionTarget.bundleUrl ?? null,
|
|
277
|
+
manifestUrl: nextSessionTarget.manifestUrl ?? null,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
setBaseUrl(
|
|
281
|
+
normalizedSession.bundleUrl ??
|
|
282
|
+
normalizedSession.manifestUrl ??
|
|
283
|
+
normalizedSession.baseUrl,
|
|
284
|
+
);
|
|
285
|
+
setSessionTarget(normalizedSession);
|
|
286
|
+
setManifest(nextManifest);
|
|
287
|
+
setStatus(`Connected to ${nextManifest.appId} v${nextManifest.version}`);
|
|
288
|
+
setRecentHistory(history =>
|
|
289
|
+
mergeHistory(history, buildHistoryItem(normalizedSession, nextManifest)),
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const openMiniapp = async (
|
|
294
|
+
nextManifest: RuntimeManifest | null = manifest,
|
|
295
|
+
nextSessionTarget: RunnerSessionTarget = sessionTarget,
|
|
296
|
+
) => {
|
|
297
|
+
if (!nextManifest) {
|
|
298
|
+
Alert.alert(
|
|
299
|
+
'Miniapp Not Ready',
|
|
300
|
+
'Connect to a miniapp first so the runner can load its manifest.',
|
|
301
|
+
);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
await NebulaAPI.openMiniAppWithBundleURL(
|
|
306
|
+
nextManifest.appId,
|
|
307
|
+
getMiniappBundleUrl(nextSessionTarget, nextManifest),
|
|
308
|
+
{
|
|
309
|
+
title:
|
|
310
|
+
nextManifest.window?.navigationBarTitleText || nextManifest.appId,
|
|
311
|
+
},
|
|
312
|
+
true,
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const activateRunnerSession = async (
|
|
317
|
+
nextSessionTarget: RunnerSessionTarget,
|
|
318
|
+
options?: { openMiniapp?: boolean },
|
|
319
|
+
) => {
|
|
320
|
+
setLoading(true);
|
|
321
|
+
try {
|
|
322
|
+
const nextManifest = await loadManifest(nextSessionTarget);
|
|
323
|
+
applySession(nextSessionTarget, nextManifest);
|
|
324
|
+
if (options?.openMiniapp) {
|
|
325
|
+
await openMiniapp(nextManifest, {
|
|
326
|
+
baseUrl: normalizeRunnerBaseUrl(nextSessionTarget.baseUrl),
|
|
327
|
+
bundleUrl: nextSessionTarget.bundleUrl ?? null,
|
|
328
|
+
manifestUrl: nextSessionTarget.manifestUrl ?? null,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
setManifest(null);
|
|
333
|
+
setStatus(
|
|
334
|
+
error instanceof Error
|
|
335
|
+
? error.message
|
|
336
|
+
: 'Failed to activate runner session.',
|
|
337
|
+
);
|
|
338
|
+
throw error;
|
|
339
|
+
} finally {
|
|
340
|
+
setLoading(false);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const refreshRunner = async () => {
|
|
345
|
+
try {
|
|
346
|
+
await activateRunnerSession(sessionTarget);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('[NebulaRunner] Failed to refresh runner', error);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
const handleUrl = (url: string) => {
|
|
354
|
+
const nextSessionTarget = parseRunnerLaunchUrl(url);
|
|
355
|
+
|
|
356
|
+
if (!nextSessionTarget) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
activateRunnerSession(nextSessionTarget, { openMiniapp: true }).catch(
|
|
360
|
+
showActionError,
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
Linking.getInitialURL()
|
|
365
|
+
.then(initialUrl => {
|
|
366
|
+
if (initialUrl) {
|
|
367
|
+
handleUrl(initialUrl);
|
|
368
|
+
} else {
|
|
369
|
+
activateRunnerSession(parseRunnerInput(baseUrl)).catch(error => {
|
|
370
|
+
console.error(
|
|
371
|
+
'[NebulaRunner] Failed to refresh initial runner',
|
|
372
|
+
error,
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
.catch(error => {
|
|
378
|
+
console.error('[NebulaRunner] Failed to read initial URL', error);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const subscription = Linking.addEventListener('url', event => {
|
|
382
|
+
handleUrl(event.url);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return () => {
|
|
386
|
+
subscription.remove();
|
|
387
|
+
};
|
|
388
|
+
}, []);
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<SafeAreaView edges={['top', 'left', 'right']} style={styles.screen}>
|
|
392
|
+
<RunnerHomeScreen
|
|
393
|
+
baseUrl={baseUrl}
|
|
394
|
+
setBaseUrl={setBaseUrl}
|
|
395
|
+
manifest={manifest}
|
|
396
|
+
status={status}
|
|
397
|
+
serviceOnline={manifest != null}
|
|
398
|
+
loading={loading}
|
|
399
|
+
bundleUrl={bundleUrl}
|
|
400
|
+
recentHistory={recentHistory}
|
|
401
|
+
clearRecentHistory={() => setRecentHistory([])}
|
|
402
|
+
urlExpanded={urlExpanded}
|
|
403
|
+
setUrlExpanded={setUrlExpanded}
|
|
404
|
+
onConnect={() => {
|
|
405
|
+
activateRunnerSession(parseRunnerInput(baseUrl), {
|
|
406
|
+
openMiniapp: true,
|
|
407
|
+
}).catch(showActionError);
|
|
408
|
+
}}
|
|
409
|
+
onRefresh={refreshRunner}
|
|
410
|
+
onOpenCurrent={() => {
|
|
411
|
+
openMiniapp().catch(error => {
|
|
412
|
+
console.error('[NebulaRunner] Failed to open miniapp', error);
|
|
413
|
+
});
|
|
414
|
+
}}
|
|
415
|
+
onSelectHistory={item => {
|
|
416
|
+
activateRunnerSession(
|
|
417
|
+
{
|
|
418
|
+
baseUrl: item.baseUrl,
|
|
419
|
+
bundleUrl: item.bundleUrl ?? null,
|
|
420
|
+
manifestUrl: item.manifestUrl ?? null,
|
|
421
|
+
},
|
|
422
|
+
{ openMiniapp: true },
|
|
423
|
+
).catch(showActionError);
|
|
424
|
+
}}
|
|
425
|
+
/>
|
|
426
|
+
</SafeAreaView>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function RunnerApp() {
|
|
431
|
+
return (
|
|
432
|
+
<SafeAreaProvider>
|
|
433
|
+
<RunnerContent />
|
|
434
|
+
</SafeAreaProvider>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export default NebulaAPI.wrap({
|
|
439
|
+
hostApis: defaultHostApis,
|
|
440
|
+
miniappLoading: {
|
|
441
|
+
component: DefaultMiniappLoadingScreen,
|
|
442
|
+
delayMs: 300,
|
|
443
|
+
enterContentDelayMs: 120,
|
|
444
|
+
},
|
|
445
|
+
})(RunnerApp);
|
|
446
|
+
|
|
447
|
+
const styles = StyleSheet.create({
|
|
448
|
+
screen: {
|
|
449
|
+
flex: 1,
|
|
450
|
+
backgroundColor: '#f4f2ff',
|
|
451
|
+
},
|
|
452
|
+
loadingOverlay: {
|
|
453
|
+
flex: 1,
|
|
454
|
+
backgroundColor: 'rgba(15, 23, 42, 0.14)',
|
|
455
|
+
alignItems: 'center',
|
|
456
|
+
justifyContent: 'center',
|
|
457
|
+
padding: 24,
|
|
458
|
+
},
|
|
459
|
+
loadingCard: {
|
|
460
|
+
width: '100%',
|
|
461
|
+
maxWidth: 420,
|
|
462
|
+
borderRadius: 24,
|
|
463
|
+
backgroundColor: '#ffffff',
|
|
464
|
+
padding: 24,
|
|
465
|
+
gap: 10,
|
|
466
|
+
},
|
|
467
|
+
loadingEyebrow: {
|
|
468
|
+
fontSize: 12,
|
|
469
|
+
fontWeight: '700',
|
|
470
|
+
letterSpacing: 1.2,
|
|
471
|
+
color: '#2563eb',
|
|
472
|
+
textTransform: 'uppercase',
|
|
473
|
+
},
|
|
474
|
+
loadingIndicator: {
|
|
475
|
+
marginTop: 4,
|
|
476
|
+
},
|
|
477
|
+
loadingIcon: {
|
|
478
|
+
width: 56,
|
|
479
|
+
height: 56,
|
|
480
|
+
borderRadius: 14,
|
|
481
|
+
},
|
|
482
|
+
loadingTitle: {
|
|
483
|
+
fontSize: 24,
|
|
484
|
+
fontWeight: '700',
|
|
485
|
+
color: '#0f172a',
|
|
486
|
+
},
|
|
487
|
+
loadingMessage: {
|
|
488
|
+
fontSize: 15,
|
|
489
|
+
lineHeight: 22,
|
|
490
|
+
color: '#475569',
|
|
491
|
+
},
|
|
492
|
+
loadingMeta: {
|
|
493
|
+
fontSize: 13,
|
|
494
|
+
color: '#64748b',
|
|
495
|
+
},
|
|
496
|
+
loadingCloseButton: {
|
|
497
|
+
marginTop: 8,
|
|
498
|
+
borderRadius: 999,
|
|
499
|
+
backgroundColor: '#0f172a',
|
|
500
|
+
paddingVertical: 12,
|
|
501
|
+
paddingHorizontal: 18,
|
|
502
|
+
alignItems: 'center',
|
|
503
|
+
},
|
|
504
|
+
loadingCloseButtonPressed: {
|
|
505
|
+
opacity: 0.88,
|
|
506
|
+
},
|
|
507
|
+
loadingCloseButtonDisabled: {
|
|
508
|
+
opacity: 0.6,
|
|
509
|
+
},
|
|
510
|
+
loadingCloseButtonText: {
|
|
511
|
+
color: '#ffffff',
|
|
512
|
+
fontSize: 14,
|
|
513
|
+
fontWeight: '600',
|
|
514
|
+
},
|
|
515
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
|
4
|
+
ruby ">= 2.6.10"
|
|
5
|
+
|
|
6
|
+
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
|
7
|
+
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
|
|
8
|
+
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
|
|
9
|
+
gem 'xcodeproj', '< 1.26.0'
|
|
10
|
+
gem 'concurrent-ruby', '< 1.3.4'
|
|
11
|
+
|
|
12
|
+
# Ruby 3.4.0 has removed some libraries from the standard library.
|
|
13
|
+
gem 'bigdecimal'
|
|
14
|
+
gem 'logger'
|
|
15
|
+
gem 'benchmark'
|
|
16
|
+
gem 'mutex_m'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @format
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import ReactTestRenderer from 'react-test-renderer';
|
|
7
|
+
import App from '../App';
|
|
8
|
+
|
|
9
|
+
test('renders correctly', async () => {
|
|
10
|
+
await ReactTestRenderer.act(() => {
|
|
11
|
+
ReactTestRenderer.create(<App />);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
apply plugin: "com.android.application"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
3
|
+
apply plugin: "com.facebook.react"
|
|
4
|
+
|
|
5
|
+
react {
|
|
6
|
+
autolinkLibrariesWithApp()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def enableProguardInReleaseBuilds = false
|
|
10
|
+
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
|
11
|
+
|
|
12
|
+
android {
|
|
13
|
+
ndkVersion rootProject.ext.ndkVersion
|
|
14
|
+
buildToolsVersion rootProject.ext.buildToolsVersion
|
|
15
|
+
compileSdk rootProject.ext.compileSdkVersion
|
|
16
|
+
|
|
17
|
+
namespace "__BUNDLE_ID__"
|
|
18
|
+
defaultConfig {
|
|
19
|
+
applicationId "__BUNDLE_ID__"
|
|
20
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
21
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
22
|
+
versionCode 1
|
|
23
|
+
versionName "1.0"
|
|
24
|
+
manifestPlaceholders = [nebulaUrlScheme: "devrunner"]
|
|
25
|
+
}
|
|
26
|
+
signingConfigs {
|
|
27
|
+
debug {
|
|
28
|
+
storeFile file('debug.keystore')
|
|
29
|
+
storePassword 'android'
|
|
30
|
+
keyAlias 'androiddebugkey'
|
|
31
|
+
keyPassword 'android'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
buildTypes {
|
|
35
|
+
debug {
|
|
36
|
+
signingConfig signingConfigs.debug
|
|
37
|
+
}
|
|
38
|
+
release {
|
|
39
|
+
signingConfig signingConfigs.debug
|
|
40
|
+
minifyEnabled enableProguardInReleaseBuilds
|
|
41
|
+
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
dependencies {
|
|
47
|
+
implementation("com.facebook.react:react-android")
|
|
48
|
+
implementation("androidx.appcompat:appcompat:1.7.1")
|
|
49
|
+
|
|
50
|
+
if (hermesEnabled.toBoolean()) {
|
|
51
|
+
implementation("com.facebook.react:hermes-android")
|
|
52
|
+
} else {
|
|
53
|
+
implementation jscFlavor
|
|
54
|
+
}
|
|
55
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Add project specific ProGuard rules here.
|
|
2
|
+
# By default, the flags in this file are appended to flags specified
|
|
3
|
+
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
|
4
|
+
# You can edit the include path and order by changing the proguardFiles
|
|
5
|
+
# directive in build.gradle.
|
|
6
|
+
#
|
|
7
|
+
# For more details, see
|
|
8
|
+
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
9
|
+
|
|
10
|
+
# Add any project specific keep options here:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
5
|
+
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
7
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
8
|
+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
9
|
+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
10
|
+
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
|
11
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
12
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
|
13
|
+
|
|
14
|
+
<application
|
|
15
|
+
android:name=".MainApplication"
|
|
16
|
+
android:label="@string/app_name"
|
|
17
|
+
android:icon="@mipmap/ic_launcher"
|
|
18
|
+
android:roundIcon="@mipmap/ic_launcher_round"
|
|
19
|
+
android:allowBackup="false"
|
|
20
|
+
android:theme="@style/AppTheme"
|
|
21
|
+
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
|
22
|
+
android:supportsRtl="true">
|
|
23
|
+
<activity
|
|
24
|
+
android:name=".MainActivity"
|
|
25
|
+
android:label="@string/app_name"
|
|
26
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
|
27
|
+
android:launchMode="singleTask"
|
|
28
|
+
android:windowSoftInputMode="adjustResize"
|
|
29
|
+
android:exported="true">
|
|
30
|
+
<intent-filter>
|
|
31
|
+
<action android:name="android.intent.action.MAIN" />
|
|
32
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
33
|
+
</intent-filter>
|
|
34
|
+
<intent-filter>
|
|
35
|
+
<action android:name="android.intent.action.VIEW" />
|
|
36
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
37
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
38
|
+
<data android:scheme="${nebulaUrlScheme}" android:host="review" android:pathPrefix="/install/" />
|
|
39
|
+
<data android:scheme="${nebulaUrlScheme}" android:host="miniapp" android:pathPrefix="/install/" />
|
|
40
|
+
</intent-filter>
|
|
41
|
+
<intent-filter>
|
|
42
|
+
<action android:name="android.intent.action.VIEW" />
|
|
43
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
44
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
45
|
+
<data android:scheme="devrunner" android:host="open" />
|
|
46
|
+
</intent-filter>
|
|
47
|
+
</activity>
|
|
48
|
+
<activity
|
|
49
|
+
android:name="com.hectorzhuang.nebula.NebulaActivity"
|
|
50
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
|
51
|
+
android:windowSoftInputMode="adjustResize"
|
|
52
|
+
android:exported="false" />
|
|
53
|
+
<activity
|
|
54
|
+
android:name="com.hectorzhuang.nebula.NebulaHostModalActivity"
|
|
55
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
|
56
|
+
android:theme="@style/NebulaHostModalTheme"
|
|
57
|
+
android:windowSoftInputMode="adjustResize"
|
|
58
|
+
android:exported="false" />
|
|
59
|
+
</application>
|
|
60
|
+
</manifest>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.devrunner
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactActivity
|
|
4
|
+
import com.facebook.react.ReactActivityDelegate
|
|
5
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
6
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
7
|
+
|
|
8
|
+
class MainActivity : ReactActivity() {
|
|
9
|
+
|
|
10
|
+
override fun getMainComponentName(): String = "__COMPONENT_NAME__"
|
|
11
|
+
|
|
12
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
|
13
|
+
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
|
14
|
+
}
|