expo-harmony-toolkit 1.7.2 → 1.8.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.en.md +25 -11
- package/README.md +27 -13
- package/build/core/autolinking.d.ts +10 -0
- package/build/core/autolinking.js +421 -0
- package/build/core/build/commands.d.ts +19 -0
- package/build/core/build/commands.js +61 -0
- package/build/core/build/compatibilityShims.d.ts +1 -0
- package/build/core/build/compatibilityShims.js +272 -0
- package/build/core/build/localHar.d.ts +13 -0
- package/build/core/build/localHar.js +337 -0
- package/build/core/build/reporting.d.ts +19 -0
- package/build/core/build/reporting.js +67 -0
- package/build/core/build/rnohCompatibility.d.ts +1 -0
- package/build/core/build/rnohCompatibility.js +38 -0
- package/build/core/build.d.ts +4 -12
- package/build/core/build.js +70 -762
- package/build/core/constants.d.ts +2 -1
- package/build/core/constants.js +3 -2
- package/build/core/dependencyInspection.d.ts +2 -0
- package/build/core/dependencyInspection.js +130 -0
- package/build/core/env.js +1 -1
- package/build/core/report.js +162 -10
- package/build/core/signing.d.ts +2 -0
- package/build/core/signing.js +30 -0
- package/build/core/template/capabilityRegistry.d.ts +9 -0
- package/build/core/template/capabilityRegistry.js +84 -0
- package/build/core/template/expoModulesCoreShim.d.ts +2 -0
- package/build/core/template/expoModulesCoreShim.js +279 -0
- package/build/core/template/metro.d.ts +2 -0
- package/build/core/template/metro.js +213 -0
- package/build/core/template/nativeFiles.d.ts +8 -0
- package/build/core/template/nativeFiles.js +333 -0
- package/build/core/template/renderers/camera.d.ts +4 -0
- package/build/core/template/renderers/camera.js +612 -0
- package/build/core/template/renderers/fileSystem.d.ts +4 -0
- package/build/core/template/renderers/fileSystem.js +814 -0
- package/build/core/template/renderers/imagePicker.d.ts +4 -0
- package/build/core/template/renderers/imagePicker.js +753 -0
- package/build/core/template/renderers/location.d.ts +4 -0
- package/build/core/template/renderers/location.js +836 -0
- package/build/core/template/runtimeShims.d.ts +2 -0
- package/build/core/template/runtimeShims.js +192 -0
- package/build/core/template/support.d.ts +11 -0
- package/build/core/template/support.js +108 -0
- package/build/core/template.d.ts +3 -6
- package/build/core/template.js +49 -4457
- package/build/data/capabilities.js +21 -4
- package/build/data/dependencyCatalog.js +10 -0
- package/build/data/publicDocs.d.ts +11 -0
- package/build/data/publicDocs.js +28 -0
- package/build/data/validatedMatrices.js +9 -1
- package/build/docs/render.d.ts +9 -0
- package/build/docs/render.js +250 -0
- package/build/types.d.ts +19 -0
- package/docs/cli-build.md +1 -1
- package/docs/npm-release.md +1 -0
- package/docs/official-app-shell-sample.md +2 -1
- package/docs/official-minimal-sample.md +2 -1
- package/docs/official-native-capabilities-sample.md +12 -2
- package/docs/official-ui-stack-sample.md +2 -1
- package/docs/roadmap.md +146 -51
- package/docs/signing-and-release.md +3 -0
- package/docs/support-matrix.md +59 -13
- package/package.json +13 -5
- package/docs/v1.5.1-acceptance.md +0 -385
- package/docs/v1.6.0-acceptance.md +0 -193
- package/docs/v1.7.0-acceptance.md +0 -111
- package/docs/v1.7.1-acceptance.md +0 -111
- package/docs/v1.7.2-acceptance.md +0 -37
package/build/core/template.js
CHANGED
|
@@ -3,27 +3,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.BUILD_REQUIRED_MANAGED_FILE_PATHS = void 0;
|
|
6
|
+
exports.BUILD_REQUIRED_MANAGED_FILE_PATHS = exports.usesExpoRouter = exports.resolveHarmonyBundleEntryFile = exports.buildDesiredPackageScripts = void 0;
|
|
7
7
|
exports.initProject = initProject;
|
|
8
8
|
exports.syncProjectTemplate = syncProjectTemplate;
|
|
9
|
-
exports.normalizeKnownHarmonyPackageJsons = normalizeKnownHarmonyPackageJsons;
|
|
10
|
-
exports.buildDesiredPackageScripts = buildDesiredPackageScripts;
|
|
11
|
-
exports.usesExpoRouter = usesExpoRouter;
|
|
12
|
-
exports.resolveHarmonyBundleEntryFile = resolveHarmonyBundleEntryFile;
|
|
13
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
|
-
const json5_1 = __importDefault(require("json5"));
|
|
15
|
-
const os_1 = __importDefault(require("os"));
|
|
16
10
|
const path_1 = __importDefault(require("path"));
|
|
17
|
-
const semver_1 = __importDefault(require("semver"));
|
|
18
11
|
const constants_1 = require("./constants");
|
|
19
12
|
const validatedMatrices_1 = require("../data/validatedMatrices");
|
|
20
13
|
const metadata_1 = require("./metadata");
|
|
21
14
|
const capabilities_1 = require("../data/capabilities");
|
|
22
|
-
const uiStack_1 = require("../data/uiStack");
|
|
23
15
|
const project_1 = require("./project");
|
|
24
16
|
const javascriptDependencies_1 = require("./javascriptDependencies");
|
|
25
17
|
const report_1 = require("./report");
|
|
26
18
|
const signing_1 = require("./signing");
|
|
19
|
+
const autolinking_1 = require("./autolinking");
|
|
20
|
+
const capabilityRegistry_1 = require("./template/capabilityRegistry");
|
|
21
|
+
const expoModulesCoreShim_1 = require("./template/expoModulesCoreShim");
|
|
22
|
+
const nativeFiles_1 = require("./template/nativeFiles");
|
|
23
|
+
const metro_1 = require("./template/metro");
|
|
24
|
+
const runtimeShims_1 = require("./template/runtimeShims");
|
|
25
|
+
const support_1 = require("./template/support");
|
|
26
|
+
const constants_2 = require("./constants");
|
|
27
|
+
var support_2 = require("./template/support");
|
|
28
|
+
Object.defineProperty(exports, "buildDesiredPackageScripts", { enumerable: true, get: function () { return support_2.buildDesiredPackageScripts; } });
|
|
29
|
+
Object.defineProperty(exports, "resolveHarmonyBundleEntryFile", { enumerable: true, get: function () { return support_2.resolveHarmonyBundleEntryFile; } });
|
|
30
|
+
Object.defineProperty(exports, "usesExpoRouter", { enumerable: true, get: function () { return support_2.usesExpoRouter; } });
|
|
27
31
|
const TEMPLATE_ROOT = path_1.default.resolve(__dirname, '..', '..', 'templates', 'harmony');
|
|
28
32
|
const TEMPLATE_FILE_PATHS = [
|
|
29
33
|
'README.md',
|
|
@@ -53,35 +57,16 @@ const TEMPLATE_FILE_PATHS = [
|
|
|
53
57
|
'entry/src/main/resources/base/profile/main_pages.json',
|
|
54
58
|
'entry/src/main/resources/rawfile/.gitkeep',
|
|
55
59
|
];
|
|
56
|
-
const AUTOLINKED_FILE_PATHS = [
|
|
57
|
-
path_1.default.join('harmony', 'oh-package.json5'),
|
|
58
|
-
path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'RNOHPackagesFactory.ets'),
|
|
59
|
-
path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'RNOHPackagesFactory.h'),
|
|
60
|
-
path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'autolinking.cmake'),
|
|
61
|
-
];
|
|
62
60
|
const RNOH_GENERATED_TS_SHIM_RELATIVE_PATH = path_1.default.join('harmony', 'oh_modules', '@rnoh', 'react-native-openharmony', 'ts.ts');
|
|
63
61
|
exports.BUILD_REQUIRED_MANAGED_FILE_PATHS = [
|
|
64
|
-
...AUTOLINKED_FILE_PATHS,
|
|
62
|
+
...autolinking_1.AUTOLINKED_FILE_PATHS,
|
|
65
63
|
RNOH_GENERATED_TS_SHIM_RELATIVE_PATH,
|
|
66
64
|
];
|
|
67
|
-
const HARMONY_PACKAGE_JSON_NORMALIZERS = {
|
|
68
|
-
'@react-native-oh-tpl/react-native-gesture-handler': (packageJson) => {
|
|
69
|
-
const harmony = packageJson.harmony && typeof packageJson.harmony === 'object' && !Array.isArray(packageJson.harmony)
|
|
70
|
-
? { ...packageJson.harmony }
|
|
71
|
-
: null;
|
|
72
|
-
if (!harmony || !('codegenConfig' in harmony)) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
delete harmony.codegenConfig;
|
|
76
|
-
return {
|
|
77
|
-
...packageJson,
|
|
78
|
-
harmony,
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
65
|
async function initProject(projectRoot, force = false) {
|
|
83
66
|
const report = await (0, report_1.buildDoctorReport)(projectRoot);
|
|
84
|
-
const sync = await syncProjectTemplate(projectRoot, force
|
|
67
|
+
const sync = await syncProjectTemplate(projectRoot, force, {
|
|
68
|
+
doctorReport: report,
|
|
69
|
+
});
|
|
85
70
|
const packageWarnings = await syncPackageScripts(projectRoot, force);
|
|
86
71
|
const doctorReportPath = await (0, report_1.writeDoctorReport)(projectRoot, report);
|
|
87
72
|
return {
|
|
@@ -95,7 +80,8 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
95
80
|
const loadedProject = await (0, project_1.loadProject)(projectRoot);
|
|
96
81
|
const identifiers = (0, project_1.deriveHarmonyIdentifiers)(loadedProject.expoConfig, loadedProject.packageJson);
|
|
97
82
|
const previousToolkitConfig = await (0, metadata_1.readToolkitConfig)(loadedProject.projectRoot);
|
|
98
|
-
const
|
|
83
|
+
const doctorReport = options.doctorReport ?? (await (0, report_1.buildDoctorReport)(loadedProject.projectRoot));
|
|
84
|
+
const desiredFiles = await buildManagedFiles(loadedProject, identifiers, previousToolkitConfig, doctorReport);
|
|
99
85
|
const previousManifest = await (0, metadata_1.readManifest)(loadedProject.projectRoot);
|
|
100
86
|
const forceManagedPaths = new Set(options.forceManagedPaths ?? []);
|
|
101
87
|
const result = {
|
|
@@ -105,7 +91,7 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
105
91
|
warnings: [],
|
|
106
92
|
manifestPath: (0, metadata_1.getManifestPath)(loadedProject.projectRoot),
|
|
107
93
|
};
|
|
108
|
-
result.warnings.push(...collectMetadataWarnings(previousManifest, previousToolkitConfig));
|
|
94
|
+
result.warnings.push(...(0, support_1.collectMetadataWarnings)(previousManifest, previousToolkitConfig));
|
|
109
95
|
const manifestFiles = [];
|
|
110
96
|
for (const file of desiredFiles) {
|
|
111
97
|
const targetPath = path_1.default.join(loadedProject.projectRoot, file.relativePath);
|
|
@@ -113,7 +99,7 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
113
99
|
const previousRecord = previousManifest?.files.find((record) => record.relativePath === file.relativePath);
|
|
114
100
|
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
115
101
|
const currentContents = await fs_extra_1.default.readFile(targetPath);
|
|
116
|
-
if (contentsEqual(currentContents, file.contents, file.binary)) {
|
|
102
|
+
if ((0, support_1.contentsEqual)(currentContents, file.contents, file.binary)) {
|
|
117
103
|
result.unchangedFiles.push(file.relativePath);
|
|
118
104
|
manifestFiles.push({ relativePath: file.relativePath, sha1: expectedHash });
|
|
119
105
|
continue;
|
|
@@ -147,9 +133,9 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
147
133
|
await fs_extra_1.default.writeJson(result.manifestPath, manifest, { spaces: 2 });
|
|
148
134
|
return result;
|
|
149
135
|
}
|
|
150
|
-
async function buildManagedFiles(loadedProject, identifiers, previousToolkitConfig) {
|
|
151
|
-
const hasExpoRouter = usesExpoRouter(loadedProject.packageJson);
|
|
152
|
-
const enabledCapabilities =
|
|
136
|
+
async function buildManagedFiles(loadedProject, identifiers, previousToolkitConfig, doctorReport) {
|
|
137
|
+
const hasExpoRouter = (0, support_1.usesExpoRouter)(loadedProject.packageJson);
|
|
138
|
+
const enabledCapabilities = doctorReport.capabilities;
|
|
153
139
|
const hasManagedExpoHarmonyPackage = enabledCapabilities.some((capability) => capability.runtimeMode !== 'shim');
|
|
154
140
|
const requestedHarmonyPermissions = (0, capabilities_1.collectCapabilityHarmonyPermissions)(loadedProject.packageJson);
|
|
155
141
|
const signingLocalConfig = await (0, signing_1.readSigningLocalConfig)(loadedProject.projectRoot);
|
|
@@ -157,7 +143,7 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
157
143
|
const renderedHarmonyRootPackage = renderTemplate(await fs_extra_1.default.readFile(path_1.default.join(TEMPLATE_ROOT, 'oh-package.json5'), 'utf8'), loadedProject, identifiers, hvigorPluginFilename);
|
|
158
144
|
const templateFiles = await Promise.all(TEMPLATE_FILE_PATHS.map(async (relativePath) => {
|
|
159
145
|
const templatePath = path_1.default.join(TEMPLATE_ROOT, relativePath);
|
|
160
|
-
const binary = isBinaryTemplate(relativePath);
|
|
146
|
+
const binary = (0, support_1.isBinaryTemplate)(relativePath);
|
|
161
147
|
const rawContents = await fs_extra_1.default.readFile(templatePath);
|
|
162
148
|
const contents = binary
|
|
163
149
|
? rawContents
|
|
@@ -171,18 +157,18 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
171
157
|
: relativePath === 'entry/src/main/resources/base/element/string.json'
|
|
172
158
|
? renderEntryStringResources(`${identifiers.appName} official minimal Harmony sample`, identifiers.appName, requestedHarmonyPermissions)
|
|
173
159
|
: relativePath === 'entry/src/main/ets/PackageProvider.ets'
|
|
174
|
-
? renderPackageProvider({
|
|
160
|
+
? (0, nativeFiles_1.renderPackageProvider)({
|
|
175
161
|
hasManagedExpoHarmonyPackage,
|
|
176
162
|
})
|
|
177
163
|
: relativePath === 'entry/src/main/cpp/PackageProvider.cpp'
|
|
178
|
-
? renderPackageProviderCpp({
|
|
164
|
+
? (0, nativeFiles_1.renderPackageProviderCpp)({
|
|
179
165
|
hasManagedExpoHarmonyPackage,
|
|
180
166
|
})
|
|
181
167
|
: contents,
|
|
182
168
|
binary,
|
|
183
169
|
};
|
|
184
170
|
}));
|
|
185
|
-
const autolinkedFiles = await buildAutolinkedManagedFiles(loadedProject.projectRoot, renderedHarmonyRootPackage);
|
|
171
|
+
const autolinkedFiles = await (0, autolinking_1.buildAutolinkedManagedFiles)(loadedProject.projectRoot, renderedHarmonyRootPackage);
|
|
186
172
|
const nextToolkitConfig = {
|
|
187
173
|
generatedAt: new Date().toISOString(),
|
|
188
174
|
toolkitVersion: constants_1.TOOLKIT_VERSION,
|
|
@@ -192,14 +178,17 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
192
178
|
rnohCliVersion: constants_1.RNOH_CLI_VERSION,
|
|
193
179
|
bundleName: identifiers.bundleName,
|
|
194
180
|
entryModuleName: identifiers.entryModuleName,
|
|
181
|
+
coverageProfile: doctorReport.coverageProfile,
|
|
195
182
|
capabilities: enabledCapabilities.map((capability) => ({
|
|
196
183
|
id: capability.id,
|
|
197
184
|
packageName: capability.packageName,
|
|
198
185
|
supportTier: capability.supportTier,
|
|
199
186
|
runtimeMode: capability.runtimeMode,
|
|
200
187
|
evidence: { ...capability.evidence },
|
|
188
|
+
evidenceSource: { ...capability.evidenceSource },
|
|
201
189
|
})),
|
|
202
190
|
requestedHarmonyPermissions,
|
|
191
|
+
nextActions: [...doctorReport.nextActions],
|
|
203
192
|
project: {
|
|
204
193
|
name: loadedProject.expoConfig.name ?? identifiers.appName,
|
|
205
194
|
slug: loadedProject.expoConfig.slug ?? identifiers.slug,
|
|
@@ -207,7 +196,7 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
207
196
|
hvigorPluginFilename,
|
|
208
197
|
},
|
|
209
198
|
};
|
|
210
|
-
const toolkitConfig = stabilizeToolkitConfigTimestamp(previousToolkitConfig, nextToolkitConfig);
|
|
199
|
+
const toolkitConfig = (0, support_1.stabilizeToolkitConfigTimestamp)(previousToolkitConfig, nextToolkitConfig);
|
|
211
200
|
return [
|
|
212
201
|
...templateFiles,
|
|
213
202
|
...autolinkedFiles,
|
|
@@ -217,29 +206,29 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
217
206
|
},
|
|
218
207
|
{
|
|
219
208
|
relativePath: 'metro.harmony.config.js',
|
|
220
|
-
contents: renderMetroConfig(enabledCapabilities),
|
|
209
|
+
contents: (0, metro_1.renderMetroConfig)(enabledCapabilities),
|
|
221
210
|
},
|
|
222
211
|
{
|
|
223
212
|
relativePath: path_1.default.join(constants_1.GENERATED_SHIMS_DIR, 'react-native-safe-area-context', 'index.js'),
|
|
224
|
-
contents: renderReactNativeSafeAreaContextHarmonyShim(),
|
|
213
|
+
contents: (0, runtimeShims_1.renderReactNativeSafeAreaContextHarmonyShim)(),
|
|
225
214
|
},
|
|
226
215
|
{
|
|
227
216
|
relativePath: path_1.default.join(constants_1.GENERATED_SHIMS_DIR, 'expo-modules-core', 'index.js'),
|
|
228
|
-
contents: renderExpoModulesCoreHarmonyShim(loadedProject.expoConfig, identifiers),
|
|
217
|
+
contents: (0, expoModulesCoreShim_1.renderExpoModulesCoreHarmonyShim)(loadedProject.expoConfig, identifiers),
|
|
229
218
|
},
|
|
230
219
|
...capabilities_1.CAPABILITY_DEFINITIONS.map((capability) => ({
|
|
231
220
|
relativePath: path_1.default.join(constants_1.GENERATED_SHIMS_DIR, capability.packageName, 'index.js'),
|
|
232
|
-
contents: renderCapabilityModuleShim(capability),
|
|
221
|
+
contents: (0, capabilityRegistry_1.renderCapabilityModuleShim)(capability),
|
|
233
222
|
})),
|
|
234
223
|
{
|
|
235
224
|
relativePath: constants_1.HARMONY_RUNTIME_PRELUDE_RELATIVE_PATH,
|
|
236
|
-
contents: renderHarmonyRuntimePrelude(),
|
|
225
|
+
contents: (0, runtimeShims_1.renderHarmonyRuntimePrelude)(),
|
|
237
226
|
},
|
|
238
227
|
...(hasExpoRouter
|
|
239
228
|
? [
|
|
240
229
|
{
|
|
241
230
|
relativePath: constants_1.HARMONY_ROUTER_ENTRY_FILENAME,
|
|
242
|
-
contents: renderRouterHarmonyEntry(identifiers),
|
|
231
|
+
contents: (0, support_1.renderRouterHarmonyEntry)(identifiers),
|
|
243
232
|
},
|
|
244
233
|
]
|
|
245
234
|
: []),
|
|
@@ -247,359 +236,33 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
247
236
|
? [
|
|
248
237
|
{
|
|
249
238
|
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'expoHarmony', 'ExpoHarmonyPackage.h'),
|
|
250
|
-
contents: renderExpoHarmonyCppPackage(),
|
|
239
|
+
contents: (0, nativeFiles_1.renderExpoHarmonyCppPackage)(),
|
|
251
240
|
},
|
|
252
241
|
{
|
|
253
242
|
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyPackage.ets'),
|
|
254
|
-
contents: renderExpoHarmonyPackage(),
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyFileSystemTurboModule.ts'),
|
|
258
|
-
contents: renderExpoHarmonyFileSystemTurboModule(),
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyImagePickerTurboModule.ts'),
|
|
262
|
-
contents: renderExpoHarmonyImagePickerTurboModule(),
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyLocationTurboModule.ts'),
|
|
266
|
-
contents: renderExpoHarmonyLocationTurboModule(),
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyCameraTurboModule.ts'),
|
|
270
|
-
contents: renderExpoHarmonyCameraTurboModule(),
|
|
243
|
+
contents: (0, nativeFiles_1.renderExpoHarmonyPackage)(),
|
|
271
244
|
},
|
|
245
|
+
...capabilityRegistry_1.MANAGED_EXPO_HARMONY_MODULE_RENDERERS.map(({ filename, render }) => ({
|
|
246
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', filename),
|
|
247
|
+
contents: render(),
|
|
248
|
+
})),
|
|
272
249
|
]
|
|
273
250
|
: []),
|
|
274
251
|
{
|
|
275
252
|
relativePath: path_1.default.join(constants_1.GENERATED_DIR, constants_1.TOOLKIT_CONFIG_FILENAME),
|
|
276
253
|
contents: JSON.stringify(toolkitConfig, null, 2) + '\n',
|
|
277
254
|
},
|
|
278
|
-
];
|
|
279
|
-
}
|
|
280
|
-
async function buildAutolinkedManagedFiles(projectRoot, harmonyRootPackageContents) {
|
|
281
|
-
const generated = await generateAutolinkingArtifacts(projectRoot, harmonyRootPackageContents);
|
|
282
|
-
return [
|
|
283
|
-
{
|
|
284
|
-
relativePath: AUTOLINKED_FILE_PATHS[0],
|
|
285
|
-
contents: generated.ohPackageContents,
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
relativePath: AUTOLINKED_FILE_PATHS[1],
|
|
289
|
-
contents: generated.etsFactoryContents,
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
relativePath: AUTOLINKED_FILE_PATHS[2],
|
|
293
|
-
contents: generated.cppFactoryContents,
|
|
294
|
-
},
|
|
295
255
|
{
|
|
296
|
-
relativePath:
|
|
297
|
-
contents:
|
|
256
|
+
relativePath: path_1.default.join(constants_1.GENERATED_DIR, constants_2.SIGNING_LOCAL_EXAMPLE_FILENAME),
|
|
257
|
+
contents: (0, signing_1.renderSigningLocalExampleConfig)(),
|
|
298
258
|
},
|
|
299
259
|
];
|
|
300
260
|
}
|
|
301
|
-
async function generateAutolinkingArtifacts(projectRoot, harmonyRootPackageContents) {
|
|
302
|
-
const rnohCliPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh/react-native-harmony-cli');
|
|
303
|
-
const managedOhPackageContents = await buildManagedHarmonyRootPackageContents(projectRoot, harmonyRootPackageContents);
|
|
304
|
-
const managedAutolinkingEntries = await resolveManagedAutolinkingEntries(projectRoot);
|
|
305
|
-
if (!rnohCliPackageJsonPath) {
|
|
306
|
-
return createEmptyAutolinkingArtifacts(managedOhPackageContents, managedAutolinkingEntries);
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
const restoreNormalizedHarmonyPackageJsons = await normalizeKnownHarmonyPackageJsons(projectRoot);
|
|
310
|
-
try {
|
|
311
|
-
const temporaryRoot = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'expo-harmony-autolinking-'));
|
|
312
|
-
try {
|
|
313
|
-
const temporaryHarmonyRoot = path_1.default.join(temporaryRoot, 'harmony');
|
|
314
|
-
await fs_extra_1.default.ensureDir(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'ets'));
|
|
315
|
-
await fs_extra_1.default.ensureDir(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp'));
|
|
316
|
-
await fs_extra_1.default.writeFile(path_1.default.join(temporaryHarmonyRoot, 'oh-package.json5'), harmonyRootPackageContents);
|
|
317
|
-
await runRnohLinkHarmonyCommand(projectRoot, rnohCliPackageJsonPath, temporaryHarmonyRoot);
|
|
318
|
-
const normalizedEtsFactoryContents = await normalizeAutolinkingEtsFactoryContents(projectRoot, await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'ets', 'RNOHPackagesFactory.ets'), 'utf8'));
|
|
319
|
-
const normalizedCppFactoryContents = await normalizeAutolinkingCppFactoryContents(projectRoot, await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp', 'RNOHPackagesFactory.h'), 'utf8'));
|
|
320
|
-
const normalizedCmakeContents = await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp', 'autolinking.cmake'), 'utf8');
|
|
321
|
-
return {
|
|
322
|
-
ohPackageContents: managedOhPackageContents,
|
|
323
|
-
etsFactoryContents: injectManagedAutolinkingIntoEtsFactory(normalizedEtsFactoryContents, managedAutolinkingEntries),
|
|
324
|
-
cppFactoryContents: injectManagedAutolinkingIntoCppFactory(normalizedCppFactoryContents, managedAutolinkingEntries),
|
|
325
|
-
cmakeContents: injectManagedAutolinkingIntoCmake(normalizedCmakeContents, managedAutolinkingEntries),
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
finally {
|
|
329
|
-
await fs_extra_1.default.remove(temporaryRoot);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
finally {
|
|
333
|
-
await restoreNormalizedHarmonyPackageJsons();
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
return createEmptyAutolinkingArtifacts(managedOhPackageContents, managedAutolinkingEntries);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
async function resolveManagedAutolinkingEntries(projectRoot) {
|
|
341
|
-
const entries = await Promise.all(uiStack_1.UI_STACK_VALIDATED_ADAPTERS.map(async (adapter) => {
|
|
342
|
-
if (adapter.supportsAutolinking || !adapter.managedAutolinking) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
const dependencySpecifier = await resolveHarmonyAdapterHarDependency(projectRoot, adapter.adapterPackageName);
|
|
346
|
-
if (!dependencySpecifier) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
return {
|
|
350
|
-
adapterPackageName: adapter.adapterPackageName,
|
|
351
|
-
...adapter.managedAutolinking,
|
|
352
|
-
};
|
|
353
|
-
}));
|
|
354
|
-
return entries.filter((entry) => entry !== null);
|
|
355
|
-
}
|
|
356
|
-
async function normalizeKnownHarmonyPackageJsons(projectRoot) {
|
|
357
|
-
const originalContentsByPath = new Map();
|
|
358
|
-
for (const [packageName, normalizePackageJson] of Object.entries(HARMONY_PACKAGE_JSON_NORMALIZERS)) {
|
|
359
|
-
const packageJsonPath = resolveProjectPackageJson(projectRoot, packageName);
|
|
360
|
-
if (!packageJsonPath || originalContentsByPath.has(packageJsonPath)) {
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
const currentContents = await fs_extra_1.default.readFile(packageJsonPath, 'utf8');
|
|
365
|
-
const packageJson = JSON.parse(currentContents);
|
|
366
|
-
const normalizedPackageJson = normalizePackageJson(packageJson);
|
|
367
|
-
if (!normalizedPackageJson) {
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
originalContentsByPath.set(packageJsonPath, currentContents);
|
|
371
|
-
await fs_extra_1.default.writeFile(packageJsonPath, JSON.stringify(normalizedPackageJson, null, 2) + '\n');
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
// Ignore malformed adapter package metadata and let downstream tooling surface the failure.
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return async () => {
|
|
378
|
-
for (const [packageJsonPath, originalContents] of originalContentsByPath) {
|
|
379
|
-
await fs_extra_1.default.writeFile(packageJsonPath, originalContents);
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
async function normalizeAutolinkingEtsFactoryContents(projectRoot, contents) {
|
|
384
|
-
let normalizedContents = contents
|
|
385
|
-
.replace(/import type \{\s*RNPackageContext\s*,\s*RNOHPackage\s*\} from '@rnoh\/react-native-openharmony';/, "import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';")
|
|
386
|
-
.replace(/export function createRNOHPackages\(([^)]*)\):\s*RNOHPackage\[\]/, 'export function createRNOHPackages($1): RNPackage[]');
|
|
387
|
-
const gestureHandlerPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh-tpl/react-native-gesture-handler');
|
|
388
|
-
if (!gestureHandlerPackageJsonPath) {
|
|
389
|
-
return normalizedContents;
|
|
390
|
-
}
|
|
391
|
-
try {
|
|
392
|
-
const gestureHandlerPackageJson = (await fs_extra_1.default.readJson(gestureHandlerPackageJsonPath));
|
|
393
|
-
if (gestureHandlerPackageJson.harmony?.codegenConfig) {
|
|
394
|
-
return normalizedContents;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
catch {
|
|
398
|
-
return normalizedContents;
|
|
399
|
-
}
|
|
400
|
-
let gestureHandlerPackageImportName = null;
|
|
401
|
-
normalizedContents = normalizedContents.replace(/import\s+([A-Za-z_$][\w$]*)\s+from ['"]@react-native-oh-tpl\/react-native-gesture-handler['"];?/, (_match, importName) => {
|
|
402
|
-
gestureHandlerPackageImportName = importName;
|
|
403
|
-
return "import { GestureHandlerPackage } from '@react-native-oh-tpl/react-native-gesture-handler/ts';";
|
|
404
|
-
});
|
|
405
|
-
if (!gestureHandlerPackageImportName) {
|
|
406
|
-
return normalizedContents;
|
|
407
|
-
}
|
|
408
|
-
return normalizedContents.replace(new RegExp(`new\\s+${escapeRegExp(gestureHandlerPackageImportName)}\\(ctx\\)`, 'g'), 'new GestureHandlerPackage(ctx)');
|
|
409
|
-
}
|
|
410
|
-
function injectManagedAutolinkingIntoEtsFactory(contents, entries) {
|
|
411
|
-
if (entries.length === 0) {
|
|
412
|
-
return contents;
|
|
413
|
-
}
|
|
414
|
-
const missingImports = entries
|
|
415
|
-
.filter((entry) => !contents.includes(`from '${entry.etsImportPath}'`))
|
|
416
|
-
.map((entry) => `import { ${entry.etsPackageName} } from '${entry.etsImportPath}';`);
|
|
417
|
-
if (missingImports.length > 0) {
|
|
418
|
-
contents = contents.replace("import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';", `import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';\n${missingImports.join('\n')}`);
|
|
419
|
-
}
|
|
420
|
-
const missingFactoryEntries = entries
|
|
421
|
-
.filter((entry) => !contents.includes(`new ${entry.etsPackageName}(ctx)`))
|
|
422
|
-
.map((entry) => ` new ${entry.etsPackageName}(ctx),`);
|
|
423
|
-
if (missingFactoryEntries.length > 0) {
|
|
424
|
-
contents = contents.replace(/return \[\n/, `return [\n${missingFactoryEntries.join('\n')}\n`);
|
|
425
|
-
}
|
|
426
|
-
return contents;
|
|
427
|
-
}
|
|
428
|
-
async function normalizeAutolinkingCppFactoryContents(projectRoot, contents) {
|
|
429
|
-
const gestureHandlerPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh-tpl/react-native-gesture-handler');
|
|
430
|
-
if (!gestureHandlerPackageJsonPath) {
|
|
431
|
-
return contents;
|
|
432
|
-
}
|
|
433
|
-
try {
|
|
434
|
-
const gestureHandlerPackageJson = (await fs_extra_1.default.readJson(gestureHandlerPackageJsonPath));
|
|
435
|
-
if (gestureHandlerPackageJson.harmony?.codegenConfig) {
|
|
436
|
-
return contents;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
catch {
|
|
440
|
-
return contents;
|
|
441
|
-
}
|
|
442
|
-
return contents
|
|
443
|
-
.replace(/#include "ReactNativeOhTplReactNativeGestureHandlerPackage\.h"/, '#include "RnohReactNativeHarmonyGestureHandlerPackage.h"')
|
|
444
|
-
.replace(/\brnoh::ReactNativeOhTplReactNativeGestureHandlerPackage\b/g, 'rnoh::RnohReactNativeHarmonyGestureHandlerPackage');
|
|
445
|
-
}
|
|
446
|
-
function injectManagedAutolinkingIntoCppFactory(contents, entries) {
|
|
447
|
-
if (entries.length === 0) {
|
|
448
|
-
return contents;
|
|
449
|
-
}
|
|
450
|
-
const missingIncludes = entries
|
|
451
|
-
.filter((entry) => !contents.includes(`#include "${entry.cppHeaderName}"`))
|
|
452
|
-
.map((entry) => `#include "${entry.cppHeaderName}"`);
|
|
453
|
-
if (missingIncludes.length > 0) {
|
|
454
|
-
contents = contents.replace('#include "RNOH/Package.h"', `#include "RNOH/Package.h"\n${missingIncludes.join('\n')}`);
|
|
455
|
-
}
|
|
456
|
-
const missingFactoryEntries = entries
|
|
457
|
-
.filter((entry) => !contents.includes(`std::make_shared<rnoh::${entry.cppPackageName}>(ctx)`))
|
|
458
|
-
.map((entry) => ` std::make_shared<rnoh::${entry.cppPackageName}>(ctx),`);
|
|
459
|
-
if (missingFactoryEntries.length > 0) {
|
|
460
|
-
contents = contents.replace(/return \{\n/, `return {\n${missingFactoryEntries.join('\n')}\n`);
|
|
461
|
-
}
|
|
462
|
-
return contents;
|
|
463
|
-
}
|
|
464
|
-
function injectManagedAutolinkingIntoCmake(contents, entries) {
|
|
465
|
-
if (entries.length === 0) {
|
|
466
|
-
return contents;
|
|
467
|
-
}
|
|
468
|
-
const missingSubdirectories = entries
|
|
469
|
-
.filter((entry) => !contents.includes(`./${entry.cmakeTargetName}`))
|
|
470
|
-
.map((entry) => ` add_subdirectory("\${OH_MODULES_DIR}/${entry.adapterPackageName}/src/main/cpp" ./${entry.cmakeTargetName})`);
|
|
471
|
-
if (missingSubdirectories.length > 0) {
|
|
472
|
-
contents = contents.replace(/function\(autolink_libraries target\)\n/, `function(autolink_libraries target)\n${missingSubdirectories.join('\n')}\n`);
|
|
473
|
-
}
|
|
474
|
-
const missingLibraryTargets = entries
|
|
475
|
-
.filter((entry) => !contents.includes(` ${entry.cmakeTargetName}`))
|
|
476
|
-
.map((entry) => ` ${entry.cmakeTargetName}`);
|
|
477
|
-
if (missingLibraryTargets.length > 0) {
|
|
478
|
-
contents = contents.replace(/set\(AUTOLINKED_LIBRARIES\n/, `set(AUTOLINKED_LIBRARIES\n${missingLibraryTargets.join('\n')}\n`);
|
|
479
|
-
}
|
|
480
|
-
return contents;
|
|
481
|
-
}
|
|
482
|
-
async function runRnohLinkHarmonyCommand(projectRoot, rnohCliPackageJsonPath, harmonyProjectPath) {
|
|
483
|
-
const rnohCliRoot = path_1.default.dirname(rnohCliPackageJsonPath);
|
|
484
|
-
const { commandLinkHarmony } = require(path_1.default.join(rnohCliRoot, 'dist', 'commands', 'link-harmony.js'));
|
|
485
|
-
await commandLinkHarmony.func([], {}, {
|
|
486
|
-
harmonyProjectPath,
|
|
487
|
-
nodeModulesPath: path_1.default.join(projectRoot, 'node_modules'),
|
|
488
|
-
cmakeAutolinkPathRelativeToHarmony: './entry/src/main/cpp/autolinking.cmake',
|
|
489
|
-
cppRnohPackagesFactoryPathRelativeToHarmony: './entry/src/main/cpp/RNOHPackagesFactory.h',
|
|
490
|
-
etsRnohPackagesFactoryPathRelativeToHarmony: './entry/src/main/ets/RNOHPackagesFactory.ets',
|
|
491
|
-
ohPackagePathRelativeToHarmony: './oh-package.json5',
|
|
492
|
-
includeNpmPackages: uiStack_1.UI_STACK_ADAPTER_PACKAGE_NAMES,
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
async function buildManagedHarmonyRootPackageContents(projectRoot, harmonyRootPackageContents) {
|
|
496
|
-
const parsedPackageJson = json5_1.default.parse(harmonyRootPackageContents);
|
|
497
|
-
const dependencies = {
|
|
498
|
-
...(parsedPackageJson.dependencies ?? {}),
|
|
499
|
-
...(await readPreservedHarmonyRootDependencies(projectRoot)),
|
|
500
|
-
};
|
|
501
|
-
for (const adapter of uiStack_1.UI_STACK_VALIDATED_ADAPTERS) {
|
|
502
|
-
const dependencySpecifier = await resolveHarmonyAdapterHarDependency(projectRoot, adapter.adapterPackageName);
|
|
503
|
-
if (dependencySpecifier) {
|
|
504
|
-
dependencies[adapter.adapterPackageName] = dependencySpecifier;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
parsedPackageJson.dependencies = sortRecordByKey(dependencies);
|
|
508
|
-
return json5_1.default.stringify(parsedPackageJson, null, 2) + '\n';
|
|
509
|
-
}
|
|
510
|
-
async function readPreservedHarmonyRootDependencies(projectRoot) {
|
|
511
|
-
const harmonyRootPackagePath = path_1.default.join(projectRoot, 'harmony', 'oh-package.json5');
|
|
512
|
-
if (!(await fs_extra_1.default.pathExists(harmonyRootPackagePath))) {
|
|
513
|
-
return {};
|
|
514
|
-
}
|
|
515
|
-
const currentHarmonyRootPackage = json5_1.default.parse(await fs_extra_1.default.readFile(harmonyRootPackagePath, 'utf8'));
|
|
516
|
-
const preservedDependencies = {};
|
|
517
|
-
const validatedAdapterPackageNames = new Set(uiStack_1.UI_STACK_ADAPTER_PACKAGE_NAMES);
|
|
518
|
-
for (const [packageName, specifier] of Object.entries(currentHarmonyRootPackage.dependencies ?? {})) {
|
|
519
|
-
if (typeof specifier !== 'string') {
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
522
|
-
if (validatedAdapterPackageNames.has(packageName)) {
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
preservedDependencies[packageName] = specifier;
|
|
526
|
-
}
|
|
527
|
-
return preservedDependencies;
|
|
528
|
-
}
|
|
529
|
-
async function resolveHarmonyAdapterHarDependency(projectRoot, adapterPackageName) {
|
|
530
|
-
const adapterEntry = uiStack_1.UI_STACK_VALIDATED_ADAPTERS.find((candidate) => candidate.adapterPackageName === adapterPackageName);
|
|
531
|
-
if (!adapterEntry) {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
const adapterRoot = path_1.default.join(projectRoot, 'node_modules', ...adapterPackageName.split('/'));
|
|
535
|
-
const harPath = path_1.default.join(adapterRoot, 'harmony', adapterEntry.harmonyHarFileName);
|
|
536
|
-
if (!(await fs_extra_1.default.pathExists(harPath))) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
const relativeHarPath = path_1.default.relative(path_1.default.join(projectRoot, 'harmony'), harPath).replace(/\\/g, '/');
|
|
540
|
-
return `file:${relativeHarPath}`;
|
|
541
|
-
}
|
|
542
|
-
function createEmptyAutolinkingArtifacts(harmonyRootPackageContents, managedAutolinkingEntries) {
|
|
543
|
-
return {
|
|
544
|
-
ohPackageContents: harmonyRootPackageContents,
|
|
545
|
-
etsFactoryContents: injectManagedAutolinkingIntoEtsFactory(`/*
|
|
546
|
-
* This file was generated by Expo Harmony Toolkit autolinking.
|
|
547
|
-
* DO NOT modify it manually, your changes WILL be overwritten.
|
|
548
|
-
*/
|
|
549
|
-
import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
|
|
550
|
-
|
|
551
|
-
export function createRNOHPackages(_ctx: RNPackageContext): RNPackage[] {
|
|
552
|
-
return [];
|
|
553
|
-
}
|
|
554
|
-
`, managedAutolinkingEntries),
|
|
555
|
-
cppFactoryContents: injectManagedAutolinkingIntoCppFactory(`/*
|
|
556
|
-
* This file was generated by Expo Harmony Toolkit autolinking.
|
|
557
|
-
* DO NOT modify it manually, your changes WILL be overwritten.
|
|
558
|
-
*/
|
|
559
|
-
#pragma once
|
|
560
|
-
#include "RNOH/Package.h"
|
|
561
|
-
|
|
562
|
-
std::vector<rnoh::Package::Shared> createRNOHPackages(const rnoh::Package::Context &_ctx) {
|
|
563
|
-
return {};
|
|
564
|
-
}
|
|
565
|
-
`, managedAutolinkingEntries),
|
|
566
|
-
cmakeContents: injectManagedAutolinkingIntoCmake(`# This file was generated by Expo Harmony Toolkit autolinking.
|
|
567
|
-
# DO NOT modify it manually, your changes WILL be overwritten.
|
|
568
|
-
cmake_minimum_required(VERSION 3.5)
|
|
569
|
-
|
|
570
|
-
function(autolink_libraries target)
|
|
571
|
-
set(AUTOLINKED_LIBRARIES
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
foreach(lib \${AUTOLINKED_LIBRARIES})
|
|
575
|
-
target_link_libraries(\${target} PUBLIC \${lib})
|
|
576
|
-
endforeach()
|
|
577
|
-
endfunction()
|
|
578
|
-
`, managedAutolinkingEntries),
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function resolveProjectPackageJson(projectRoot, request) {
|
|
582
|
-
const directPackageJsonPath = path_1.default.join(projectRoot, 'node_modules', ...request.split('/'), 'package.json');
|
|
583
|
-
if (fs_extra_1.default.existsSync(directPackageJsonPath)) {
|
|
584
|
-
return directPackageJsonPath;
|
|
585
|
-
}
|
|
586
|
-
try {
|
|
587
|
-
return require.resolve(path_1.default.join(request, 'package.json'), {
|
|
588
|
-
paths: [projectRoot],
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
catch {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
261
|
async function syncPackageScripts(projectRoot, _force) {
|
|
596
262
|
const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
|
|
597
263
|
const packageJson = (await fs_extra_1.default.readJson(packageJsonPath));
|
|
598
|
-
const desiredScripts = buildDesiredPackageScripts(packageJson);
|
|
599
|
-
const desiredPnpmOverrides = buildDesiredPnpmOverrides(packageJson);
|
|
264
|
+
const desiredScripts = (0, support_1.buildDesiredPackageScripts)(packageJson);
|
|
600
265
|
const scripts = { ...(packageJson.scripts ?? {}) };
|
|
601
|
-
const pnpm = packageJson.pnpm && typeof packageJson.pnpm === 'object' ? { ...packageJson.pnpm } : {};
|
|
602
|
-
const pnpmOverrides = { ...(pnpm.overrides ?? {}) };
|
|
603
266
|
const warnings = [];
|
|
604
267
|
let didChange = false;
|
|
605
268
|
for (const [scriptName, desiredCommand] of Object.entries(desiredScripts)) {
|
|
@@ -612,29 +275,13 @@ async function syncPackageScripts(projectRoot, _force) {
|
|
|
612
275
|
if (currentCommand === desiredCommand) {
|
|
613
276
|
continue;
|
|
614
277
|
}
|
|
615
|
-
if (isEquivalentToolkitScript(scriptName, currentCommand, desiredCommand)) {
|
|
278
|
+
if ((0, support_1.isEquivalentToolkitScript)(scriptName, currentCommand, desiredCommand)) {
|
|
616
279
|
continue;
|
|
617
280
|
}
|
|
618
281
|
warnings.push(`Left package.json script "${scriptName}" unchanged because it already exists with different contents.`);
|
|
619
282
|
}
|
|
620
|
-
for (const [packageName, desiredSpecifier] of Object.entries(desiredPnpmOverrides)) {
|
|
621
|
-
const currentSpecifier = pnpmOverrides[packageName];
|
|
622
|
-
if (!currentSpecifier) {
|
|
623
|
-
pnpmOverrides[packageName] = desiredSpecifier;
|
|
624
|
-
didChange = true;
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
if (currentSpecifier === desiredSpecifier) {
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
warnings.push(`Left package.json pnpm.overrides["${packageName}"] unchanged because it already exists with different contents.`);
|
|
631
|
-
}
|
|
632
283
|
if (didChange) {
|
|
633
|
-
packageJson.scripts = sortRecordByKey(scripts);
|
|
634
|
-
if (Object.keys(pnpmOverrides).length > 0) {
|
|
635
|
-
pnpm.overrides = sortRecordByKey(pnpmOverrides);
|
|
636
|
-
packageJson.pnpm = pnpm;
|
|
637
|
-
}
|
|
284
|
+
packageJson.scripts = (0, support_1.sortRecordByKey)(scripts);
|
|
638
285
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
639
286
|
await fs_extra_1.default.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
640
287
|
}
|
|
@@ -765,4058 +412,3 @@ function renderEntryStringResources(appDescription, appName, requestedHarmonyPer
|
|
|
765
412
|
string: stringEntries,
|
|
766
413
|
}, null, 2) + '\n';
|
|
767
414
|
}
|
|
768
|
-
function renderPackageProvider(options) {
|
|
769
|
-
const managedImports = options.hasManagedExpoHarmonyPackage
|
|
770
|
-
? "import { ExpoHarmonyPackage } from './expoHarmony/ExpoHarmonyPackage';\n"
|
|
771
|
-
: '';
|
|
772
|
-
const managedPackages = options.hasManagedExpoHarmonyPackage ? ', new ExpoHarmonyPackage(ctx)' : '';
|
|
773
|
-
return `import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
|
|
774
|
-
import { createRNOHPackages as createRNOHPackagesAutolinking } from './RNOHPackagesFactory';
|
|
775
|
-
${managedImports}
|
|
776
|
-
export function getRNOHPackages(ctx: RNPackageContext): RNPackage[] {
|
|
777
|
-
return [...createRNOHPackagesAutolinking(ctx)${managedPackages}];
|
|
778
|
-
}
|
|
779
|
-
`;
|
|
780
|
-
}
|
|
781
|
-
function renderPackageProviderCpp(options) {
|
|
782
|
-
const managedInclude = options.hasManagedExpoHarmonyPackage
|
|
783
|
-
? '#include "expoHarmony/ExpoHarmonyPackage.h"\n'
|
|
784
|
-
: '';
|
|
785
|
-
const managedPackage = options.hasManagedExpoHarmonyPackage
|
|
786
|
-
? ' packages.push_back(std::make_shared<ExpoHarmonyPackage>(ctx));\n'
|
|
787
|
-
: '';
|
|
788
|
-
return `#include "RNOH/PackageProvider.h"
|
|
789
|
-
#include "RNOHPackagesFactory.h"
|
|
790
|
-
#include "generated/RNOHGeneratedPackage.h"
|
|
791
|
-
${managedInclude}
|
|
792
|
-
using namespace rnoh;
|
|
793
|
-
|
|
794
|
-
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(
|
|
795
|
-
Package::Context ctx) {
|
|
796
|
-
auto packages = createRNOHPackages(ctx);
|
|
797
|
-
packages.push_back(std::make_shared<RNOHGeneratedPackage>(ctx));
|
|
798
|
-
${managedPackage} return packages;
|
|
799
|
-
}
|
|
800
|
-
`;
|
|
801
|
-
}
|
|
802
|
-
function renderExpoHarmonyCppPackage() {
|
|
803
|
-
return `#pragma once
|
|
804
|
-
|
|
805
|
-
#include <ReactCommon/TurboModule.h>
|
|
806
|
-
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
807
|
-
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
808
|
-
#include "RNOH/ArkTSTurboModule.h"
|
|
809
|
-
#include "RNOH/Package.h"
|
|
810
|
-
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
|
|
811
|
-
|
|
812
|
-
namespace rnoh {
|
|
813
|
-
using namespace facebook;
|
|
814
|
-
|
|
815
|
-
inline constexpr char ExpoHarmonyCameraViewComponentName[] =
|
|
816
|
-
"ExpoHarmonyCameraView";
|
|
817
|
-
using ExpoHarmonyCameraViewShadowNode =
|
|
818
|
-
react::ConcreteViewShadowNode<ExpoHarmonyCameraViewComponentName>;
|
|
819
|
-
using ExpoHarmonyCameraViewComponentDescriptor =
|
|
820
|
-
react::ConcreteComponentDescriptor<ExpoHarmonyCameraViewShadowNode>;
|
|
821
|
-
|
|
822
|
-
static jsi::Value __hostFunction_ExpoHarmonyFileSystemTurboModule_getConstants(
|
|
823
|
-
jsi::Runtime& rt,
|
|
824
|
-
react::TurboModule& turboModule,
|
|
825
|
-
const jsi::Value* args,
|
|
826
|
-
size_t count) {
|
|
827
|
-
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
828
|
-
.call(rt, "getConstants", args, count);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
static jsi::Value __hostFunction_ExpoHarmonyImagePickerTurboModule_getConstants(
|
|
832
|
-
jsi::Runtime& rt,
|
|
833
|
-
react::TurboModule& turboModule,
|
|
834
|
-
const jsi::Value* args,
|
|
835
|
-
size_t count) {
|
|
836
|
-
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
837
|
-
.call(rt, "getConstants", args, count);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
static jsi::Value __hostFunction_ExpoHarmonyLocationTurboModule_getConstants(
|
|
841
|
-
jsi::Runtime& rt,
|
|
842
|
-
react::TurboModule& turboModule,
|
|
843
|
-
const jsi::Value* args,
|
|
844
|
-
size_t count) {
|
|
845
|
-
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
846
|
-
.call(rt, "getConstants", args, count);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
static jsi::Value __hostFunction_ExpoHarmonyCameraTurboModule_getConstants(
|
|
850
|
-
jsi::Runtime& rt,
|
|
851
|
-
react::TurboModule& turboModule,
|
|
852
|
-
const jsi::Value* args,
|
|
853
|
-
size_t count) {
|
|
854
|
-
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
855
|
-
.call(rt, "getConstants", args, count);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
class JSI_EXPORT ExpoHarmonyFileSystemTurboModule : public ArkTSTurboModule {
|
|
859
|
-
public:
|
|
860
|
-
ExpoHarmonyFileSystemTurboModule(
|
|
861
|
-
const ArkTSTurboModule::Context ctx,
|
|
862
|
-
const std::string name)
|
|
863
|
-
: ArkTSTurboModule(ctx, name) {
|
|
864
|
-
methodMap_["getConstants"] = MethodMetadata{
|
|
865
|
-
0, __hostFunction_ExpoHarmonyFileSystemTurboModule_getConstants};
|
|
866
|
-
methodMap_["getInfo"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(getInfo)};
|
|
867
|
-
methodMap_["readAsString"] = MethodMetadata{
|
|
868
|
-
2, ARK_ASYNC_METHOD_CALLER(readAsString)};
|
|
869
|
-
methodMap_["writeAsString"] = MethodMetadata{
|
|
870
|
-
3, ARK_ASYNC_METHOD_CALLER(writeAsString)};
|
|
871
|
-
methodMap_["deletePath"] = MethodMetadata{
|
|
872
|
-
2, ARK_ASYNC_METHOD_CALLER(deletePath)};
|
|
873
|
-
methodMap_["makeDirectory"] = MethodMetadata{
|
|
874
|
-
2, ARK_ASYNC_METHOD_CALLER(makeDirectory)};
|
|
875
|
-
methodMap_["readDirectory"] = MethodMetadata{
|
|
876
|
-
1, ARK_ASYNC_METHOD_CALLER(readDirectory)};
|
|
877
|
-
methodMap_["copy"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(copy)};
|
|
878
|
-
methodMap_["move"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(move)};
|
|
879
|
-
methodMap_["download"] = MethodMetadata{
|
|
880
|
-
3, ARK_ASYNC_METHOD_CALLER(download)};
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
|
|
884
|
-
class JSI_EXPORT ExpoHarmonyImagePickerTurboModule : public ArkTSTurboModule {
|
|
885
|
-
public:
|
|
886
|
-
ExpoHarmonyImagePickerTurboModule(
|
|
887
|
-
const ArkTSTurboModule::Context ctx,
|
|
888
|
-
const std::string name)
|
|
889
|
-
: ArkTSTurboModule(ctx, name) {
|
|
890
|
-
methodMap_["getConstants"] = MethodMetadata{
|
|
891
|
-
0, __hostFunction_ExpoHarmonyImagePickerTurboModule_getConstants};
|
|
892
|
-
methodMap_["getMediaLibraryPermissionStatus"] = MethodMetadata{
|
|
893
|
-
1, ARK_ASYNC_METHOD_CALLER(getMediaLibraryPermissionStatus)};
|
|
894
|
-
methodMap_["requestMediaLibraryPermission"] = MethodMetadata{
|
|
895
|
-
1, ARK_ASYNC_METHOD_CALLER(requestMediaLibraryPermission)};
|
|
896
|
-
methodMap_["getCameraPermissionStatus"] = MethodMetadata{
|
|
897
|
-
0, ARK_ASYNC_METHOD_CALLER(getCameraPermissionStatus)};
|
|
898
|
-
methodMap_["requestCameraPermission"] = MethodMetadata{
|
|
899
|
-
0, ARK_ASYNC_METHOD_CALLER(requestCameraPermission)};
|
|
900
|
-
methodMap_["launchImageLibrary"] = MethodMetadata{
|
|
901
|
-
1, ARK_ASYNC_METHOD_CALLER(launchImageLibrary)};
|
|
902
|
-
methodMap_["launchCamera"] = MethodMetadata{
|
|
903
|
-
1, ARK_ASYNC_METHOD_CALLER(launchCamera)};
|
|
904
|
-
methodMap_["getPendingResult"] = MethodMetadata{
|
|
905
|
-
0, ARK_ASYNC_METHOD_CALLER(getPendingResult)};
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
class JSI_EXPORT ExpoHarmonyLocationTurboModule : public ArkTSTurboModule {
|
|
910
|
-
public:
|
|
911
|
-
ExpoHarmonyLocationTurboModule(
|
|
912
|
-
const ArkTSTurboModule::Context ctx,
|
|
913
|
-
const std::string name)
|
|
914
|
-
: ArkTSTurboModule(ctx, name) {
|
|
915
|
-
methodMap_["getConstants"] = MethodMetadata{
|
|
916
|
-
0, __hostFunction_ExpoHarmonyLocationTurboModule_getConstants};
|
|
917
|
-
methodMap_["getForegroundPermissionStatus"] = MethodMetadata{
|
|
918
|
-
0, ARK_ASYNC_METHOD_CALLER(getForegroundPermissionStatus)};
|
|
919
|
-
methodMap_["requestForegroundPermission"] = MethodMetadata{
|
|
920
|
-
0, ARK_ASYNC_METHOD_CALLER(requestForegroundPermission)};
|
|
921
|
-
methodMap_["getBackgroundPermissionStatus"] = MethodMetadata{
|
|
922
|
-
0, ARK_ASYNC_METHOD_CALLER(getBackgroundPermissionStatus)};
|
|
923
|
-
methodMap_["requestBackgroundPermission"] = MethodMetadata{
|
|
924
|
-
0, ARK_ASYNC_METHOD_CALLER(requestBackgroundPermission)};
|
|
925
|
-
methodMap_["hasServicesEnabled"] = MethodMetadata{
|
|
926
|
-
0, ARK_ASYNC_METHOD_CALLER(hasServicesEnabled)};
|
|
927
|
-
methodMap_["getProviderStatus"] = MethodMetadata{
|
|
928
|
-
0, ARK_ASYNC_METHOD_CALLER(getProviderStatus)};
|
|
929
|
-
methodMap_["getCurrentPosition"] = MethodMetadata{
|
|
930
|
-
1, ARK_ASYNC_METHOD_CALLER(getCurrentPosition)};
|
|
931
|
-
methodMap_["getLastKnownPosition"] = MethodMetadata{
|
|
932
|
-
1, ARK_ASYNC_METHOD_CALLER(getLastKnownPosition)};
|
|
933
|
-
methodMap_["geocode"] = MethodMetadata{1, ARK_ASYNC_METHOD_CALLER(geocode)};
|
|
934
|
-
methodMap_["reverseGeocode"] = MethodMetadata{
|
|
935
|
-
1, ARK_ASYNC_METHOD_CALLER(reverseGeocode)};
|
|
936
|
-
methodMap_["startWatchPosition"] = MethodMetadata{
|
|
937
|
-
2, ARK_ASYNC_METHOD_CALLER(startWatchPosition)};
|
|
938
|
-
methodMap_["stopWatchPosition"] = MethodMetadata{
|
|
939
|
-
1, ARK_ASYNC_METHOD_CALLER(stopWatchPosition)};
|
|
940
|
-
methodMap_["getHeading"] = MethodMetadata{
|
|
941
|
-
0, ARK_ASYNC_METHOD_CALLER(getHeading)};
|
|
942
|
-
methodMap_["startWatchHeading"] = MethodMetadata{
|
|
943
|
-
1, ARK_ASYNC_METHOD_CALLER(startWatchHeading)};
|
|
944
|
-
methodMap_["stopWatchHeading"] = MethodMetadata{
|
|
945
|
-
1, ARK_ASYNC_METHOD_CALLER(stopWatchHeading)};
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
class JSI_EXPORT ExpoHarmonyCameraTurboModule : public ArkTSTurboModule {
|
|
950
|
-
public:
|
|
951
|
-
ExpoHarmonyCameraTurboModule(
|
|
952
|
-
const ArkTSTurboModule::Context ctx,
|
|
953
|
-
const std::string name)
|
|
954
|
-
: ArkTSTurboModule(ctx, name) {
|
|
955
|
-
methodMap_["getConstants"] = MethodMetadata{
|
|
956
|
-
0, __hostFunction_ExpoHarmonyCameraTurboModule_getConstants};
|
|
957
|
-
methodMap_["getCameraPermissionStatus"] = MethodMetadata{
|
|
958
|
-
0, ARK_ASYNC_METHOD_CALLER(getCameraPermissionStatus)};
|
|
959
|
-
methodMap_["requestCameraPermission"] = MethodMetadata{
|
|
960
|
-
0, ARK_ASYNC_METHOD_CALLER(requestCameraPermission)};
|
|
961
|
-
methodMap_["getMicrophonePermissionStatus"] = MethodMetadata{
|
|
962
|
-
0, ARK_ASYNC_METHOD_CALLER(getMicrophonePermissionStatus)};
|
|
963
|
-
methodMap_["requestMicrophonePermission"] = MethodMetadata{
|
|
964
|
-
0, ARK_ASYNC_METHOD_CALLER(requestMicrophonePermission)};
|
|
965
|
-
methodMap_["createPreview"] = MethodMetadata{
|
|
966
|
-
1, ARK_ASYNC_METHOD_CALLER(createPreview)};
|
|
967
|
-
methodMap_["disposePreview"] = MethodMetadata{
|
|
968
|
-
1, ARK_ASYNC_METHOD_CALLER(disposePreview)};
|
|
969
|
-
methodMap_["pausePreview"] = MethodMetadata{
|
|
970
|
-
1, ARK_ASYNC_METHOD_CALLER(pausePreview)};
|
|
971
|
-
methodMap_["resumePreview"] = MethodMetadata{
|
|
972
|
-
1, ARK_ASYNC_METHOD_CALLER(resumePreview)};
|
|
973
|
-
methodMap_["takePicture"] = MethodMetadata{
|
|
974
|
-
1, ARK_ASYNC_METHOD_CALLER(takePicture)};
|
|
975
|
-
methodMap_["startRecording"] = MethodMetadata{
|
|
976
|
-
1, ARK_ASYNC_METHOD_CALLER(startRecording)};
|
|
977
|
-
methodMap_["stopRecording"] = MethodMetadata{
|
|
978
|
-
1, ARK_ASYNC_METHOD_CALLER(stopRecording)};
|
|
979
|
-
methodMap_["toggleRecording"] = MethodMetadata{
|
|
980
|
-
1, ARK_ASYNC_METHOD_CALLER(toggleRecording)};
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
class ExpoHarmonyTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
|
|
985
|
-
public:
|
|
986
|
-
SharedTurboModule createTurboModule(Context ctx, const std::string& name)
|
|
987
|
-
const override {
|
|
988
|
-
if (name == "ExpoHarmonyFileSystem") {
|
|
989
|
-
return std::make_shared<ExpoHarmonyFileSystemTurboModule>(ctx, name);
|
|
990
|
-
}
|
|
991
|
-
if (name == "ExpoHarmonyImagePicker") {
|
|
992
|
-
return std::make_shared<ExpoHarmonyImagePickerTurboModule>(ctx, name);
|
|
993
|
-
}
|
|
994
|
-
if (name == "ExpoHarmonyLocation") {
|
|
995
|
-
return std::make_shared<ExpoHarmonyLocationTurboModule>(ctx, name);
|
|
996
|
-
}
|
|
997
|
-
if (name == "ExpoHarmonyCamera") {
|
|
998
|
-
return std::make_shared<ExpoHarmonyCameraTurboModule>(ctx, name);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
return nullptr;
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
class ExpoHarmonyCameraViewJSIBinder : public ViewComponentJSIBinder {
|
|
1006
|
-
protected:
|
|
1007
|
-
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime& rt) override {
|
|
1008
|
-
auto nativeProps = ViewComponentJSIBinder::createNativeProps(rt);
|
|
1009
|
-
nativeProps.setProperty(rt, "viewId", "string");
|
|
1010
|
-
nativeProps.setProperty(rt, "facing", "string");
|
|
1011
|
-
nativeProps.setProperty(rt, "mode", "string");
|
|
1012
|
-
return nativeProps;
|
|
1013
|
-
}
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
class ExpoHarmonyPackage : public Package {
|
|
1017
|
-
public:
|
|
1018
|
-
explicit ExpoHarmonyPackage(Package::Context ctx) : Package(ctx) {}
|
|
1019
|
-
|
|
1020
|
-
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate()
|
|
1021
|
-
override {
|
|
1022
|
-
return std::make_unique<ExpoHarmonyTurboModuleFactoryDelegate>();
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
std::vector<facebook::react::ComponentDescriptorProvider>
|
|
1026
|
-
createComponentDescriptorProviders() override {
|
|
1027
|
-
return {
|
|
1028
|
-
facebook::react::concreteComponentDescriptorProvider<
|
|
1029
|
-
ExpoHarmonyCameraViewComponentDescriptor>(),
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
ComponentJSIBinderByString createComponentJSIBinderByName() override {
|
|
1034
|
-
auto binder = std::make_shared<ExpoHarmonyCameraViewJSIBinder>();
|
|
1035
|
-
return {
|
|
1036
|
-
{"ExpoHarmonyCameraView", binder},
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
} // namespace rnoh
|
|
1041
|
-
`;
|
|
1042
|
-
}
|
|
1043
|
-
function renderCapabilityModuleShim(capability) {
|
|
1044
|
-
switch (capability.packageName) {
|
|
1045
|
-
case 'expo-file-system':
|
|
1046
|
-
return capability.runtimeMode !== 'shim'
|
|
1047
|
-
? renderExpoFileSystemHarmonyAdapterShim(capability)
|
|
1048
|
-
: renderExpoFileSystemPreviewShim(capability);
|
|
1049
|
-
case 'expo-image-picker':
|
|
1050
|
-
return capability.runtimeMode !== 'shim'
|
|
1051
|
-
? renderExpoImagePickerHarmonyAdapterShim(capability)
|
|
1052
|
-
: renderExpoImagePickerPreviewShim(capability);
|
|
1053
|
-
case 'expo-location':
|
|
1054
|
-
return capability.runtimeMode !== 'shim'
|
|
1055
|
-
? renderExpoLocationHarmonyAdapterShim(capability)
|
|
1056
|
-
: renderExpoLocationPreviewShim(capability);
|
|
1057
|
-
case 'expo-camera':
|
|
1058
|
-
return capability.runtimeMode !== 'shim'
|
|
1059
|
-
? renderExpoCameraHarmonyAdapterShim(capability)
|
|
1060
|
-
: renderExpoCameraPreviewShim(capability);
|
|
1061
|
-
default:
|
|
1062
|
-
return renderUnsupportedCapabilityShim(capability);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
function renderMetroConfig(enabledCapabilities) {
|
|
1066
|
-
const previewCapabilityAliases = enabledCapabilities
|
|
1067
|
-
.filter((capability) => capability.supportTier === 'preview' || capability.supportTier === 'experimental')
|
|
1068
|
-
.map((capability) => ` '${capability.packageName}': path.resolve(__dirname, '.expo-harmony/shims/${capability.packageName}'),`)
|
|
1069
|
-
.join('\n');
|
|
1070
|
-
return `const fs = require('fs');
|
|
1071
|
-
const path = require('path');
|
|
1072
|
-
|
|
1073
|
-
process.env.EXPO_ROUTER_APP_ROOT = process.env.EXPO_ROUTER_APP_ROOT ?? 'app';
|
|
1074
|
-
|
|
1075
|
-
const { getDefaultConfig } = require('expo/metro-config');
|
|
1076
|
-
const { createHarmonyMetroConfig } = require('@react-native-oh/react-native-harmony/metro.config');
|
|
1077
|
-
|
|
1078
|
-
const defaultConfig = getDefaultConfig(__dirname);
|
|
1079
|
-
const harmonyConfig = createHarmonyMetroConfig({
|
|
1080
|
-
reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
|
|
1081
|
-
});
|
|
1082
|
-
const expoHarmonyShims = {
|
|
1083
|
-
'expo-modules-core': path.resolve(__dirname, '.expo-harmony/shims/expo-modules-core'),
|
|
1084
|
-
'react-native-safe-area-context': path.resolve(
|
|
1085
|
-
__dirname,
|
|
1086
|
-
'.expo-harmony/shims/react-native-safe-area-context',
|
|
1087
|
-
),
|
|
1088
|
-
${previewCapabilityAliases ? `${previewCapabilityAliases}\n` : ''}};
|
|
1089
|
-
const uiStackRootModuleAliases = {
|
|
1090
|
-
'react-native-gesture-handler': path.resolve(__dirname, 'node_modules/react-native-gesture-handler'),
|
|
1091
|
-
'react-native-reanimated': path.resolve(__dirname, 'node_modules/react-native-reanimated'),
|
|
1092
|
-
'react-native-svg': path.resolve(__dirname, 'node_modules/react-native-svg'),
|
|
1093
|
-
};
|
|
1094
|
-
const resolvePackageAlias = (context, moduleName, platform, aliases) => {
|
|
1095
|
-
for (const [aliasedModuleName, aliasedModulePath] of Object.entries(aliases)) {
|
|
1096
|
-
if (moduleName === aliasedModuleName) {
|
|
1097
|
-
return context.resolveRequest(context, aliasedModulePath, platform);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
if (moduleName.startsWith(\`\${aliasedModuleName}/\`)) {
|
|
1101
|
-
return context.resolveRequest(
|
|
1102
|
-
context,
|
|
1103
|
-
path.join(aliasedModulePath, moduleName.slice(aliasedModuleName.length + 1)),
|
|
1104
|
-
platform,
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
return null;
|
|
1110
|
-
};
|
|
1111
|
-
const resolveUiStackModuleAlias = (context, moduleName, platform) =>
|
|
1112
|
-
resolvePackageAlias(context, moduleName, platform, uiStackRootModuleAliases);
|
|
1113
|
-
const resolveExpoHarmonyModuleAlias = (context, moduleName, platform) =>
|
|
1114
|
-
resolvePackageAlias(context, moduleName, platform, expoHarmonyShims);
|
|
1115
|
-
const reactNativeCompatibilitySourceExts = ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'json'];
|
|
1116
|
-
const reactNativeCompatibilityPackageMarkers = [
|
|
1117
|
-
path.sep + '@react-native-oh' + path.sep + 'react-native-harmony' + path.sep,
|
|
1118
|
-
path.sep + 'react-native' + path.sep,
|
|
1119
|
-
];
|
|
1120
|
-
const findFirstExistingCompatibilityModule = (candidateBasePath, platforms) => {
|
|
1121
|
-
for (const candidatePlatform of platforms) {
|
|
1122
|
-
for (const candidateExtension of reactNativeCompatibilitySourceExts) {
|
|
1123
|
-
const candidatePath = candidatePlatform
|
|
1124
|
-
? \`\${candidateBasePath}.\${candidatePlatform}.\${candidateExtension}\`
|
|
1125
|
-
: \`\${candidateBasePath}.\${candidateExtension}\`;
|
|
1126
|
-
|
|
1127
|
-
if (fs.existsSync(candidatePath)) {
|
|
1128
|
-
return { candidatePath, candidatePlatform };
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
return null;
|
|
1134
|
-
};
|
|
1135
|
-
const resolveReactNativeCompatibilityWrapper = (context, moduleName, platform) => {
|
|
1136
|
-
if (platform !== 'harmony' || !context.originModulePath || !moduleName.startsWith('.')) {
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const originModulePath = context.originModulePath;
|
|
1141
|
-
const isReactNativeCompatibilityWrapper = reactNativeCompatibilityPackageMarkers.some((marker) =>
|
|
1142
|
-
originModulePath.includes(marker),
|
|
1143
|
-
);
|
|
1144
|
-
|
|
1145
|
-
if (!isReactNativeCompatibilityWrapper) {
|
|
1146
|
-
return null;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
const originExtension = path.extname(originModulePath);
|
|
1150
|
-
const originBasename = path.basename(originModulePath, originExtension);
|
|
1151
|
-
const candidateModulePath = path.resolve(path.dirname(originModulePath), moduleName);
|
|
1152
|
-
const candidateModuleExtension = path.extname(candidateModulePath);
|
|
1153
|
-
const candidateBasename = path.basename(candidateModulePath, candidateModuleExtension);
|
|
1154
|
-
|
|
1155
|
-
const candidateBasePath = candidateModuleExtension
|
|
1156
|
-
? candidateModulePath.slice(0, -candidateModuleExtension.length)
|
|
1157
|
-
: candidateModulePath;
|
|
1158
|
-
|
|
1159
|
-
if (candidateBasename === originBasename) {
|
|
1160
|
-
const compatibilityWrapperCandidate = findFirstExistingCompatibilityModule(candidateBasePath, [
|
|
1161
|
-
'harmony',
|
|
1162
|
-
'native',
|
|
1163
|
-
'android',
|
|
1164
|
-
'ios',
|
|
1165
|
-
]);
|
|
1166
|
-
|
|
1167
|
-
if (compatibilityWrapperCandidate) {
|
|
1168
|
-
return context.resolveRequest(
|
|
1169
|
-
context,
|
|
1170
|
-
compatibilityWrapperCandidate.candidatePath,
|
|
1171
|
-
compatibilityWrapperCandidate.candidatePlatform || platform,
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
return null;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
const standardResolutionCandidate = findFirstExistingCompatibilityModule(candidateBasePath, [
|
|
1179
|
-
'harmony',
|
|
1180
|
-
'native',
|
|
1181
|
-
'',
|
|
1182
|
-
]);
|
|
1183
|
-
|
|
1184
|
-
if (standardResolutionCandidate) {
|
|
1185
|
-
return null;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
const compatibilityFallbackCandidate = findFirstExistingCompatibilityModule(candidateBasePath, [
|
|
1189
|
-
'android',
|
|
1190
|
-
'ios',
|
|
1191
|
-
]);
|
|
1192
|
-
|
|
1193
|
-
if (compatibilityFallbackCandidate) {
|
|
1194
|
-
return context.resolveRequest(
|
|
1195
|
-
context,
|
|
1196
|
-
compatibilityFallbackCandidate.candidatePath,
|
|
1197
|
-
compatibilityFallbackCandidate.candidatePlatform || platform,
|
|
1198
|
-
);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
return null;
|
|
1202
|
-
};
|
|
1203
|
-
const resolveExpoHarmonyShim = (context, moduleName, platform) => {
|
|
1204
|
-
const uiStackModuleAliasResolution = resolveUiStackModuleAlias(context, moduleName, platform);
|
|
1205
|
-
|
|
1206
|
-
if (uiStackModuleAliasResolution) {
|
|
1207
|
-
return uiStackModuleAliasResolution;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
const expoHarmonyModuleAliasResolution = resolveExpoHarmonyModuleAlias(context, moduleName, platform);
|
|
1211
|
-
|
|
1212
|
-
if (expoHarmonyModuleAliasResolution) {
|
|
1213
|
-
return expoHarmonyModuleAliasResolution;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
const compatibilityWrapperResolution = resolveReactNativeCompatibilityWrapper(
|
|
1217
|
-
context,
|
|
1218
|
-
moduleName,
|
|
1219
|
-
platform,
|
|
1220
|
-
);
|
|
1221
|
-
|
|
1222
|
-
if (compatibilityWrapperResolution) {
|
|
1223
|
-
return compatibilityWrapperResolution;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
const harmonyResolveRequest = harmonyConfig.resolver?.resolveRequest;
|
|
1227
|
-
|
|
1228
|
-
if (typeof harmonyResolveRequest === 'function') {
|
|
1229
|
-
return harmonyResolveRequest(context, moduleName, platform);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
return context.resolveRequest(context, moduleName, platform);
|
|
1233
|
-
};
|
|
1234
|
-
|
|
1235
|
-
module.exports = {
|
|
1236
|
-
...defaultConfig,
|
|
1237
|
-
...harmonyConfig,
|
|
1238
|
-
projectRoot: __dirname,
|
|
1239
|
-
server: {
|
|
1240
|
-
...(defaultConfig.server ?? {}),
|
|
1241
|
-
...(harmonyConfig.server ?? {}),
|
|
1242
|
-
unstable_serverRoot: __dirname,
|
|
1243
|
-
},
|
|
1244
|
-
transformer: {
|
|
1245
|
-
...(defaultConfig.transformer ?? {}),
|
|
1246
|
-
...(harmonyConfig.transformer ?? {}),
|
|
1247
|
-
},
|
|
1248
|
-
serializer: {
|
|
1249
|
-
...(defaultConfig.serializer ?? {}),
|
|
1250
|
-
...(harmonyConfig.serializer ?? {}),
|
|
1251
|
-
},
|
|
1252
|
-
resolver: {
|
|
1253
|
-
...(defaultConfig.resolver ?? {}),
|
|
1254
|
-
...(harmonyConfig.resolver ?? {}),
|
|
1255
|
-
extraNodeModules: {
|
|
1256
|
-
...((defaultConfig.resolver?.extraNodeModules ?? {})),
|
|
1257
|
-
...((harmonyConfig.resolver?.extraNodeModules ?? {})),
|
|
1258
|
-
...uiStackRootModuleAliases,
|
|
1259
|
-
...expoHarmonyShims,
|
|
1260
|
-
},
|
|
1261
|
-
resolveRequest: resolveExpoHarmonyShim,
|
|
1262
|
-
sourceExts: [
|
|
1263
|
-
'harmony.ts',
|
|
1264
|
-
'harmony.tsx',
|
|
1265
|
-
'harmony.js',
|
|
1266
|
-
'harmony.jsx',
|
|
1267
|
-
...((harmonyConfig.resolver?.sourceExts ?? defaultConfig.resolver?.sourceExts ?? ['ts', 'tsx', 'js', 'jsx', 'json'])),
|
|
1268
|
-
],
|
|
1269
|
-
},
|
|
1270
|
-
};
|
|
1271
|
-
`;
|
|
1272
|
-
}
|
|
1273
|
-
function renderUnsupportedCapabilityShim(capability) {
|
|
1274
|
-
return `'use strict';
|
|
1275
|
-
|
|
1276
|
-
const { CodedError } = require('expo-modules-core');
|
|
1277
|
-
|
|
1278
|
-
function createPreviewError() {
|
|
1279
|
-
return new CodedError(
|
|
1280
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
1281
|
-
'${capability.packageName} is classified as ${capability.supportTier} for Harmony, but no managed runtime shim has been wired yet.',
|
|
1282
|
-
);
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
function unavailable() {
|
|
1286
|
-
throw createPreviewError();
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
module.exports = new Proxy(
|
|
1290
|
-
{},
|
|
1291
|
-
{
|
|
1292
|
-
get(_target, propertyName) {
|
|
1293
|
-
if (propertyName === '__esModule') {
|
|
1294
|
-
return true;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
return unavailable;
|
|
1298
|
-
},
|
|
1299
|
-
},
|
|
1300
|
-
);
|
|
1301
|
-
`;
|
|
1302
|
-
}
|
|
1303
|
-
function renderExpoHarmonyPackage() {
|
|
1304
|
-
return `import type {
|
|
1305
|
-
AnyThreadTurboModule,
|
|
1306
|
-
AnyThreadTurboModuleContext,
|
|
1307
|
-
UITurboModule,
|
|
1308
|
-
UITurboModuleContext,
|
|
1309
|
-
} from '@rnoh/react-native-openharmony';
|
|
1310
|
-
import {
|
|
1311
|
-
RNOHPackage,
|
|
1312
|
-
} from '@rnoh/react-native-openharmony';
|
|
1313
|
-
import { ExpoHarmonyFileSystemTurboModule } from './ExpoHarmonyFileSystemTurboModule';
|
|
1314
|
-
import { ExpoHarmonyImagePickerTurboModule } from './ExpoHarmonyImagePickerTurboModule';
|
|
1315
|
-
import { ExpoHarmonyLocationTurboModule } from './ExpoHarmonyLocationTurboModule';
|
|
1316
|
-
import { ExpoHarmonyCameraTurboModule } from './ExpoHarmonyCameraTurboModule';
|
|
1317
|
-
|
|
1318
|
-
export class ExpoHarmonyPackage extends RNOHPackage {
|
|
1319
|
-
override getAnyThreadTurboModuleFactoryByNameMap(): Map<
|
|
1320
|
-
string,
|
|
1321
|
-
(ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null
|
|
1322
|
-
> {
|
|
1323
|
-
return new Map<string, (ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null>()
|
|
1324
|
-
.set(
|
|
1325
|
-
ExpoHarmonyFileSystemTurboModule.NAME,
|
|
1326
|
-
(ctx) => new ExpoHarmonyFileSystemTurboModule(ctx),
|
|
1327
|
-
)
|
|
1328
|
-
.set(
|
|
1329
|
-
ExpoHarmonyLocationTurboModule.NAME,
|
|
1330
|
-
(ctx) => new ExpoHarmonyLocationTurboModule(ctx),
|
|
1331
|
-
);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
override getUITurboModuleFactoryByNameMap(): Map<
|
|
1335
|
-
string,
|
|
1336
|
-
(ctx: UITurboModuleContext) => UITurboModule | null
|
|
1337
|
-
> {
|
|
1338
|
-
return new Map<string, (ctx: UITurboModuleContext) => UITurboModule | null>()
|
|
1339
|
-
.set(
|
|
1340
|
-
ExpoHarmonyImagePickerTurboModule.NAME,
|
|
1341
|
-
(ctx) => new ExpoHarmonyImagePickerTurboModule(ctx),
|
|
1342
|
-
)
|
|
1343
|
-
.set(
|
|
1344
|
-
ExpoHarmonyCameraTurboModule.NAME,
|
|
1345
|
-
(ctx) => new ExpoHarmonyCameraTurboModule(ctx),
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
override getDebugName() {
|
|
1350
|
-
return 'expo-harmony';
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
`;
|
|
1354
|
-
}
|
|
1355
|
-
function renderExpoHarmonyFileSystemTurboModule() {
|
|
1356
|
-
return `import fs from '@ohos.file.fs';
|
|
1357
|
-
import { AnyThreadTurboModuleContext, AnyThreadTurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
1358
|
-
|
|
1359
|
-
type FileInfoOptions = {
|
|
1360
|
-
md5?: boolean;
|
|
1361
|
-
};
|
|
1362
|
-
|
|
1363
|
-
type WriteOptions = {
|
|
1364
|
-
encoding?: string;
|
|
1365
|
-
append?: boolean;
|
|
1366
|
-
};
|
|
1367
|
-
|
|
1368
|
-
type ReadOptions = {
|
|
1369
|
-
encoding?: string;
|
|
1370
|
-
position?: number;
|
|
1371
|
-
length?: number;
|
|
1372
|
-
};
|
|
1373
|
-
|
|
1374
|
-
type MakeDirectoryOptions = {
|
|
1375
|
-
intermediates?: boolean;
|
|
1376
|
-
};
|
|
1377
|
-
|
|
1378
|
-
type DeleteOptions = {
|
|
1379
|
-
idempotent?: boolean;
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
|
-
type FileInfoResult = {
|
|
1383
|
-
exists: boolean;
|
|
1384
|
-
path: string;
|
|
1385
|
-
isDirectory: boolean;
|
|
1386
|
-
size?: number;
|
|
1387
|
-
modificationTime?: number;
|
|
1388
|
-
md5?: string;
|
|
1389
|
-
};
|
|
1390
|
-
|
|
1391
|
-
type DownloadOptions = {
|
|
1392
|
-
headers?: Record<string, string>;
|
|
1393
|
-
md5?: boolean;
|
|
1394
|
-
};
|
|
1395
|
-
|
|
1396
|
-
type DownloadResult = {
|
|
1397
|
-
uri: string;
|
|
1398
|
-
status: number;
|
|
1399
|
-
headers: Record<string, string>;
|
|
1400
|
-
md5?: string;
|
|
1401
|
-
};
|
|
1402
|
-
|
|
1403
|
-
export class ExpoHarmonyFileSystemTurboModule extends AnyThreadTurboModule {
|
|
1404
|
-
public static readonly NAME = 'ExpoHarmonyFileSystem';
|
|
1405
|
-
|
|
1406
|
-
public constructor(ctx: AnyThreadTurboModuleContext) {
|
|
1407
|
-
super(ctx);
|
|
1408
|
-
this.ensureManagedDirectoriesSync();
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
getConstants(): {
|
|
1412
|
-
documentDirectoryPath: string;
|
|
1413
|
-
cacheDirectoryPath: string;
|
|
1414
|
-
bundleDirectoryPath: string | null;
|
|
1415
|
-
} {
|
|
1416
|
-
const abilityContext = this.ctx.uiAbilityContext as {
|
|
1417
|
-
bundleCodeDir?: string;
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
|
-
return {
|
|
1421
|
-
documentDirectoryPath: this.documentDirectoryPath,
|
|
1422
|
-
cacheDirectoryPath: this.cacheDirectoryPath,
|
|
1423
|
-
bundleDirectoryPath:
|
|
1424
|
-
typeof abilityContext.bundleCodeDir === 'string' && abilityContext.bundleCodeDir.length > 0
|
|
1425
|
-
? abilityContext.bundleCodeDir
|
|
1426
|
-
: null,
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
async getInfo(path: string, options?: FileInfoOptions): Promise<FileInfoResult> {
|
|
1431
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1432
|
-
const stat = await this.getStatOrNull(normalizedPath);
|
|
1433
|
-
|
|
1434
|
-
if (!stat) {
|
|
1435
|
-
return {
|
|
1436
|
-
exists: false,
|
|
1437
|
-
path: normalizedPath,
|
|
1438
|
-
isDirectory: false,
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
return {
|
|
1443
|
-
exists: true,
|
|
1444
|
-
path: normalizedPath,
|
|
1445
|
-
isDirectory: stat.isDirectory(),
|
|
1446
|
-
size: Number(stat.size),
|
|
1447
|
-
modificationTime: Number(stat.mtime),
|
|
1448
|
-
md5:
|
|
1449
|
-
options?.md5 === true && !stat.isDirectory()
|
|
1450
|
-
? this.computePreviewDigest(await this.readFileBytes(normalizedPath))
|
|
1451
|
-
: undefined,
|
|
1452
|
-
};
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
async readAsString(path: string, options?: ReadOptions): Promise<string> {
|
|
1456
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1457
|
-
const encoding = options?.encoding ?? 'utf8';
|
|
1458
|
-
const bytes = await this.readFileBytes(normalizedPath);
|
|
1459
|
-
const position = typeof options?.position === 'number' && options.position > 0
|
|
1460
|
-
? Math.floor(options.position)
|
|
1461
|
-
: 0;
|
|
1462
|
-
const length = typeof options?.length === 'number' && options.length >= 0
|
|
1463
|
-
? Math.floor(options.length)
|
|
1464
|
-
: bytes.length - position;
|
|
1465
|
-
const slicedBytes = bytes.slice(position, position + length);
|
|
1466
|
-
|
|
1467
|
-
if (encoding === 'base64') {
|
|
1468
|
-
return this.encodeBase64(slicedBytes);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
return this.decodeUtf8(slicedBytes);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
async writeAsString(path: string, contents: string, options?: WriteOptions): Promise<void> {
|
|
1475
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1476
|
-
const encoding = options?.encoding ?? 'utf8';
|
|
1477
|
-
|
|
1478
|
-
await this.ensureParentDirectory(normalizedPath);
|
|
1479
|
-
|
|
1480
|
-
const file = await fs.open(
|
|
1481
|
-
normalizedPath,
|
|
1482
|
-
fs.OpenMode.READ_WRITE |
|
|
1483
|
-
fs.OpenMode.CREATE |
|
|
1484
|
-
(options?.append === true ? fs.OpenMode.APPEND : fs.OpenMode.TRUNC),
|
|
1485
|
-
);
|
|
1486
|
-
|
|
1487
|
-
try {
|
|
1488
|
-
if (encoding === 'base64') {
|
|
1489
|
-
await fs.write(file.fd, this.decodeBase64(contents).buffer);
|
|
1490
|
-
} else {
|
|
1491
|
-
await fs.write(file.fd, contents);
|
|
1492
|
-
}
|
|
1493
|
-
} finally {
|
|
1494
|
-
await fs.close(file);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
async deletePath(path: string, options?: DeleteOptions): Promise<void> {
|
|
1499
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1500
|
-
await this.deleteInternal(normalizedPath, options?.idempotent === true);
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
async makeDirectory(path: string, options?: MakeDirectoryOptions): Promise<void> {
|
|
1504
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1505
|
-
await fs.mkdir(normalizedPath, options?.intermediates === true);
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
async readDirectory(path: string): Promise<string[]> {
|
|
1509
|
-
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1510
|
-
const stat = await fs.stat(normalizedPath);
|
|
1511
|
-
|
|
1512
|
-
if (!stat.isDirectory()) {
|
|
1513
|
-
throw new Error('readDirectory expects a directory path.');
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
return fs.listFile(normalizedPath);
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
async copy(from: string, to: string): Promise<void> {
|
|
1520
|
-
const fromPath = this.normalizeSandboxPath(from);
|
|
1521
|
-
const toPath = this.normalizeSandboxPath(to);
|
|
1522
|
-
await this.copyInternal(fromPath, toPath);
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
async move(from: string, to: string): Promise<void> {
|
|
1526
|
-
const fromPath = this.normalizeSandboxPath(from);
|
|
1527
|
-
const toPath = this.normalizeSandboxPath(to);
|
|
1528
|
-
const stat = await fs.stat(fromPath);
|
|
1529
|
-
|
|
1530
|
-
await this.ensureParentDirectory(toPath);
|
|
1531
|
-
|
|
1532
|
-
if (stat.isDirectory()) {
|
|
1533
|
-
await this.copyInternal(fromPath, toPath);
|
|
1534
|
-
await this.deleteInternal(fromPath, false);
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
await fs.moveFile(fromPath, toPath);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
async download(url: string, destinationPath: string, options?: DownloadOptions): Promise<DownloadResult> {
|
|
1542
|
-
const normalizedDestinationPath = this.normalizeSandboxPath(destinationPath);
|
|
1543
|
-
await this.ensureParentDirectory(normalizedDestinationPath);
|
|
1544
|
-
|
|
1545
|
-
const response = await fetch(url, {
|
|
1546
|
-
headers: options?.headers ?? {},
|
|
1547
|
-
});
|
|
1548
|
-
const responseBuffer = new Uint8Array(await response.arrayBuffer());
|
|
1549
|
-
const file = await fs.open(
|
|
1550
|
-
normalizedDestinationPath,
|
|
1551
|
-
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC,
|
|
1552
|
-
);
|
|
1553
|
-
|
|
1554
|
-
try {
|
|
1555
|
-
await fs.write(file.fd, responseBuffer.buffer);
|
|
1556
|
-
} finally {
|
|
1557
|
-
await fs.close(file);
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
return {
|
|
1561
|
-
uri: normalizedDestinationPath,
|
|
1562
|
-
status: Number(response.status ?? 200),
|
|
1563
|
-
headers: {},
|
|
1564
|
-
md5: options?.md5 === true ? this.computePreviewDigest(responseBuffer) : undefined,
|
|
1565
|
-
};
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
private get documentDirectoryPath(): string {
|
|
1569
|
-
return \`\${this.ctx.uiAbilityContext.filesDir}/expo-harmony/document\`;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
private get cacheDirectoryPath(): string {
|
|
1573
|
-
return \`\${this.ctx.uiAbilityContext.cacheDir}/expo-harmony/cache\`;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
private ensureManagedDirectoriesSync(): void {
|
|
1577
|
-
this.ensureDirectorySync(this.documentDirectoryPath);
|
|
1578
|
-
this.ensureDirectorySync(this.cacheDirectoryPath);
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
private ensureDirectorySync(directoryPath: string): void {
|
|
1582
|
-
if (!fs.accessSync(directoryPath)) {
|
|
1583
|
-
fs.mkdirSync(directoryPath, true);
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
private async ensureParentDirectory(targetPath: string): Promise<void> {
|
|
1588
|
-
const parentPath = this.getParentPath(targetPath);
|
|
1589
|
-
|
|
1590
|
-
if (!parentPath) {
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
const parentStat = await this.getStatOrNull(parentPath);
|
|
1595
|
-
|
|
1596
|
-
if (parentStat) {
|
|
1597
|
-
if (!parentStat.isDirectory()) {
|
|
1598
|
-
throw new Error(\`Expected parent path to be a directory: \${parentPath}\`);
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
await fs.mkdir(parentPath, true);
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
private getParentPath(targetPath: string): string | null {
|
|
1608
|
-
let normalizedPath = targetPath;
|
|
1609
|
-
|
|
1610
|
-
while (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
1611
|
-
normalizedPath = normalizedPath.slice(0, -1);
|
|
1612
|
-
}
|
|
1613
|
-
const slashIndex = normalizedPath.lastIndexOf('/');
|
|
1614
|
-
|
|
1615
|
-
if (slashIndex <= 0) {
|
|
1616
|
-
return null;
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
return normalizedPath.slice(0, slashIndex);
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
private async deleteInternal(targetPath: string, idempotent: boolean): Promise<void> {
|
|
1623
|
-
const stat = await this.getStatOrNull(targetPath);
|
|
1624
|
-
|
|
1625
|
-
if (!stat) {
|
|
1626
|
-
if (idempotent) {
|
|
1627
|
-
return;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
throw new Error(\`No file or directory exists at \${targetPath}.\`);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
if (stat.isDirectory()) {
|
|
1634
|
-
const entries = await fs.listFile(targetPath);
|
|
1635
|
-
|
|
1636
|
-
for (const entryName of entries) {
|
|
1637
|
-
await this.deleteInternal(\`\${targetPath}/\${entryName}\`, idempotent);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
await fs.rmdir(targetPath);
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
await fs.unlink(targetPath);
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
private async copyInternal(fromPath: string, toPath: string): Promise<void> {
|
|
1648
|
-
const stat = await fs.stat(fromPath);
|
|
1649
|
-
|
|
1650
|
-
await this.ensureParentDirectory(toPath);
|
|
1651
|
-
|
|
1652
|
-
if (stat.isDirectory()) {
|
|
1653
|
-
await fs.mkdir(toPath, true);
|
|
1654
|
-
const entries = await fs.listFile(fromPath);
|
|
1655
|
-
|
|
1656
|
-
for (const entryName of entries) {
|
|
1657
|
-
await this.copyInternal(\`\${fromPath}/\${entryName}\`, \`\${toPath}/\${entryName}\`);
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
await fs.copyFile(fromPath, toPath);
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
private async getStatOrNull(targetPath: string): Promise<fs.Stat | null> {
|
|
1667
|
-
try {
|
|
1668
|
-
return await fs.stat(targetPath);
|
|
1669
|
-
} catch (error) {
|
|
1670
|
-
if (this.isNoSuchFileError(error)) {
|
|
1671
|
-
return null;
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
throw error;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
private isNoSuchFileError(error: unknown): boolean {
|
|
1679
|
-
return (
|
|
1680
|
-
typeof error === 'object' &&
|
|
1681
|
-
error !== null &&
|
|
1682
|
-
'code' in error &&
|
|
1683
|
-
Number((error as { code?: number }).code) === 13900002
|
|
1684
|
-
);
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
private async readFileBytes(targetPath: string): Promise<Uint8Array> {
|
|
1688
|
-
const stat = await fs.stat(targetPath);
|
|
1689
|
-
const file = await fs.open(targetPath, fs.OpenMode.READ_ONLY);
|
|
1690
|
-
const buffer = new ArrayBuffer(Number(stat.size ?? 0));
|
|
1691
|
-
|
|
1692
|
-
try {
|
|
1693
|
-
await fs.read(file.fd, buffer);
|
|
1694
|
-
return new Uint8Array(buffer);
|
|
1695
|
-
} finally {
|
|
1696
|
-
await fs.close(file);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
private decodeUtf8(bytes: Uint8Array): string {
|
|
1701
|
-
return new TextDecoder().decode(bytes);
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
private encodeBase64(bytes: Uint8Array): string {
|
|
1705
|
-
let binary = '';
|
|
1706
|
-
|
|
1707
|
-
for (const byte of bytes) {
|
|
1708
|
-
binary += String.fromCharCode(byte);
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
return btoa(binary);
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
private decodeBase64(contents: string): Uint8Array {
|
|
1715
|
-
const binary = atob(contents);
|
|
1716
|
-
const bytes = new Uint8Array(binary.length);
|
|
1717
|
-
|
|
1718
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
1719
|
-
bytes[index] = binary.charCodeAt(index);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
return bytes;
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
private computePreviewDigest(bytes: Uint8Array): string {
|
|
1726
|
-
let hash = 2166136261;
|
|
1727
|
-
|
|
1728
|
-
for (const byte of bytes) {
|
|
1729
|
-
hash ^= byte;
|
|
1730
|
-
hash = Math.imul(hash, 16777619);
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
return String(hash >>> 0).padStart(32, '0').slice(-32);
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
private normalizeSandboxPath(inputPath: string): string {
|
|
1737
|
-
if (typeof inputPath !== 'string' || inputPath.length === 0) {
|
|
1738
|
-
throw new Error('ExpoHarmonyFileSystem expected a non-empty sandbox path.');
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
if (!inputPath.startsWith('/')) {
|
|
1742
|
-
throw new Error('ExpoHarmonyFileSystem accepts only absolute sandbox paths.');
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
if (inputPath.includes('/../') || inputPath.endsWith('/..') || inputPath.includes('/./')) {
|
|
1746
|
-
throw new Error('ExpoHarmonyFileSystem does not accept relative path segments.');
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
const allowedRoots = [
|
|
1750
|
-
this.ctx.uiAbilityContext.filesDir,
|
|
1751
|
-
this.ctx.uiAbilityContext.cacheDir,
|
|
1752
|
-
].filter((value): value is string => typeof value === 'string' && value.length > 0);
|
|
1753
|
-
|
|
1754
|
-
const isAllowed = allowedRoots.some(
|
|
1755
|
-
(rootPath) => inputPath === rootPath || inputPath.startsWith(\`\${rootPath}/\`),
|
|
1756
|
-
);
|
|
1757
|
-
|
|
1758
|
-
if (!isAllowed) {
|
|
1759
|
-
throw new Error('ExpoHarmonyFileSystem accepts only app sandbox paths.');
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
return inputPath;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
`;
|
|
1766
|
-
}
|
|
1767
|
-
function renderExpoHarmonyImagePickerTurboModule() {
|
|
1768
|
-
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
1769
|
-
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
1770
|
-
import photoAccessHelper from '@ohos.file.photoAccessHelper';
|
|
1771
|
-
import picker from '@ohos.file.picker';
|
|
1772
|
-
import image from '@ohos.multimedia.image';
|
|
1773
|
-
import fs from '@ohos.file.fs';
|
|
1774
|
-
import { UITurboModuleContext, UITurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
1775
|
-
|
|
1776
|
-
type PermissionResponse = {
|
|
1777
|
-
status: 'granted' | 'denied' | 'undetermined';
|
|
1778
|
-
granted: boolean;
|
|
1779
|
-
canAskAgain: boolean;
|
|
1780
|
-
expires: 'never';
|
|
1781
|
-
accessPrivileges?: 'all' | 'limited' | 'none';
|
|
1782
|
-
};
|
|
1783
|
-
|
|
1784
|
-
type LaunchImageLibraryOptions = {
|
|
1785
|
-
mediaTypes?: string | string[];
|
|
1786
|
-
allowsMultipleSelection?: boolean;
|
|
1787
|
-
selectionLimit?: number;
|
|
1788
|
-
};
|
|
1789
|
-
|
|
1790
|
-
type LaunchCameraOptions = {
|
|
1791
|
-
mediaTypes?: string | string[];
|
|
1792
|
-
cameraType?: string;
|
|
1793
|
-
};
|
|
1794
|
-
|
|
1795
|
-
type ImagePickerAsset = {
|
|
1796
|
-
uri: string;
|
|
1797
|
-
assetId: string | null;
|
|
1798
|
-
width: number;
|
|
1799
|
-
height: number;
|
|
1800
|
-
type: 'image' | 'video' | null;
|
|
1801
|
-
fileName: string | null;
|
|
1802
|
-
fileSize: number | null;
|
|
1803
|
-
mimeType: string | null;
|
|
1804
|
-
duration: number | null;
|
|
1805
|
-
exif: null;
|
|
1806
|
-
base64: null;
|
|
1807
|
-
};
|
|
1808
|
-
|
|
1809
|
-
type ImagePickerResult = {
|
|
1810
|
-
canceled: boolean;
|
|
1811
|
-
assets: ImagePickerAsset[] | null;
|
|
1812
|
-
};
|
|
1813
|
-
|
|
1814
|
-
export class ExpoHarmonyImagePickerTurboModule extends UITurboModule {
|
|
1815
|
-
public static readonly NAME = 'ExpoHarmonyImagePicker';
|
|
1816
|
-
|
|
1817
|
-
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
1818
|
-
private pendingResult: ImagePickerResult | null = null;
|
|
1819
|
-
|
|
1820
|
-
getConstants(): Record<string, never> {
|
|
1821
|
-
return {};
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
async getMediaLibraryPermissionStatus(_writeOnly?: boolean): Promise<PermissionResponse> {
|
|
1825
|
-
return this.getPermissionResponse('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
async requestMediaLibraryPermission(_writeOnly?: boolean): Promise<PermissionResponse> {
|
|
1829
|
-
return this.requestPermissionResponse('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
async getCameraPermissionStatus(): Promise<PermissionResponse> {
|
|
1833
|
-
return this.getPermissionResponse('ohos.permission.CAMERA', false);
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
async requestCameraPermission(): Promise<PermissionResponse> {
|
|
1837
|
-
return this.requestPermissionResponse('ohos.permission.CAMERA', false);
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
async launchImageLibrary(options?: LaunchImageLibraryOptions): Promise<ImagePickerResult> {
|
|
1841
|
-
await this.ensurePermissionGranted('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1842
|
-
|
|
1843
|
-
const photoPicker = new photoAccessHelper.PhotoViewPicker();
|
|
1844
|
-
const selection = await photoPicker.select(this.createPhotoSelectOptions(options));
|
|
1845
|
-
let selectedUris = this.normalizeSelectedUris(selection?.photoUris);
|
|
1846
|
-
|
|
1847
|
-
if (selectedUris.length === 0) {
|
|
1848
|
-
selectedUris = await this.launchLegacyPhotoPicker(options);
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
if (selectedUris.length === 0) {
|
|
1852
|
-
return this.createCanceledResult();
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
const authorizedUris = await this.requestAuthorizedUris(selectedUris);
|
|
1856
|
-
const assets = await Promise.all(
|
|
1857
|
-
authorizedUris.map((uri, index) =>
|
|
1858
|
-
this.createImagePickerAsset(uri, selectedUris[index] ?? uri, this.inferAssetTypeFromMediaTypes(options?.mediaTypes)),
|
|
1859
|
-
),
|
|
1860
|
-
);
|
|
1861
|
-
|
|
1862
|
-
const result = {
|
|
1863
|
-
canceled: false,
|
|
1864
|
-
assets,
|
|
1865
|
-
};
|
|
1866
|
-
this.pendingResult = result;
|
|
1867
|
-
return result;
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
async launchCamera(options?: LaunchCameraOptions): Promise<ImagePickerResult> {
|
|
1871
|
-
const requestedAssetType = this.inferAssetTypeFromMediaTypes(options?.mediaTypes);
|
|
1872
|
-
|
|
1873
|
-
await this.ensurePermissionGranted('ohos.permission.CAMERA', false);
|
|
1874
|
-
await this.ensurePermissionGranted('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1875
|
-
if (requestedAssetType === 'video') {
|
|
1876
|
-
await this.ensurePermissionGranted('ohos.permission.MICROPHONE', false);
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
const cameraEntryPicker = new photoAccessHelper.PhotoViewPicker();
|
|
1880
|
-
const selection = await cameraEntryPicker.select(
|
|
1881
|
-
this.createPhotoSelectOptions(
|
|
1882
|
-
{
|
|
1883
|
-
mediaTypes: requestedAssetType === 'video' ? 'videos' : 'images',
|
|
1884
|
-
allowsMultipleSelection: false,
|
|
1885
|
-
},
|
|
1886
|
-
true,
|
|
1887
|
-
),
|
|
1888
|
-
);
|
|
1889
|
-
const selectedUris = this.normalizeSelectedUris(selection?.photoUris);
|
|
1890
|
-
|
|
1891
|
-
if (selectedUris.length === 0) {
|
|
1892
|
-
return this.createCanceledResult();
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
const authorizedUris = await this.requestAuthorizedUris(selectedUris);
|
|
1896
|
-
const assetUri = authorizedUris[0] ?? selectedUris[0];
|
|
1897
|
-
|
|
1898
|
-
const result = {
|
|
1899
|
-
canceled: false,
|
|
1900
|
-
assets: [await this.createImagePickerAsset(assetUri, selectedUris[0], requestedAssetType ?? 'image')],
|
|
1901
|
-
};
|
|
1902
|
-
this.pendingResult = result;
|
|
1903
|
-
return result;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
async getPendingResult(): Promise<ImagePickerResult | null> {
|
|
1907
|
-
const result = this.pendingResult;
|
|
1908
|
-
this.pendingResult = null;
|
|
1909
|
-
return result;
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
private async ensurePermissionGranted(
|
|
1913
|
-
permissionName: Permissions,
|
|
1914
|
-
isMediaLibraryPermission: boolean,
|
|
1915
|
-
): Promise<void> {
|
|
1916
|
-
const permissionResponse = await this.getPermissionResponse(permissionName, isMediaLibraryPermission);
|
|
1917
|
-
|
|
1918
|
-
if (permissionResponse.granted) {
|
|
1919
|
-
return;
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
const requestedResponse = await this.requestPermissionResponse(permissionName, isMediaLibraryPermission);
|
|
1923
|
-
|
|
1924
|
-
if (!requestedResponse.granted) {
|
|
1925
|
-
throw new Error(\`Permission denied for \${permissionName}.\`);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
private async getPermissionResponse(
|
|
1930
|
-
permissionName: Permissions,
|
|
1931
|
-
isMediaLibraryPermission: boolean,
|
|
1932
|
-
): Promise<PermissionResponse> {
|
|
1933
|
-
return this.permissionResponseFromStatus(
|
|
1934
|
-
this.resolvePermissionStatus(permissionName),
|
|
1935
|
-
isMediaLibraryPermission,
|
|
1936
|
-
);
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
private async requestPermissionResponse(
|
|
1940
|
-
permissionName: Permissions,
|
|
1941
|
-
isMediaLibraryPermission: boolean,
|
|
1942
|
-
): Promise<PermissionResponse> {
|
|
1943
|
-
const requestResult = await this.atManager.requestPermissionsFromUser(
|
|
1944
|
-
this.ctx.uiAbilityContext,
|
|
1945
|
-
[permissionName],
|
|
1946
|
-
);
|
|
1947
|
-
const authResult = Array.isArray(requestResult.authResults)
|
|
1948
|
-
? Number(requestResult.authResults[0] ?? abilityAccessCtrl.GrantStatus.PERMISSION_DENIED)
|
|
1949
|
-
: abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
|
|
1950
|
-
|
|
1951
|
-
if (authResult === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
|
|
1952
|
-
return this.permissionResponseFromStatus(
|
|
1953
|
-
abilityAccessCtrl.PermissionStatus.GRANTED,
|
|
1954
|
-
isMediaLibraryPermission,
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
return this.permissionResponseFromStatus(
|
|
1959
|
-
abilityAccessCtrl.PermissionStatus.DENIED,
|
|
1960
|
-
isMediaLibraryPermission,
|
|
1961
|
-
);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
private resolvePermissionStatus(permissionName: Permissions): abilityAccessCtrl.PermissionStatus {
|
|
1965
|
-
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
1966
|
-
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
1967
|
-
};
|
|
1968
|
-
|
|
1969
|
-
if (typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function') {
|
|
1970
|
-
return atManagerWithSelfStatus.getSelfPermissionStatus(permissionName);
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
const accessTokenId = this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId;
|
|
1974
|
-
const grantStatus = this.atManager.checkAccessTokenSync(accessTokenId, permissionName);
|
|
1975
|
-
|
|
1976
|
-
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
1977
|
-
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
1978
|
-
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
private permissionResponseFromStatus(
|
|
1982
|
-
permissionStatus: abilityAccessCtrl.PermissionStatus,
|
|
1983
|
-
isMediaLibraryPermission: boolean,
|
|
1984
|
-
): PermissionResponse {
|
|
1985
|
-
const granted = permissionStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
1986
|
-
const denied =
|
|
1987
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
1988
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
1989
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
1990
|
-
|
|
1991
|
-
return {
|
|
1992
|
-
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
1993
|
-
granted,
|
|
1994
|
-
canAskAgain: !denied,
|
|
1995
|
-
expires: 'never',
|
|
1996
|
-
...(isMediaLibraryPermission
|
|
1997
|
-
? {
|
|
1998
|
-
accessPrivileges: granted ? 'all' : 'none',
|
|
1999
|
-
}
|
|
2000
|
-
: {}),
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
private createPhotoSelectOptions(
|
|
2005
|
-
options?: LaunchImageLibraryOptions,
|
|
2006
|
-
isPhotoTakingSupported: boolean = false,
|
|
2007
|
-
): photoAccessHelper.PhotoSelectOptions {
|
|
2008
|
-
const selectOptions = new photoAccessHelper.PhotoSelectOptions();
|
|
2009
|
-
selectOptions.MIMEType = this.resolvePhotoViewMimeType(options?.mediaTypes);
|
|
2010
|
-
selectOptions.maxSelectNumber =
|
|
2011
|
-
options?.allowsMultipleSelection === true
|
|
2012
|
-
? this.resolveSelectionLimit(options?.selectionLimit)
|
|
2013
|
-
: 1;
|
|
2014
|
-
selectOptions.isSearchSupported = true;
|
|
2015
|
-
selectOptions.isPhotoTakingSupported = isPhotoTakingSupported;
|
|
2016
|
-
return selectOptions;
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
private async launchLegacyPhotoPicker(options?: LaunchImageLibraryOptions): Promise<string[]> {
|
|
2020
|
-
const legacyPicker = new picker.PhotoViewPicker(this.ctx.uiAbilityContext);
|
|
2021
|
-
const legacyOptions = new picker.PhotoSelectOptions();
|
|
2022
|
-
legacyOptions.MIMEType = this.resolveLegacyPhotoViewMimeType(options?.mediaTypes);
|
|
2023
|
-
legacyOptions.maxSelectNumber =
|
|
2024
|
-
options?.allowsMultipleSelection === true
|
|
2025
|
-
? this.resolveSelectionLimit(options?.selectionLimit)
|
|
2026
|
-
: 1;
|
|
2027
|
-
|
|
2028
|
-
const selection = await legacyPicker.select(legacyOptions);
|
|
2029
|
-
return this.normalizeSelectedUris(selection?.photoUris);
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
private resolveSelectionLimit(selectionLimit?: number): number {
|
|
2033
|
-
if (typeof selectionLimit === 'number' && Number.isFinite(selectionLimit) && selectionLimit > 0) {
|
|
2034
|
-
return Math.floor(selectionLimit);
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
return 20;
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
private resolvePhotoViewMimeType(
|
|
2041
|
-
rawMediaTypes?: string | string[],
|
|
2042
|
-
): photoAccessHelper.PhotoViewMIMETypes {
|
|
2043
|
-
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2044
|
-
|
|
2045
|
-
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2046
|
-
return photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
if (normalized.includes('video') && normalized.includes('image')) {
|
|
2050
|
-
return photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
return photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
private resolveLegacyPhotoViewMimeType(
|
|
2057
|
-
rawMediaTypes?: string | string[],
|
|
2058
|
-
): picker.PhotoViewMIMETypes {
|
|
2059
|
-
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2060
|
-
|
|
2061
|
-
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2062
|
-
return picker.PhotoViewMIMETypes.VIDEO_TYPE;
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
if (normalized.includes('video') && normalized.includes('image')) {
|
|
2066
|
-
return picker.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
return picker.PhotoViewMIMETypes.IMAGE_TYPE;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
private inferAssetTypeFromMediaTypes(
|
|
2073
|
-
rawMediaTypes?: string | string[],
|
|
2074
|
-
): 'image' | 'video' | null {
|
|
2075
|
-
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2076
|
-
|
|
2077
|
-
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2078
|
-
return 'video';
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
if (normalized.includes('image')) {
|
|
2082
|
-
return 'image';
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
return null;
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
private normalizeMediaTypes(rawMediaTypes?: string | string[]): string[] {
|
|
2089
|
-
if (Array.isArray(rawMediaTypes)) {
|
|
2090
|
-
return Array.from(
|
|
2091
|
-
new Set(
|
|
2092
|
-
rawMediaTypes
|
|
2093
|
-
.map((value) => this.normalizeMediaTypeValue(value))
|
|
2094
|
-
.filter((value): value is string => value !== null),
|
|
2095
|
-
),
|
|
2096
|
-
);
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
const singleValue = this.normalizeMediaTypeValue(rawMediaTypes);
|
|
2100
|
-
return singleValue ? [singleValue] : ['image'];
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
private normalizeMediaTypeValue(rawValue?: string): string | null {
|
|
2104
|
-
if (typeof rawValue !== 'string' || rawValue.length === 0) {
|
|
2105
|
-
return 'image';
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
switch (rawValue) {
|
|
2109
|
-
case 'All':
|
|
2110
|
-
case 'all':
|
|
2111
|
-
case 'images':
|
|
2112
|
-
case 'image':
|
|
2113
|
-
case 'livePhotos':
|
|
2114
|
-
return 'image';
|
|
2115
|
-
case 'Videos':
|
|
2116
|
-
case 'videos':
|
|
2117
|
-
case 'video':
|
|
2118
|
-
return 'video';
|
|
2119
|
-
default:
|
|
2120
|
-
return rawValue.includes('video') ? 'video' : rawValue.includes('image') ? 'image' : null;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
private normalizeSelectedUris(photoUris: Array<string> | undefined | null): string[] {
|
|
2125
|
-
if (!Array.isArray(photoUris)) {
|
|
2126
|
-
return [];
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
return photoUris.filter(
|
|
2130
|
-
(value): value is string => typeof value === 'string' && value.length > 0,
|
|
2131
|
-
);
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
private async requestAuthorizedUris(photoUris: string[]): Promise<string[]> {
|
|
2135
|
-
const helper = photoAccessHelper.getPhotoAccessHelper(this.ctx.uiAbilityContext);
|
|
2136
|
-
|
|
2137
|
-
try {
|
|
2138
|
-
const authorizedUris = await helper.requestPhotoUrisReadPermission(photoUris);
|
|
2139
|
-
const normalizedAuthorizedUris = this.normalizeSelectedUris(authorizedUris);
|
|
2140
|
-
return normalizedAuthorizedUris.length > 0 ? normalizedAuthorizedUris : photoUris;
|
|
2141
|
-
} catch (_error) {
|
|
2142
|
-
return photoUris;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
private async createImagePickerAsset(
|
|
2147
|
-
assetUri: string,
|
|
2148
|
-
originalUri: string,
|
|
2149
|
-
fallbackType: 'image' | 'video' | null,
|
|
2150
|
-
): Promise<ImagePickerAsset> {
|
|
2151
|
-
const inferredType = this.inferAssetTypeFromUri(assetUri, fallbackType);
|
|
2152
|
-
const imageSize =
|
|
2153
|
-
inferredType === 'image' ? await this.getImageSize(assetUri) : { width: 0, height: 0 };
|
|
2154
|
-
const fileSize = await this.getFileSize(assetUri);
|
|
2155
|
-
const fileName = this.extractFileName(assetUri) ?? this.extractFileName(originalUri);
|
|
2156
|
-
|
|
2157
|
-
return {
|
|
2158
|
-
uri: assetUri,
|
|
2159
|
-
assetId: originalUri,
|
|
2160
|
-
width: imageSize.width,
|
|
2161
|
-
height: imageSize.height,
|
|
2162
|
-
type: inferredType,
|
|
2163
|
-
fileName,
|
|
2164
|
-
fileSize,
|
|
2165
|
-
mimeType: this.inferMimeType(assetUri, inferredType),
|
|
2166
|
-
duration: inferredType === 'video' ? await this.getVideoDuration(assetUri) : null,
|
|
2167
|
-
exif: null,
|
|
2168
|
-
base64: null,
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
private inferAssetTypeFromUri(
|
|
2173
|
-
assetUri: string,
|
|
2174
|
-
fallbackType: 'image' | 'video' | null,
|
|
2175
|
-
): 'image' | 'video' | null {
|
|
2176
|
-
const normalizedUri = assetUri.toLowerCase();
|
|
2177
|
-
|
|
2178
|
-
if (normalizedUri.match(/\\.(mp4|m4v|mov|3gp|webm)(\\?|#|$)/)) {
|
|
2179
|
-
return 'video';
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
if (normalizedUri.match(/\\.(png|jpe?g|gif|bmp|webp|heic|heif)(\\?|#|$)/)) {
|
|
2183
|
-
return 'image';
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
return fallbackType;
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
private inferMimeType(
|
|
2190
|
-
assetUri: string,
|
|
2191
|
-
assetType: 'image' | 'video' | null,
|
|
2192
|
-
): string | null {
|
|
2193
|
-
const normalizedUri = assetUri.toLowerCase();
|
|
2194
|
-
|
|
2195
|
-
if (normalizedUri.endsWith('.png')) {
|
|
2196
|
-
return 'image/png';
|
|
2197
|
-
}
|
|
2198
|
-
if (normalizedUri.endsWith('.gif')) {
|
|
2199
|
-
return 'image/gif';
|
|
2200
|
-
}
|
|
2201
|
-
if (normalizedUri.endsWith('.webp')) {
|
|
2202
|
-
return 'image/webp';
|
|
2203
|
-
}
|
|
2204
|
-
if (normalizedUri.endsWith('.bmp')) {
|
|
2205
|
-
return 'image/bmp';
|
|
2206
|
-
}
|
|
2207
|
-
if (normalizedUri.endsWith('.heic')) {
|
|
2208
|
-
return 'image/heic';
|
|
2209
|
-
}
|
|
2210
|
-
if (normalizedUri.endsWith('.heif')) {
|
|
2211
|
-
return 'image/heif';
|
|
2212
|
-
}
|
|
2213
|
-
if (normalizedUri.match(/\\.jpe?g(\\?|#|$)/)) {
|
|
2214
|
-
return 'image/jpeg';
|
|
2215
|
-
}
|
|
2216
|
-
if (normalizedUri.match(/\\.(mp4|m4v)(\\?|#|$)/)) {
|
|
2217
|
-
return 'video/mp4';
|
|
2218
|
-
}
|
|
2219
|
-
if (normalizedUri.match(/\\.mov(\\?|#|$)/)) {
|
|
2220
|
-
return 'video/quicktime';
|
|
2221
|
-
}
|
|
2222
|
-
if (normalizedUri.match(/\\.webm(\\?|#|$)/)) {
|
|
2223
|
-
return 'video/webm';
|
|
2224
|
-
}
|
|
2225
|
-
|
|
2226
|
-
return assetType === 'video' ? 'video/*' : assetType === 'image' ? 'image/*' : null;
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
private extractFileName(assetUri: string): string | null {
|
|
2230
|
-
if (typeof assetUri !== 'string' || assetUri.length === 0) {
|
|
2231
|
-
return null;
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
const sanitizedUri = assetUri.split('?')[0]?.split('#')[0] ?? assetUri;
|
|
2235
|
-
const lastSlashIndex = sanitizedUri.lastIndexOf('/');
|
|
2236
|
-
const rawFileName =
|
|
2237
|
-
lastSlashIndex >= 0 ? sanitizedUri.slice(lastSlashIndex + 1) : sanitizedUri;
|
|
2238
|
-
|
|
2239
|
-
if (rawFileName.length === 0) {
|
|
2240
|
-
return null;
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
try {
|
|
2244
|
-
return decodeURIComponent(rawFileName);
|
|
2245
|
-
} catch (_error) {
|
|
2246
|
-
return rawFileName;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
private async getImageSize(assetUri: string): Promise<{ width: number; height: number }> {
|
|
2251
|
-
let imageSource: image.ImageSource | null = null;
|
|
2252
|
-
|
|
2253
|
-
try {
|
|
2254
|
-
imageSource = image.createImageSource(assetUri);
|
|
2255
|
-
const imageInfo = await imageSource.getImageInfo();
|
|
2256
|
-
return {
|
|
2257
|
-
width: Number(imageInfo.size?.width ?? 0),
|
|
2258
|
-
height: Number(imageInfo.size?.height ?? 0),
|
|
2259
|
-
};
|
|
2260
|
-
} catch (_error) {
|
|
2261
|
-
return {
|
|
2262
|
-
width: 0,
|
|
2263
|
-
height: 0,
|
|
2264
|
-
};
|
|
2265
|
-
} finally {
|
|
2266
|
-
if (imageSource) {
|
|
2267
|
-
try {
|
|
2268
|
-
await imageSource.release();
|
|
2269
|
-
} catch (_error) {
|
|
2270
|
-
// Ignore cleanup errors from ImageSource release.
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
private async getFileSize(assetUri: string): Promise<number | null> {
|
|
2277
|
-
const fsTarget = this.resolveFsTarget(assetUri);
|
|
2278
|
-
|
|
2279
|
-
if (!fsTarget) {
|
|
2280
|
-
return null;
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
try {
|
|
2284
|
-
const stat = await fs.stat(fsTarget);
|
|
2285
|
-
return Number(stat.size ?? 0);
|
|
2286
|
-
} catch (_error) {
|
|
2287
|
-
return null;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
private async getVideoDuration(_assetUri: string): Promise<number | null> {
|
|
2292
|
-
return 0;
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
private resolveFsTarget(assetUri: string): string | null {
|
|
2296
|
-
if (assetUri.startsWith('file://')) {
|
|
2297
|
-
return assetUri.slice('file://'.length);
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
return assetUri.startsWith('/') ? assetUri : null;
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
private createCanceledResult(): ImagePickerResult {
|
|
2304
|
-
return {
|
|
2305
|
-
canceled: true,
|
|
2306
|
-
assets: null,
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
`;
|
|
2311
|
-
}
|
|
2312
|
-
function renderExpoHarmonyLocationTurboModule() {
|
|
2313
|
-
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
2314
|
-
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
2315
|
-
import geoLocationManager from '@ohos.geoLocationManager';
|
|
2316
|
-
import { sensor } from '@kit.SensorServiceKit';
|
|
2317
|
-
import { AnyThreadTurboModuleContext, AnyThreadTurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
2318
|
-
|
|
2319
|
-
type PermissionResponse = {
|
|
2320
|
-
status: 'granted' | 'denied' | 'undetermined';
|
|
2321
|
-
granted: boolean;
|
|
2322
|
-
canAskAgain: boolean;
|
|
2323
|
-
expires: 'never';
|
|
2324
|
-
android: {
|
|
2325
|
-
accuracy: 'fine' | 'coarse' | 'none';
|
|
2326
|
-
};
|
|
2327
|
-
};
|
|
2328
|
-
|
|
2329
|
-
type ExpoLocationObject = {
|
|
2330
|
-
coords: {
|
|
2331
|
-
latitude: number;
|
|
2332
|
-
longitude: number;
|
|
2333
|
-
altitude: number | null;
|
|
2334
|
-
accuracy: number | null;
|
|
2335
|
-
altitudeAccuracy: number | null;
|
|
2336
|
-
heading: number | null;
|
|
2337
|
-
speed: number | null;
|
|
2338
|
-
};
|
|
2339
|
-
timestamp: number;
|
|
2340
|
-
mocked: false;
|
|
2341
|
-
};
|
|
2342
|
-
|
|
2343
|
-
type ProviderStatus = {
|
|
2344
|
-
locationServicesEnabled: boolean;
|
|
2345
|
-
backgroundModeEnabled: boolean;
|
|
2346
|
-
gpsAvailable: boolean;
|
|
2347
|
-
networkAvailable: boolean;
|
|
2348
|
-
passiveAvailable: boolean;
|
|
2349
|
-
};
|
|
2350
|
-
|
|
2351
|
-
type ReverseGeocodeResult = {
|
|
2352
|
-
city: string | null;
|
|
2353
|
-
district: string | null;
|
|
2354
|
-
streetNumber: string | null;
|
|
2355
|
-
street: string | null;
|
|
2356
|
-
region: string | null;
|
|
2357
|
-
subregion: string | null;
|
|
2358
|
-
country: string | null;
|
|
2359
|
-
postalCode: string | null;
|
|
2360
|
-
name: string | null;
|
|
2361
|
-
isoCountryCode: string | null;
|
|
2362
|
-
timezone: null;
|
|
2363
|
-
formattedAddress: string | null;
|
|
2364
|
-
};
|
|
2365
|
-
|
|
2366
|
-
type GeocodeResult = {
|
|
2367
|
-
latitude: number;
|
|
2368
|
-
longitude: number;
|
|
2369
|
-
altitude?: number;
|
|
2370
|
-
accuracy?: number;
|
|
2371
|
-
};
|
|
2372
|
-
|
|
2373
|
-
type HeadingObject = {
|
|
2374
|
-
magHeading: number;
|
|
2375
|
-
trueHeading: number | null;
|
|
2376
|
-
accuracy: number;
|
|
2377
|
-
};
|
|
2378
|
-
|
|
2379
|
-
export class ExpoHarmonyLocationTurboModule extends AnyThreadTurboModule {
|
|
2380
|
-
public static readonly NAME = 'ExpoHarmonyLocation';
|
|
2381
|
-
|
|
2382
|
-
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
2383
|
-
private nextWatchId = 1;
|
|
2384
|
-
|
|
2385
|
-
getConstants(): Record<string, never> {
|
|
2386
|
-
return {};
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
async getForegroundPermissionStatus(): Promise<PermissionResponse> {
|
|
2390
|
-
return this.getLocationPermissionResponse();
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
async requestForegroundPermission(): Promise<PermissionResponse> {
|
|
2394
|
-
return this.requestLocationPermissionResponse();
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
async getBackgroundPermissionStatus(): Promise<PermissionResponse> {
|
|
2398
|
-
const backgroundStatus = this.resolvePermissionStatus('ohos.permission.LOCATION_IN_BACKGROUND');
|
|
2399
|
-
return this.buildBackgroundPermissionResponse(backgroundStatus);
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
async requestBackgroundPermission(): Promise<PermissionResponse> {
|
|
2403
|
-
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [
|
|
2404
|
-
'ohos.permission.LOCATION_IN_BACKGROUND',
|
|
2405
|
-
]);
|
|
2406
|
-
return this.getBackgroundPermissionStatus();
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
async hasServicesEnabled(): Promise<boolean> {
|
|
2410
|
-
return geoLocationManager.isLocationEnabled();
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
async getProviderStatus(): Promise<ProviderStatus> {
|
|
2414
|
-
const locationServicesEnabled = geoLocationManager.isLocationEnabled();
|
|
2415
|
-
|
|
2416
|
-
return {
|
|
2417
|
-
locationServicesEnabled,
|
|
2418
|
-
backgroundModeEnabled:
|
|
2419
|
-
this.resolvePermissionStatus('ohos.permission.LOCATION_IN_BACKGROUND') ===
|
|
2420
|
-
abilityAccessCtrl.PermissionStatus.GRANTED,
|
|
2421
|
-
gpsAvailable: locationServicesEnabled,
|
|
2422
|
-
networkAvailable: locationServicesEnabled,
|
|
2423
|
-
passiveAvailable: locationServicesEnabled,
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
async getCurrentPosition(options?: { accuracy?: number }): Promise<ExpoLocationObject> {
|
|
2428
|
-
const location = await geoLocationManager.getCurrentLocation(
|
|
2429
|
-
this.createCurrentLocationRequest(options?.accuracy),
|
|
2430
|
-
);
|
|
2431
|
-
return this.normalizeLocation(location);
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
async getLastKnownPosition(_options?: Record<string, unknown>): Promise<ExpoLocationObject | null> {
|
|
2435
|
-
try {
|
|
2436
|
-
const location = geoLocationManager.getLastLocation();
|
|
2437
|
-
return this.hasCoordinates(location) ? this.normalizeLocation(location) : null;
|
|
2438
|
-
} catch (_error) {
|
|
2439
|
-
return null;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
|
|
2443
|
-
async geocode(address: string): Promise<GeocodeResult[]> {
|
|
2444
|
-
const addresses = await geoLocationManager.getAddressesFromLocationName({
|
|
2445
|
-
description: address,
|
|
2446
|
-
locale: 'en-US',
|
|
2447
|
-
maxItems: 5,
|
|
2448
|
-
});
|
|
2449
|
-
|
|
2450
|
-
return addresses.map((addressEntry) => ({
|
|
2451
|
-
latitude: Number(addressEntry.latitude ?? 0),
|
|
2452
|
-
longitude: Number(addressEntry.longitude ?? 0),
|
|
2453
|
-
}));
|
|
2454
|
-
}
|
|
2455
|
-
|
|
2456
|
-
async reverseGeocode(location: { latitude: number; longitude: number }): Promise<ReverseGeocodeResult[]> {
|
|
2457
|
-
const addresses = await geoLocationManager.getAddressesFromLocation({
|
|
2458
|
-
latitude: Number(location.latitude),
|
|
2459
|
-
longitude: Number(location.longitude),
|
|
2460
|
-
locale: 'en-US',
|
|
2461
|
-
maxItems: 5,
|
|
2462
|
-
});
|
|
2463
|
-
|
|
2464
|
-
return addresses.map((addressEntry) => ({
|
|
2465
|
-
city: addressEntry.locality ?? null,
|
|
2466
|
-
district: addressEntry.subLocality ?? null,
|
|
2467
|
-
streetNumber: addressEntry.subThoroughfare ?? null,
|
|
2468
|
-
street: addressEntry.thoroughfare ?? null,
|
|
2469
|
-
region: addressEntry.administrativeArea ?? null,
|
|
2470
|
-
subregion: addressEntry.subAdministrativeArea ?? null,
|
|
2471
|
-
country: addressEntry.countryName ?? null,
|
|
2472
|
-
postalCode: addressEntry.postalCode ?? null,
|
|
2473
|
-
name: addressEntry.placeName ?? null,
|
|
2474
|
-
isoCountryCode: addressEntry.countryCode ?? null,
|
|
2475
|
-
timezone: null,
|
|
2476
|
-
formattedAddress:
|
|
2477
|
-
addressEntry.addressUrl ??
|
|
2478
|
-
(Array.isArray(addressEntry.descriptions) && addressEntry.descriptions.length > 0
|
|
2479
|
-
? addressEntry.descriptions.join(', ')
|
|
2480
|
-
: null),
|
|
2481
|
-
}));
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
async startWatchPosition(
|
|
2485
|
-
options?: { accuracy?: number },
|
|
2486
|
-
_listenerConfig?: Record<string, unknown>,
|
|
2487
|
-
): Promise<{ watchId: number; initialLocation: ExpoLocationObject | null }> {
|
|
2488
|
-
const watchId = this.nextWatchId++;
|
|
2489
|
-
return {
|
|
2490
|
-
watchId,
|
|
2491
|
-
initialLocation: await this.getLastKnownPosition(options ?? {}),
|
|
2492
|
-
};
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
async stopWatchPosition(_watchId: number): Promise<void> {
|
|
2496
|
-
return;
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
async getHeading(): Promise<HeadingObject> {
|
|
2500
|
-
return this.readHeadingSnapshot();
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
async startWatchHeading(
|
|
2504
|
-
_listenerConfig?: Record<string, unknown>,
|
|
2505
|
-
): Promise<{ watchId: number; initialHeading: HeadingObject }> {
|
|
2506
|
-
const watchId = this.nextWatchId++;
|
|
2507
|
-
return {
|
|
2508
|
-
watchId,
|
|
2509
|
-
initialHeading: await this.getHeading(),
|
|
2510
|
-
};
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
async stopWatchHeading(_watchId: number): Promise<void> {
|
|
2514
|
-
return;
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
private async getLocationPermissionResponse(): Promise<PermissionResponse> {
|
|
2518
|
-
const approximateStatus = this.resolvePermissionStatus('ohos.permission.APPROXIMATELY_LOCATION');
|
|
2519
|
-
const preciseStatus = this.resolvePermissionStatus('ohos.permission.LOCATION');
|
|
2520
|
-
|
|
2521
|
-
return this.buildPermissionResponse(approximateStatus, preciseStatus);
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
private buildBackgroundPermissionResponse(
|
|
2525
|
-
backgroundStatus: abilityAccessCtrl.PermissionStatus,
|
|
2526
|
-
): PermissionResponse {
|
|
2527
|
-
const foreground = this.getLocationPermissionResponse();
|
|
2528
|
-
const granted = backgroundStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2529
|
-
const denied =
|
|
2530
|
-
backgroundStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2531
|
-
backgroundStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2532
|
-
backgroundStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
2533
|
-
|
|
2534
|
-
return foreground.then((foregroundPermission) => ({
|
|
2535
|
-
...foregroundPermission,
|
|
2536
|
-
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2537
|
-
granted,
|
|
2538
|
-
canAskAgain: !denied,
|
|
2539
|
-
}));
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
private async requestLocationPermissionResponse(): Promise<PermissionResponse> {
|
|
2543
|
-
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [
|
|
2544
|
-
'ohos.permission.APPROXIMATELY_LOCATION',
|
|
2545
|
-
'ohos.permission.LOCATION',
|
|
2546
|
-
]);
|
|
2547
|
-
|
|
2548
|
-
return this.getLocationPermissionResponse();
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
private resolvePermissionStatus(permissionName: Permissions): abilityAccessCtrl.PermissionStatus {
|
|
2552
|
-
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
2553
|
-
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
2554
|
-
};
|
|
2555
|
-
|
|
2556
|
-
if (typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function') {
|
|
2557
|
-
return atManagerWithSelfStatus.getSelfPermissionStatus(permissionName);
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
const accessTokenId = this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId;
|
|
2561
|
-
const grantStatus = this.atManager.checkAccessTokenSync(accessTokenId, permissionName);
|
|
2562
|
-
|
|
2563
|
-
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
2564
|
-
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
2565
|
-
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
private buildPermissionResponse(
|
|
2569
|
-
approximateStatus: abilityAccessCtrl.PermissionStatus,
|
|
2570
|
-
preciseStatus: abilityAccessCtrl.PermissionStatus,
|
|
2571
|
-
): PermissionResponse {
|
|
2572
|
-
const coarseGranted = approximateStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2573
|
-
const fineGranted = preciseStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2574
|
-
const granted = coarseGranted || fineGranted;
|
|
2575
|
-
const denied =
|
|
2576
|
-
(approximateStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2577
|
-
approximateStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2578
|
-
approximateStatus === abilityAccessCtrl.PermissionStatus.INVALID) &&
|
|
2579
|
-
(preciseStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2580
|
-
preciseStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2581
|
-
preciseStatus === abilityAccessCtrl.PermissionStatus.INVALID);
|
|
2582
|
-
|
|
2583
|
-
return {
|
|
2584
|
-
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2585
|
-
granted,
|
|
2586
|
-
canAskAgain: !denied,
|
|
2587
|
-
expires: 'never',
|
|
2588
|
-
android: {
|
|
2589
|
-
accuracy: fineGranted ? 'fine' : coarseGranted ? 'coarse' : 'none',
|
|
2590
|
-
},
|
|
2591
|
-
};
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
private createCurrentLocationRequest(accuracy?: number): geoLocationManager.CurrentLocationRequest {
|
|
2595
|
-
if (typeof accuracy === 'number' && accuracy >= 4) {
|
|
2596
|
-
return {
|
|
2597
|
-
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
|
|
2598
|
-
};
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
if (typeof accuracy === 'number' && accuracy <= 2) {
|
|
2602
|
-
return {
|
|
2603
|
-
priority: geoLocationManager.LocationRequestPriority.LOW_POWER,
|
|
2604
|
-
};
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
return {
|
|
2608
|
-
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
|
|
2609
|
-
};
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
private hasCoordinates(location: geoLocationManager.Location | null | undefined): location is geoLocationManager.Location {
|
|
2613
|
-
return (
|
|
2614
|
-
!!location &&
|
|
2615
|
-
typeof location.latitude === 'number' &&
|
|
2616
|
-
typeof location.longitude === 'number'
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
private normalizeLocation(location: geoLocationManager.Location): ExpoLocationObject {
|
|
2621
|
-
return {
|
|
2622
|
-
coords: {
|
|
2623
|
-
latitude: Number(location.latitude),
|
|
2624
|
-
longitude: Number(location.longitude),
|
|
2625
|
-
altitude: typeof location.altitude === 'number' ? location.altitude : null,
|
|
2626
|
-
accuracy: typeof location.accuracy === 'number' ? location.accuracy : null,
|
|
2627
|
-
altitudeAccuracy: null,
|
|
2628
|
-
heading: typeof location.direction === 'number' ? location.direction : null,
|
|
2629
|
-
speed: typeof location.speed === 'number' ? location.speed : null,
|
|
2630
|
-
},
|
|
2631
|
-
timestamp: Number(location.timeStamp ?? Date.now()),
|
|
2632
|
-
mocked: false,
|
|
2633
|
-
};
|
|
2634
|
-
}
|
|
2635
|
-
|
|
2636
|
-
private async readHeadingSnapshot(): Promise<HeadingObject> {
|
|
2637
|
-
return new Promise((resolve) => {
|
|
2638
|
-
try {
|
|
2639
|
-
sensor.once(sensor.SensorId.ROTATION_VECTOR, (data: Record<string, number>) => {
|
|
2640
|
-
const x = Number(data?.x ?? 0);
|
|
2641
|
-
const y = Number(data?.y ?? 0);
|
|
2642
|
-
const z = Number(data?.z ?? 0);
|
|
2643
|
-
const w = Number(data?.w ?? 1);
|
|
2644
|
-
const sinyCosp = 2 * (w * z + x * y);
|
|
2645
|
-
const cosyCosp = 1 - 2 * (y * y + z * z);
|
|
2646
|
-
const magHeading = (Math.atan2(sinyCosp, cosyCosp) * 180 / Math.PI + 360) % 360;
|
|
2647
|
-
resolve({
|
|
2648
|
-
magHeading,
|
|
2649
|
-
trueHeading: magHeading,
|
|
2650
|
-
accuracy: 3,
|
|
2651
|
-
});
|
|
2652
|
-
});
|
|
2653
|
-
} catch (_error) {
|
|
2654
|
-
resolve({
|
|
2655
|
-
magHeading: 0,
|
|
2656
|
-
trueHeading: null,
|
|
2657
|
-
accuracy: 0,
|
|
2658
|
-
});
|
|
2659
|
-
}
|
|
2660
|
-
});
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
`;
|
|
2664
|
-
}
|
|
2665
|
-
function renderExpoHarmonyCameraTurboModule() {
|
|
2666
|
-
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
2667
|
-
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
2668
|
-
import camera from '@ohos.multimedia.camera';
|
|
2669
|
-
import cameraPicker from '@ohos.multimedia.cameraPicker';
|
|
2670
|
-
import image from '@ohos.multimedia.image';
|
|
2671
|
-
import { UITurboModuleContext, UITurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
2672
|
-
|
|
2673
|
-
type PermissionResponse = {
|
|
2674
|
-
status: 'granted' | 'denied' | 'undetermined';
|
|
2675
|
-
granted: boolean;
|
|
2676
|
-
canAskAgain: boolean;
|
|
2677
|
-
expires: 'never';
|
|
2678
|
-
};
|
|
2679
|
-
|
|
2680
|
-
type CameraCaptureResult = {
|
|
2681
|
-
uri: string;
|
|
2682
|
-
width: number;
|
|
2683
|
-
height: number;
|
|
2684
|
-
base64?: string;
|
|
2685
|
-
exif: null;
|
|
2686
|
-
};
|
|
2687
|
-
|
|
2688
|
-
type CameraRecordingResult = {
|
|
2689
|
-
uri: string;
|
|
2690
|
-
duration: number;
|
|
2691
|
-
fileSize: number | null;
|
|
2692
|
-
mimeType: string;
|
|
2693
|
-
};
|
|
2694
|
-
|
|
2695
|
-
export class ExpoHarmonyCameraTurboModule extends UITurboModule {
|
|
2696
|
-
public static readonly NAME = 'ExpoHarmonyCamera';
|
|
2697
|
-
|
|
2698
|
-
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
2699
|
-
private readonly previewStates = new Map<string, 'running' | 'paused'>();
|
|
2700
|
-
private readonly activeRecordings = new Map<string, CameraRecordingResult>();
|
|
2701
|
-
|
|
2702
|
-
getConstants(): Record<string, never> {
|
|
2703
|
-
return {};
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
async getCameraPermissionStatus(): Promise<PermissionResponse> {
|
|
2707
|
-
return this.getPermissionResponse('ohos.permission.CAMERA');
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
async requestCameraPermission(): Promise<PermissionResponse> {
|
|
2711
|
-
return this.requestPermissionResponse('ohos.permission.CAMERA');
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
async getMicrophonePermissionStatus(): Promise<PermissionResponse> {
|
|
2715
|
-
return this.getPermissionResponse('ohos.permission.MICROPHONE');
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
async requestMicrophonePermission(): Promise<PermissionResponse> {
|
|
2719
|
-
return this.requestPermissionResponse('ohos.permission.MICROPHONE');
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
async createPreview(options?: { viewId?: string }): Promise<{ viewId: string; state: 'running' }> {
|
|
2723
|
-
const viewId = typeof options?.viewId === 'string' && options.viewId.length > 0
|
|
2724
|
-
? options.viewId
|
|
2725
|
-
: 'expo-harmony-camera-preview';
|
|
2726
|
-
this.previewStates.set(viewId, 'running');
|
|
2727
|
-
return {
|
|
2728
|
-
viewId,
|
|
2729
|
-
state: 'running',
|
|
2730
|
-
};
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
async disposePreview(options?: { viewId?: string }): Promise<void> {
|
|
2734
|
-
if (typeof options?.viewId === 'string') {
|
|
2735
|
-
this.previewStates.delete(options.viewId);
|
|
2736
|
-
this.activeRecordings.delete(options.viewId);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
async pausePreview(options?: { viewId?: string }): Promise<{ paused: boolean }> {
|
|
2741
|
-
if (typeof options?.viewId === 'string') {
|
|
2742
|
-
this.previewStates.set(options.viewId, 'paused');
|
|
2743
|
-
}
|
|
2744
|
-
return {
|
|
2745
|
-
paused: true,
|
|
2746
|
-
};
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
async resumePreview(options?: { viewId?: string }): Promise<{ paused: boolean }> {
|
|
2750
|
-
if (typeof options?.viewId === 'string') {
|
|
2751
|
-
this.previewStates.set(options.viewId, 'running');
|
|
2752
|
-
}
|
|
2753
|
-
return {
|
|
2754
|
-
paused: false,
|
|
2755
|
-
};
|
|
2756
|
-
}
|
|
2757
|
-
|
|
2758
|
-
async takePicture(options?: { cameraType?: string; viewId?: string }): Promise<CameraCaptureResult> {
|
|
2759
|
-
const profile = new cameraPicker.PickerProfile();
|
|
2760
|
-
profile.cameraPosition =
|
|
2761
|
-
options?.cameraType === 'front'
|
|
2762
|
-
? camera.CameraPosition.CAMERA_POSITION_FRONT
|
|
2763
|
-
: camera.CameraPosition.CAMERA_POSITION_BACK;
|
|
2764
|
-
|
|
2765
|
-
const result = await cameraPicker.pick(
|
|
2766
|
-
this.ctx.uiAbilityContext,
|
|
2767
|
-
[cameraPicker.PickerMediaType.PHOTO],
|
|
2768
|
-
profile,
|
|
2769
|
-
);
|
|
2770
|
-
|
|
2771
|
-
if (!result || typeof result.resultUri !== 'string' || result.resultUri.length === 0) {
|
|
2772
|
-
throw new Error('Camera capture was canceled.');
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
const imageSize = await this.getImageSize(result.resultUri);
|
|
2776
|
-
|
|
2777
|
-
return {
|
|
2778
|
-
uri: result.resultUri,
|
|
2779
|
-
width: imageSize.width,
|
|
2780
|
-
height: imageSize.height,
|
|
2781
|
-
exif: null,
|
|
2782
|
-
};
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
async startRecording(options?: { viewId?: string; cameraType?: string }): Promise<CameraRecordingResult> {
|
|
2786
|
-
await this.requestPermissionResponse('ohos.permission.MICROPHONE');
|
|
2787
|
-
const profile = new cameraPicker.PickerProfile();
|
|
2788
|
-
profile.cameraPosition =
|
|
2789
|
-
options?.cameraType === 'front'
|
|
2790
|
-
? camera.CameraPosition.CAMERA_POSITION_FRONT
|
|
2791
|
-
: camera.CameraPosition.CAMERA_POSITION_BACK;
|
|
2792
|
-
|
|
2793
|
-
const result = await cameraPicker.pick(
|
|
2794
|
-
this.ctx.uiAbilityContext,
|
|
2795
|
-
[cameraPicker.PickerMediaType.VIDEO],
|
|
2796
|
-
profile,
|
|
2797
|
-
);
|
|
2798
|
-
|
|
2799
|
-
if (!result || typeof result.resultUri !== 'string' || result.resultUri.length === 0) {
|
|
2800
|
-
throw new Error('Camera recording was canceled.');
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
const recording = {
|
|
2804
|
-
uri: result.resultUri,
|
|
2805
|
-
duration: 0,
|
|
2806
|
-
fileSize: null,
|
|
2807
|
-
mimeType: 'video/mp4',
|
|
2808
|
-
};
|
|
2809
|
-
|
|
2810
|
-
if (typeof options?.viewId === 'string') {
|
|
2811
|
-
this.activeRecordings.set(options.viewId, recording);
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
return recording;
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
async stopRecording(options?: { viewId?: string }): Promise<CameraRecordingResult | null> {
|
|
2818
|
-
if (typeof options?.viewId === 'string') {
|
|
2819
|
-
const recording = this.activeRecordings.get(options.viewId) ?? null;
|
|
2820
|
-
this.activeRecordings.delete(options.viewId);
|
|
2821
|
-
return recording;
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
return null;
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
|
-
async toggleRecording(options?: { viewId?: string; cameraType?: string }): Promise<CameraRecordingResult | null> {
|
|
2828
|
-
if (typeof options?.viewId === 'string' && this.activeRecordings.has(options.viewId)) {
|
|
2829
|
-
return this.stopRecording(options);
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
return this.startRecording(options);
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
private async getPermissionResponse(permissionName: Permissions): Promise<PermissionResponse> {
|
|
2836
|
-
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
2837
|
-
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
2838
|
-
};
|
|
2839
|
-
const permissionStatus =
|
|
2840
|
-
typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function'
|
|
2841
|
-
? atManagerWithSelfStatus.getSelfPermissionStatus(permissionName)
|
|
2842
|
-
: this.atManager.checkAccessTokenSync(
|
|
2843
|
-
this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId,
|
|
2844
|
-
permissionName,
|
|
2845
|
-
) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
2846
|
-
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
2847
|
-
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
2848
|
-
|
|
2849
|
-
return this.createPermissionResponse(permissionStatus);
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
private async requestPermissionResponse(permissionName: Permissions): Promise<PermissionResponse> {
|
|
2853
|
-
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [permissionName]);
|
|
2854
|
-
return this.getPermissionResponse(permissionName);
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
private createPermissionResponse(
|
|
2858
|
-
permissionStatus: abilityAccessCtrl.PermissionStatus,
|
|
2859
|
-
): PermissionResponse {
|
|
2860
|
-
const granted = permissionStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2861
|
-
const denied =
|
|
2862
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2863
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2864
|
-
permissionStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
2865
|
-
|
|
2866
|
-
return {
|
|
2867
|
-
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2868
|
-
granted,
|
|
2869
|
-
canAskAgain: !denied,
|
|
2870
|
-
expires: 'never',
|
|
2871
|
-
};
|
|
2872
|
-
}
|
|
2873
|
-
|
|
2874
|
-
private async getImageSize(assetUri: string): Promise<{ width: number; height: number }> {
|
|
2875
|
-
let imageSource: image.ImageSource | null = null;
|
|
2876
|
-
|
|
2877
|
-
try {
|
|
2878
|
-
imageSource = image.createImageSource(assetUri);
|
|
2879
|
-
const imageInfo = await imageSource.getImageInfo();
|
|
2880
|
-
return {
|
|
2881
|
-
width: Number(imageInfo.size?.width ?? 0),
|
|
2882
|
-
height: Number(imageInfo.size?.height ?? 0),
|
|
2883
|
-
};
|
|
2884
|
-
} catch (_error) {
|
|
2885
|
-
return {
|
|
2886
|
-
width: 0,
|
|
2887
|
-
height: 0,
|
|
2888
|
-
};
|
|
2889
|
-
} finally {
|
|
2890
|
-
if (imageSource) {
|
|
2891
|
-
try {
|
|
2892
|
-
await imageSource.release();
|
|
2893
|
-
} catch (_error) {
|
|
2894
|
-
// Ignore cleanup errors from ImageSource release.
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
`;
|
|
2901
|
-
}
|
|
2902
|
-
function renderExpoFileSystemHarmonyAdapterShim(capability) {
|
|
2903
|
-
return `'use strict';
|
|
2904
|
-
|
|
2905
|
-
const { TurboModuleRegistry } = require('react-native');
|
|
2906
|
-
const { CodedError } = require('expo-modules-core');
|
|
2907
|
-
|
|
2908
|
-
const FILE_SCHEME = 'file://';
|
|
2909
|
-
const NATIVE_MODULE_NAME = 'ExpoHarmonyFileSystem';
|
|
2910
|
-
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
2911
|
-
const NATIVE_CONSTANTS = NATIVE_MODULE?.getConstants ? NATIVE_MODULE.getConstants() : {};
|
|
2912
|
-
|
|
2913
|
-
function createError(code, message) {
|
|
2914
|
-
return new CodedError(code, message);
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
function requireNativeModule(operationName) {
|
|
2918
|
-
if (NATIVE_MODULE) {
|
|
2919
|
-
return NATIVE_MODULE;
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
throw createError(
|
|
2923
|
-
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
2924
|
-
'${capability.packageName} expected the ' +
|
|
2925
|
-
NATIVE_MODULE_NAME +
|
|
2926
|
-
' TurboModule to be registered, but it was missing while running ' +
|
|
2927
|
-
operationName +
|
|
2928
|
-
'.',
|
|
2929
|
-
);
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
function createUnsupportedError(operationName) {
|
|
2933
|
-
return createError(
|
|
2934
|
-
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
2935
|
-
'${capability.packageName} currently supports UTF-8 sandbox file operations only. Unsupported operation: ' +
|
|
2936
|
-
operationName +
|
|
2937
|
-
'.',
|
|
2938
|
-
);
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
function toFileUri(pathValue, ensureTrailingSlash) {
|
|
2942
|
-
if (typeof pathValue !== 'string' || pathValue.length === 0) {
|
|
2943
|
-
return null;
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
|
-
const normalizedPath = pathValue.startsWith(FILE_SCHEME)
|
|
2947
|
-
? pathValue.slice(FILE_SCHEME.length)
|
|
2948
|
-
: pathValue;
|
|
2949
|
-
const withScheme = FILE_SCHEME + normalizedPath;
|
|
2950
|
-
|
|
2951
|
-
if (!ensureTrailingSlash) {
|
|
2952
|
-
return withScheme;
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
let normalizedSchemePath = withScheme;
|
|
2956
|
-
|
|
2957
|
-
while (normalizedSchemePath.endsWith('/')) {
|
|
2958
|
-
normalizedSchemePath = normalizedSchemePath.slice(0, -1);
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
return normalizedSchemePath + '/';
|
|
2962
|
-
}
|
|
2963
|
-
|
|
2964
|
-
function normalizeInputPath(inputPath) {
|
|
2965
|
-
if (typeof inputPath !== 'string' || inputPath.length === 0) {
|
|
2966
|
-
throw createError(
|
|
2967
|
-
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2968
|
-
'${capability.packageName} expected a non-empty file URI.',
|
|
2969
|
-
);
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
const normalizedPath = inputPath.startsWith(FILE_SCHEME)
|
|
2973
|
-
? inputPath.slice(FILE_SCHEME.length)
|
|
2974
|
-
: inputPath;
|
|
2975
|
-
|
|
2976
|
-
if (!normalizedPath.startsWith('/')) {
|
|
2977
|
-
throw createError(
|
|
2978
|
-
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2979
|
-
'${capability.packageName} supports only absolute file:// URIs inside the app sandbox.',
|
|
2980
|
-
);
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
if (
|
|
2984
|
-
normalizedPath.includes('/../') ||
|
|
2985
|
-
normalizedPath.endsWith('/..') ||
|
|
2986
|
-
normalizedPath.includes('/./')
|
|
2987
|
-
) {
|
|
2988
|
-
throw createError(
|
|
2989
|
-
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2990
|
-
'${capability.packageName} does not accept relative path segments.',
|
|
2991
|
-
);
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
return normalizedPath;
|
|
2995
|
-
}
|
|
2996
|
-
|
|
2997
|
-
function normalizeStringEncoding(rawEncoding) {
|
|
2998
|
-
if (rawEncoding == null || rawEncoding === 'utf8') {
|
|
2999
|
-
return 'utf8';
|
|
3000
|
-
}
|
|
3001
|
-
|
|
3002
|
-
if (rawEncoding === 'base64') {
|
|
3003
|
-
return 'base64';
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
throw createUnsupportedError('encoding=' + String(rawEncoding));
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
function normalizeFileDownloadResult(result, requestedUri) {
|
|
3010
|
-
return {
|
|
3011
|
-
uri: result?.uri ?? String(requestedUri),
|
|
3012
|
-
status:
|
|
3013
|
-
typeof result?.status === 'number' && Number.isFinite(result.status)
|
|
3014
|
-
? result.status
|
|
3015
|
-
: 200,
|
|
3016
|
-
headers: result?.headers && typeof result.headers === 'object' ? result.headers : {},
|
|
3017
|
-
md5: typeof result?.md5 === 'string' ? result.md5 : undefined,
|
|
3018
|
-
};
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3021
|
-
function normalizeFileInfoResult(requestedUri, nativeResult) {
|
|
3022
|
-
if (!nativeResult || nativeResult.exists !== true) {
|
|
3023
|
-
return {
|
|
3024
|
-
exists: false,
|
|
3025
|
-
isDirectory: false,
|
|
3026
|
-
uri: String(requestedUri),
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
|
|
3030
|
-
const normalizedResult = {
|
|
3031
|
-
exists: true,
|
|
3032
|
-
uri: toFileUri(nativeResult.path, false) ?? String(requestedUri),
|
|
3033
|
-
size: Number(nativeResult.size ?? 0),
|
|
3034
|
-
isDirectory: nativeResult.isDirectory === true,
|
|
3035
|
-
modificationTime: Number(nativeResult.modificationTime ?? 0),
|
|
3036
|
-
};
|
|
3037
|
-
|
|
3038
|
-
if (typeof nativeResult.md5 === 'string' && nativeResult.md5.length > 0) {
|
|
3039
|
-
normalizedResult.md5 = nativeResult.md5;
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
return normalizedResult;
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
function normalizeNativeError(error) {
|
|
3046
|
-
if (error instanceof Error) {
|
|
3047
|
-
return error;
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
if (error && typeof error === 'object') {
|
|
3051
|
-
const code =
|
|
3052
|
-
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3053
|
-
? String(error.code)
|
|
3054
|
-
: null;
|
|
3055
|
-
const message =
|
|
3056
|
-
typeof error.message === 'string' && error.message.length > 0
|
|
3057
|
-
? error.message
|
|
3058
|
-
: typeof error.name === 'string' && error.name.length > 0
|
|
3059
|
-
? error.name
|
|
3060
|
-
: JSON.stringify(error);
|
|
3061
|
-
|
|
3062
|
-
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
return new Error(String(error));
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
module.exports = {
|
|
3069
|
-
documentDirectory: toFileUri(NATIVE_CONSTANTS.documentDirectoryPath, true),
|
|
3070
|
-
cacheDirectory: toFileUri(NATIVE_CONSTANTS.cacheDirectoryPath, true),
|
|
3071
|
-
bundleDirectory: toFileUri(NATIVE_CONSTANTS.bundleDirectoryPath, true),
|
|
3072
|
-
EncodingType: {
|
|
3073
|
-
UTF8: 'utf8',
|
|
3074
|
-
Base64: 'base64',
|
|
3075
|
-
},
|
|
3076
|
-
FileSystemSessionType: {
|
|
3077
|
-
BACKGROUND: 0,
|
|
3078
|
-
FOREGROUND: 1,
|
|
3079
|
-
},
|
|
3080
|
-
async getInfoAsync(fileUri, options) {
|
|
3081
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3082
|
-
try {
|
|
3083
|
-
const result = await requireNativeModule('getInfoAsync').getInfo(normalizedPath, {
|
|
3084
|
-
md5: options?.md5 === true,
|
|
3085
|
-
});
|
|
3086
|
-
return normalizeFileInfoResult(fileUri, result);
|
|
3087
|
-
} catch (error) {
|
|
3088
|
-
throw normalizeNativeError(error);
|
|
3089
|
-
}
|
|
3090
|
-
},
|
|
3091
|
-
async readAsStringAsync(fileUri, options) {
|
|
3092
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3093
|
-
try {
|
|
3094
|
-
return await requireNativeModule('readAsStringAsync').readAsString(normalizedPath, {
|
|
3095
|
-
encoding: normalizeStringEncoding(options?.encoding),
|
|
3096
|
-
position: options?.position,
|
|
3097
|
-
length: options?.length,
|
|
3098
|
-
});
|
|
3099
|
-
} catch (error) {
|
|
3100
|
-
throw normalizeNativeError(error);
|
|
3101
|
-
}
|
|
3102
|
-
},
|
|
3103
|
-
async writeAsStringAsync(fileUri, contents, options) {
|
|
3104
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3105
|
-
try {
|
|
3106
|
-
await requireNativeModule('writeAsStringAsync').writeAsString(
|
|
3107
|
-
normalizedPath,
|
|
3108
|
-
String(contents),
|
|
3109
|
-
{
|
|
3110
|
-
encoding: normalizeStringEncoding(options?.encoding),
|
|
3111
|
-
append: options?.append === true,
|
|
3112
|
-
},
|
|
3113
|
-
);
|
|
3114
|
-
} catch (error) {
|
|
3115
|
-
throw normalizeNativeError(error);
|
|
3116
|
-
}
|
|
3117
|
-
},
|
|
3118
|
-
async deleteAsync(fileUri, options) {
|
|
3119
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3120
|
-
try {
|
|
3121
|
-
await requireNativeModule('deleteAsync').deletePath(normalizedPath, {
|
|
3122
|
-
idempotent: options?.idempotent === true,
|
|
3123
|
-
});
|
|
3124
|
-
} catch (error) {
|
|
3125
|
-
throw normalizeNativeError(error);
|
|
3126
|
-
}
|
|
3127
|
-
},
|
|
3128
|
-
async makeDirectoryAsync(fileUri, options) {
|
|
3129
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3130
|
-
try {
|
|
3131
|
-
await requireNativeModule('makeDirectoryAsync').makeDirectory(normalizedPath, {
|
|
3132
|
-
intermediates: options?.intermediates === true,
|
|
3133
|
-
});
|
|
3134
|
-
} catch (error) {
|
|
3135
|
-
throw normalizeNativeError(error);
|
|
3136
|
-
}
|
|
3137
|
-
},
|
|
3138
|
-
async readDirectoryAsync(fileUri) {
|
|
3139
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3140
|
-
try {
|
|
3141
|
-
return await requireNativeModule('readDirectoryAsync').readDirectory(normalizedPath);
|
|
3142
|
-
} catch (error) {
|
|
3143
|
-
throw normalizeNativeError(error);
|
|
3144
|
-
}
|
|
3145
|
-
},
|
|
3146
|
-
async copyAsync(options) {
|
|
3147
|
-
const fromPath = normalizeInputPath(options?.from);
|
|
3148
|
-
const toPath = normalizeInputPath(options?.to);
|
|
3149
|
-
try {
|
|
3150
|
-
await requireNativeModule('copyAsync').copy(fromPath, toPath);
|
|
3151
|
-
} catch (error) {
|
|
3152
|
-
throw normalizeNativeError(error);
|
|
3153
|
-
}
|
|
3154
|
-
},
|
|
3155
|
-
async moveAsync(options) {
|
|
3156
|
-
const fromPath = normalizeInputPath(options?.from);
|
|
3157
|
-
const toPath = normalizeInputPath(options?.to);
|
|
3158
|
-
try {
|
|
3159
|
-
await requireNativeModule('moveAsync').move(fromPath, toPath);
|
|
3160
|
-
} catch (error) {
|
|
3161
|
-
throw normalizeNativeError(error);
|
|
3162
|
-
}
|
|
3163
|
-
},
|
|
3164
|
-
async downloadAsync(url, fileUri, options) {
|
|
3165
|
-
const normalizedPath = normalizeInputPath(fileUri);
|
|
3166
|
-
try {
|
|
3167
|
-
return normalizeFileDownloadResult(
|
|
3168
|
-
await requireNativeModule('downloadAsync').download(
|
|
3169
|
-
String(url),
|
|
3170
|
-
normalizedPath,
|
|
3171
|
-
{
|
|
3172
|
-
headers: options?.headers ?? {},
|
|
3173
|
-
md5: options?.md5 === true,
|
|
3174
|
-
},
|
|
3175
|
-
),
|
|
3176
|
-
fileUri,
|
|
3177
|
-
);
|
|
3178
|
-
} catch (error) {
|
|
3179
|
-
throw normalizeNativeError(error);
|
|
3180
|
-
}
|
|
3181
|
-
},
|
|
3182
|
-
};
|
|
3183
|
-
`;
|
|
3184
|
-
}
|
|
3185
|
-
function renderExpoFileSystemPreviewShim(capability) {
|
|
3186
|
-
return `'use strict';
|
|
3187
|
-
|
|
3188
|
-
const { CodedError } = require('expo-modules-core');
|
|
3189
|
-
|
|
3190
|
-
const PREVIEW_MESSAGE =
|
|
3191
|
-
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but runtime file-system behavior is not verified yet.';
|
|
3192
|
-
|
|
3193
|
-
function createPreviewError(operationName) {
|
|
3194
|
-
return new CodedError(
|
|
3195
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3196
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3197
|
-
);
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
async function unavailable(operationName) {
|
|
3201
|
-
throw createPreviewError(operationName);
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
module.exports = {
|
|
3205
|
-
cacheDirectory: 'file:///expo-harmony/cache/',
|
|
3206
|
-
documentDirectory: 'file:///expo-harmony/document/',
|
|
3207
|
-
bundleDirectory: 'file:///expo-harmony/bundle/',
|
|
3208
|
-
EncodingType: {
|
|
3209
|
-
UTF8: 'utf8',
|
|
3210
|
-
Base64: 'base64',
|
|
3211
|
-
},
|
|
3212
|
-
FileSystemSessionType: {
|
|
3213
|
-
BACKGROUND: 0,
|
|
3214
|
-
FOREGROUND: 1,
|
|
3215
|
-
},
|
|
3216
|
-
getInfoAsync(path) {
|
|
3217
|
-
return unavailable('getInfoAsync(' + String(path) + ')');
|
|
3218
|
-
},
|
|
3219
|
-
readAsStringAsync(path) {
|
|
3220
|
-
return unavailable('readAsStringAsync(' + String(path) + ')');
|
|
3221
|
-
},
|
|
3222
|
-
writeAsStringAsync(path) {
|
|
3223
|
-
return unavailable('writeAsStringAsync(' + String(path) + ')');
|
|
3224
|
-
},
|
|
3225
|
-
deleteAsync(path) {
|
|
3226
|
-
return unavailable('deleteAsync(' + String(path) + ')');
|
|
3227
|
-
},
|
|
3228
|
-
makeDirectoryAsync(path) {
|
|
3229
|
-
return unavailable('makeDirectoryAsync(' + String(path) + ')');
|
|
3230
|
-
},
|
|
3231
|
-
readDirectoryAsync(path) {
|
|
3232
|
-
return unavailable('readDirectoryAsync(' + String(path) + ')');
|
|
3233
|
-
},
|
|
3234
|
-
copyAsync(options) {
|
|
3235
|
-
return unavailable('copyAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3236
|
-
},
|
|
3237
|
-
moveAsync(options) {
|
|
3238
|
-
return unavailable('moveAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3239
|
-
},
|
|
3240
|
-
downloadAsync(url) {
|
|
3241
|
-
return unavailable('downloadAsync(' + String(url) + ')');
|
|
3242
|
-
},
|
|
3243
|
-
};
|
|
3244
|
-
`;
|
|
3245
|
-
}
|
|
3246
|
-
function renderExpoImagePickerHarmonyAdapterShim(capability) {
|
|
3247
|
-
return `'use strict';
|
|
3248
|
-
|
|
3249
|
-
const { TurboModuleRegistry } = require('react-native');
|
|
3250
|
-
const { CodedError } = require('expo-modules-core');
|
|
3251
|
-
|
|
3252
|
-
const NATIVE_MODULE_NAME = 'ExpoHarmonyImagePicker';
|
|
3253
|
-
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3254
|
-
|
|
3255
|
-
function createError(code, message) {
|
|
3256
|
-
return new CodedError(code, message);
|
|
3257
|
-
}
|
|
3258
|
-
|
|
3259
|
-
function requireNativeModule(operationName) {
|
|
3260
|
-
if (NATIVE_MODULE) {
|
|
3261
|
-
return NATIVE_MODULE;
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
throw createError(
|
|
3265
|
-
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3266
|
-
'${capability.packageName} expected the ' +
|
|
3267
|
-
NATIVE_MODULE_NAME +
|
|
3268
|
-
' TurboModule to be registered, but it was missing while running ' +
|
|
3269
|
-
operationName +
|
|
3270
|
-
'.',
|
|
3271
|
-
);
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
|
-
function normalizeNativeError(error) {
|
|
3275
|
-
if (error instanceof Error) {
|
|
3276
|
-
return error;
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
|
-
if (error && typeof error === 'object') {
|
|
3280
|
-
const code =
|
|
3281
|
-
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3282
|
-
? String(error.code)
|
|
3283
|
-
: null;
|
|
3284
|
-
const message =
|
|
3285
|
-
typeof error.message === 'string' && error.message.length > 0
|
|
3286
|
-
? error.message
|
|
3287
|
-
: typeof error.name === 'string' && error.name.length > 0
|
|
3288
|
-
? error.name
|
|
3289
|
-
: JSON.stringify(error);
|
|
3290
|
-
|
|
3291
|
-
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
return new Error(String(error));
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
function normalizePickerResult(result) {
|
|
3298
|
-
if (!result || result.canceled === true || !Array.isArray(result.assets) || result.assets.length === 0) {
|
|
3299
|
-
return {
|
|
3300
|
-
canceled: true,
|
|
3301
|
-
assets: null,
|
|
3302
|
-
};
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
return {
|
|
3306
|
-
canceled: false,
|
|
3307
|
-
assets: result.assets.map((asset) => ({
|
|
3308
|
-
uri: String(asset.uri),
|
|
3309
|
-
assetId: asset.assetId ?? null,
|
|
3310
|
-
width: Number(asset.width ?? 0),
|
|
3311
|
-
height: Number(asset.height ?? 0),
|
|
3312
|
-
type: asset.type ?? null,
|
|
3313
|
-
fileName: asset.fileName ?? null,
|
|
3314
|
-
fileSize:
|
|
3315
|
-
typeof asset.fileSize === 'number' && Number.isFinite(asset.fileSize)
|
|
3316
|
-
? asset.fileSize
|
|
3317
|
-
: null,
|
|
3318
|
-
mimeType: asset.mimeType ?? null,
|
|
3319
|
-
duration:
|
|
3320
|
-
typeof asset.duration === 'number' && Number.isFinite(asset.duration)
|
|
3321
|
-
? asset.duration
|
|
3322
|
-
: null,
|
|
3323
|
-
exif: asset.exif ?? null,
|
|
3324
|
-
base64: asset.base64 ?? null,
|
|
3325
|
-
})),
|
|
3326
|
-
};
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
async function invokeNative(methodName, operationName, ...args) {
|
|
3330
|
-
try {
|
|
3331
|
-
return await requireNativeModule(operationName)[methodName](...args);
|
|
3332
|
-
} catch (error) {
|
|
3333
|
-
throw normalizeNativeError(error);
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
|
|
3337
|
-
module.exports = {
|
|
3338
|
-
MediaTypeOptions: {
|
|
3339
|
-
All: 'All',
|
|
3340
|
-
Images: 'Images',
|
|
3341
|
-
Videos: 'Videos',
|
|
3342
|
-
},
|
|
3343
|
-
CameraType: {
|
|
3344
|
-
front: 'front',
|
|
3345
|
-
back: 'back',
|
|
3346
|
-
},
|
|
3347
|
-
UIImagePickerPresentationStyle: {
|
|
3348
|
-
AUTOMATIC: 'automatic',
|
|
3349
|
-
FULL_SCREEN: 'fullScreen',
|
|
3350
|
-
PAGE_SHEET: 'pageSheet',
|
|
3351
|
-
FORM_SHEET: 'formSheet',
|
|
3352
|
-
CURRENT_CONTEXT: 'currentContext',
|
|
3353
|
-
OVER_FULL_SCREEN: 'overFullScreen',
|
|
3354
|
-
},
|
|
3355
|
-
async requestCameraPermissionsAsync() {
|
|
3356
|
-
return invokeNative(
|
|
3357
|
-
'requestCameraPermission',
|
|
3358
|
-
'requestCameraPermissionsAsync',
|
|
3359
|
-
);
|
|
3360
|
-
},
|
|
3361
|
-
async requestMediaLibraryPermissionsAsync(writeOnly) {
|
|
3362
|
-
return invokeNative(
|
|
3363
|
-
'requestMediaLibraryPermission',
|
|
3364
|
-
'requestMediaLibraryPermissionsAsync',
|
|
3365
|
-
writeOnly === true,
|
|
3366
|
-
);
|
|
3367
|
-
},
|
|
3368
|
-
async getCameraPermissionsAsync() {
|
|
3369
|
-
return invokeNative('getCameraPermissionStatus', 'getCameraPermissionsAsync');
|
|
3370
|
-
},
|
|
3371
|
-
async getMediaLibraryPermissionsAsync(writeOnly) {
|
|
3372
|
-
return invokeNative(
|
|
3373
|
-
'getMediaLibraryPermissionStatus',
|
|
3374
|
-
'getMediaLibraryPermissionsAsync',
|
|
3375
|
-
writeOnly === true,
|
|
3376
|
-
);
|
|
3377
|
-
},
|
|
3378
|
-
async launchCameraAsync(options) {
|
|
3379
|
-
return normalizePickerResult(
|
|
3380
|
-
await invokeNative('launchCamera', 'launchCameraAsync', options ?? {}),
|
|
3381
|
-
);
|
|
3382
|
-
},
|
|
3383
|
-
async launchImageLibraryAsync(options) {
|
|
3384
|
-
return normalizePickerResult(
|
|
3385
|
-
await invokeNative('launchImageLibrary', 'launchImageLibraryAsync', options ?? {}),
|
|
3386
|
-
);
|
|
3387
|
-
},
|
|
3388
|
-
async getPendingResultAsync() {
|
|
3389
|
-
const result = await invokeNative('getPendingResult', 'getPendingResultAsync');
|
|
3390
|
-
return result ? normalizePickerResult(result) : null;
|
|
3391
|
-
},
|
|
3392
|
-
};
|
|
3393
|
-
`;
|
|
3394
|
-
}
|
|
3395
|
-
function renderExpoLocationHarmonyAdapterShim(capability) {
|
|
3396
|
-
return `'use strict';
|
|
3397
|
-
|
|
3398
|
-
const { TurboModuleRegistry } = require('react-native');
|
|
3399
|
-
const { CodedError } = require('expo-modules-core');
|
|
3400
|
-
|
|
3401
|
-
const NATIVE_MODULE_NAME = 'ExpoHarmonyLocation';
|
|
3402
|
-
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3403
|
-
|
|
3404
|
-
function createError(code, message) {
|
|
3405
|
-
return new CodedError(code, message);
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
function createUnsupportedError(operationName) {
|
|
3409
|
-
return createError(
|
|
3410
|
-
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
3411
|
-
'${capability.packageName} does not implement ' + operationName + ' on HarmonyOS yet.',
|
|
3412
|
-
);
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
|
-
function requireNativeModule(operationName) {
|
|
3416
|
-
if (NATIVE_MODULE) {
|
|
3417
|
-
return NATIVE_MODULE;
|
|
3418
|
-
}
|
|
3419
|
-
|
|
3420
|
-
throw createError(
|
|
3421
|
-
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3422
|
-
'${capability.packageName} expected the ' +
|
|
3423
|
-
NATIVE_MODULE_NAME +
|
|
3424
|
-
' TurboModule to be registered, but it was missing while running ' +
|
|
3425
|
-
operationName +
|
|
3426
|
-
'.',
|
|
3427
|
-
);
|
|
3428
|
-
}
|
|
3429
|
-
|
|
3430
|
-
function normalizeNativeError(error) {
|
|
3431
|
-
if (error instanceof Error) {
|
|
3432
|
-
return error;
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
|
-
if (error && typeof error === 'object') {
|
|
3436
|
-
const code =
|
|
3437
|
-
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3438
|
-
? String(error.code)
|
|
3439
|
-
: null;
|
|
3440
|
-
const message =
|
|
3441
|
-
typeof error.message === 'string' && error.message.length > 0
|
|
3442
|
-
? error.message
|
|
3443
|
-
: typeof error.name === 'string' && error.name.length > 0
|
|
3444
|
-
? error.name
|
|
3445
|
-
: JSON.stringify(error);
|
|
3446
|
-
|
|
3447
|
-
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
return new Error(String(error));
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
async function invokeNative(methodName, operationName, ...args) {
|
|
3454
|
-
try {
|
|
3455
|
-
return await requireNativeModule(operationName)[methodName](...args);
|
|
3456
|
-
} catch (error) {
|
|
3457
|
-
throw normalizeNativeError(error);
|
|
3458
|
-
}
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
function normalizePermissionResponse(permissionResponse) {
|
|
3462
|
-
return {
|
|
3463
|
-
status: permissionResponse?.status ?? 'undetermined',
|
|
3464
|
-
granted: permissionResponse?.granted === true,
|
|
3465
|
-
canAskAgain: permissionResponse?.canAskAgain !== false,
|
|
3466
|
-
expires: permissionResponse?.expires ?? 'never',
|
|
3467
|
-
android: permissionResponse?.android ?? { accuracy: 'none' },
|
|
3468
|
-
ios: permissionResponse?.ios ?? null,
|
|
3469
|
-
};
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
|
-
function normalizeLocationObject(location) {
|
|
3473
|
-
return {
|
|
3474
|
-
coords: {
|
|
3475
|
-
latitude: Number(location?.coords?.latitude ?? 0),
|
|
3476
|
-
longitude: Number(location?.coords?.longitude ?? 0),
|
|
3477
|
-
altitude:
|
|
3478
|
-
typeof location?.coords?.altitude === 'number' ? location.coords.altitude : null,
|
|
3479
|
-
accuracy:
|
|
3480
|
-
typeof location?.coords?.accuracy === 'number' ? location.coords.accuracy : null,
|
|
3481
|
-
altitudeAccuracy:
|
|
3482
|
-
typeof location?.coords?.altitudeAccuracy === 'number'
|
|
3483
|
-
? location.coords.altitudeAccuracy
|
|
3484
|
-
: null,
|
|
3485
|
-
heading: typeof location?.coords?.heading === 'number' ? location.coords.heading : null,
|
|
3486
|
-
speed: typeof location?.coords?.speed === 'number' ? location.coords.speed : null,
|
|
3487
|
-
},
|
|
3488
|
-
timestamp: Number(location?.timestamp ?? Date.now()),
|
|
3489
|
-
mocked: location?.mocked === true,
|
|
3490
|
-
};
|
|
3491
|
-
}
|
|
3492
|
-
|
|
3493
|
-
function normalizeProviderStatus(providerStatus) {
|
|
3494
|
-
return {
|
|
3495
|
-
locationServicesEnabled: providerStatus?.locationServicesEnabled === true,
|
|
3496
|
-
backgroundModeEnabled: providerStatus?.backgroundModeEnabled === true,
|
|
3497
|
-
gpsAvailable: providerStatus?.gpsAvailable === true,
|
|
3498
|
-
networkAvailable: providerStatus?.networkAvailable === true,
|
|
3499
|
-
passiveAvailable: providerStatus?.passiveAvailable === true,
|
|
3500
|
-
};
|
|
3501
|
-
}
|
|
3502
|
-
|
|
3503
|
-
function normalizeReverseGeocodeResult(address) {
|
|
3504
|
-
return {
|
|
3505
|
-
city: address?.city ?? null,
|
|
3506
|
-
district: address?.district ?? null,
|
|
3507
|
-
streetNumber: address?.streetNumber ?? null,
|
|
3508
|
-
street: address?.street ?? null,
|
|
3509
|
-
region: address?.region ?? null,
|
|
3510
|
-
subregion: address?.subregion ?? null,
|
|
3511
|
-
country: address?.country ?? null,
|
|
3512
|
-
postalCode: address?.postalCode ?? null,
|
|
3513
|
-
name: address?.name ?? null,
|
|
3514
|
-
isoCountryCode: address?.isoCountryCode ?? null,
|
|
3515
|
-
timezone: address?.timezone ?? null,
|
|
3516
|
-
formattedAddress: address?.formattedAddress ?? null,
|
|
3517
|
-
};
|
|
3518
|
-
}
|
|
3519
|
-
|
|
3520
|
-
function normalizeGeocodeInput(address) {
|
|
3521
|
-
if (typeof address === 'string') {
|
|
3522
|
-
return address.trim();
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
if (!address || typeof address !== 'object') {
|
|
3526
|
-
return '';
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
const parts = [
|
|
3530
|
-
address.name,
|
|
3531
|
-
address.streetNumber,
|
|
3532
|
-
address.street,
|
|
3533
|
-
address.district,
|
|
3534
|
-
address.city,
|
|
3535
|
-
address.region,
|
|
3536
|
-
address.postalCode,
|
|
3537
|
-
address.country,
|
|
3538
|
-
]
|
|
3539
|
-
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
3540
|
-
.map((part) => part.trim());
|
|
3541
|
-
|
|
3542
|
-
return parts.join(', ');
|
|
3543
|
-
}
|
|
3544
|
-
|
|
3545
|
-
function normalizeGeocodeResults(results) {
|
|
3546
|
-
if (!Array.isArray(results)) {
|
|
3547
|
-
return [];
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
return results.map((result) => ({
|
|
3551
|
-
latitude: Number(result?.latitude ?? 0),
|
|
3552
|
-
longitude: Number(result?.longitude ?? 0),
|
|
3553
|
-
altitude: typeof result?.altitude === 'number' ? result.altitude : null,
|
|
3554
|
-
accuracy: typeof result?.accuracy === 'number' ? result.accuracy : null,
|
|
3555
|
-
}));
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
function normalizeHeadingObject(heading) {
|
|
3559
|
-
return {
|
|
3560
|
-
magHeading: Number(heading?.magHeading ?? 0),
|
|
3561
|
-
trueHeading:
|
|
3562
|
-
typeof heading?.trueHeading === 'number' && Number.isFinite(heading.trueHeading)
|
|
3563
|
-
? heading.trueHeading
|
|
3564
|
-
: null,
|
|
3565
|
-
accuracy:
|
|
3566
|
-
typeof heading?.accuracy === 'number' && Number.isFinite(heading.accuracy)
|
|
3567
|
-
? heading.accuracy
|
|
3568
|
-
: 0,
|
|
3569
|
-
};
|
|
3570
|
-
}
|
|
3571
|
-
|
|
3572
|
-
function createSubscription(remove) {
|
|
3573
|
-
let active = true;
|
|
3574
|
-
|
|
3575
|
-
return {
|
|
3576
|
-
remove() {
|
|
3577
|
-
if (!active) {
|
|
3578
|
-
return;
|
|
3579
|
-
}
|
|
3580
|
-
|
|
3581
|
-
active = false;
|
|
3582
|
-
remove();
|
|
3583
|
-
},
|
|
3584
|
-
};
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
module.exports = {
|
|
3588
|
-
Accuracy: {
|
|
3589
|
-
Lowest: 1,
|
|
3590
|
-
Low: 2,
|
|
3591
|
-
Balanced: 3,
|
|
3592
|
-
High: 4,
|
|
3593
|
-
Highest: 5,
|
|
3594
|
-
BestForNavigation: 6,
|
|
3595
|
-
},
|
|
3596
|
-
PermissionStatus: {
|
|
3597
|
-
DENIED: 'denied',
|
|
3598
|
-
GRANTED: 'granted',
|
|
3599
|
-
UNDETERMINED: 'undetermined',
|
|
3600
|
-
},
|
|
3601
|
-
async getForegroundPermissionsAsync() {
|
|
3602
|
-
return normalizePermissionResponse(
|
|
3603
|
-
await invokeNative(
|
|
3604
|
-
'getForegroundPermissionStatus',
|
|
3605
|
-
'getForegroundPermissionsAsync',
|
|
3606
|
-
),
|
|
3607
|
-
);
|
|
3608
|
-
},
|
|
3609
|
-
async requestForegroundPermissionsAsync() {
|
|
3610
|
-
return normalizePermissionResponse(
|
|
3611
|
-
await invokeNative(
|
|
3612
|
-
'requestForegroundPermission',
|
|
3613
|
-
'requestForegroundPermissionsAsync',
|
|
3614
|
-
),
|
|
3615
|
-
);
|
|
3616
|
-
},
|
|
3617
|
-
async getBackgroundPermissionsAsync() {
|
|
3618
|
-
return normalizePermissionResponse(
|
|
3619
|
-
await invokeNative(
|
|
3620
|
-
'getBackgroundPermissionStatus',
|
|
3621
|
-
'getBackgroundPermissionsAsync',
|
|
3622
|
-
),
|
|
3623
|
-
);
|
|
3624
|
-
},
|
|
3625
|
-
async requestBackgroundPermissionsAsync() {
|
|
3626
|
-
return normalizePermissionResponse(
|
|
3627
|
-
await invokeNative(
|
|
3628
|
-
'requestBackgroundPermission',
|
|
3629
|
-
'requestBackgroundPermissionsAsync',
|
|
3630
|
-
),
|
|
3631
|
-
);
|
|
3632
|
-
},
|
|
3633
|
-
async hasServicesEnabledAsync() {
|
|
3634
|
-
return await invokeNative('hasServicesEnabled', 'hasServicesEnabledAsync');
|
|
3635
|
-
},
|
|
3636
|
-
async getProviderStatusAsync() {
|
|
3637
|
-
return normalizeProviderStatus(
|
|
3638
|
-
await invokeNative('getProviderStatus', 'getProviderStatusAsync'),
|
|
3639
|
-
);
|
|
3640
|
-
},
|
|
3641
|
-
async getCurrentPositionAsync(options) {
|
|
3642
|
-
return normalizeLocationObject(
|
|
3643
|
-
await invokeNative('getCurrentPosition', 'getCurrentPositionAsync', options ?? {}),
|
|
3644
|
-
);
|
|
3645
|
-
},
|
|
3646
|
-
async getLastKnownPositionAsync(options) {
|
|
3647
|
-
const location = await invokeNative(
|
|
3648
|
-
'getLastKnownPosition',
|
|
3649
|
-
'getLastKnownPositionAsync',
|
|
3650
|
-
options ?? {},
|
|
3651
|
-
);
|
|
3652
|
-
return location ? normalizeLocationObject(location) : null;
|
|
3653
|
-
},
|
|
3654
|
-
async geocodeAsync(address) {
|
|
3655
|
-
return normalizeGeocodeResults(
|
|
3656
|
-
await invokeNative(
|
|
3657
|
-
'geocode',
|
|
3658
|
-
'geocodeAsync',
|
|
3659
|
-
normalizeGeocodeInput(address),
|
|
3660
|
-
),
|
|
3661
|
-
);
|
|
3662
|
-
},
|
|
3663
|
-
async reverseGeocodeAsync(location) {
|
|
3664
|
-
const results = await invokeNative(
|
|
3665
|
-
'reverseGeocode',
|
|
3666
|
-
'reverseGeocodeAsync',
|
|
3667
|
-
{
|
|
3668
|
-
latitude: Number(location?.latitude ?? 0),
|
|
3669
|
-
longitude: Number(location?.longitude ?? 0),
|
|
3670
|
-
},
|
|
3671
|
-
);
|
|
3672
|
-
|
|
3673
|
-
if (!Array.isArray(results)) {
|
|
3674
|
-
return [];
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
return results.map(normalizeReverseGeocodeResult);
|
|
3678
|
-
},
|
|
3679
|
-
async watchPositionAsync(options, callback, errorHandler) {
|
|
3680
|
-
if (typeof callback !== 'function') {
|
|
3681
|
-
throw createError(
|
|
3682
|
-
'ERR_EXPO_HARMONY_INVALID_LISTENER',
|
|
3683
|
-
'${capability.packageName} expected watchPositionAsync to receive a callback.',
|
|
3684
|
-
);
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
try {
|
|
3688
|
-
const watchResult = await invokeNative(
|
|
3689
|
-
'startWatchPosition',
|
|
3690
|
-
'watchPositionAsync',
|
|
3691
|
-
options ?? {},
|
|
3692
|
-
null,
|
|
3693
|
-
);
|
|
3694
|
-
const watchId =
|
|
3695
|
-
typeof watchResult?.watchId === 'number' ? watchResult.watchId : Number(Date.now());
|
|
3696
|
-
|
|
3697
|
-
if (watchResult?.initialLocation) {
|
|
3698
|
-
callback(normalizeLocationObject(watchResult.initialLocation));
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3701
|
-
return createSubscription(() => {
|
|
3702
|
-
void invokeNative(
|
|
3703
|
-
'stopWatchPosition',
|
|
3704
|
-
'Location.watchPositionAsync.remove',
|
|
3705
|
-
watchId,
|
|
3706
|
-
).catch(() => {});
|
|
3707
|
-
});
|
|
3708
|
-
} catch (error) {
|
|
3709
|
-
if (typeof errorHandler === 'function') {
|
|
3710
|
-
errorHandler(error);
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3713
|
-
throw error;
|
|
3714
|
-
}
|
|
3715
|
-
},
|
|
3716
|
-
async watchHeadingAsync(callback) {
|
|
3717
|
-
if (typeof callback !== 'function') {
|
|
3718
|
-
throw createError(
|
|
3719
|
-
'ERR_EXPO_HARMONY_INVALID_LISTENER',
|
|
3720
|
-
'${capability.packageName} expected watchHeadingAsync to receive a callback.',
|
|
3721
|
-
);
|
|
3722
|
-
}
|
|
3723
|
-
|
|
3724
|
-
const watchResult = await invokeNative(
|
|
3725
|
-
'startWatchHeading',
|
|
3726
|
-
'watchHeadingAsync',
|
|
3727
|
-
null,
|
|
3728
|
-
);
|
|
3729
|
-
const watchId =
|
|
3730
|
-
typeof watchResult?.watchId === 'number' ? watchResult.watchId : Number(Date.now());
|
|
3731
|
-
|
|
3732
|
-
if (watchResult?.initialHeading) {
|
|
3733
|
-
callback(normalizeHeadingObject(watchResult.initialHeading));
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
return createSubscription(() => {
|
|
3737
|
-
void invokeNative(
|
|
3738
|
-
'stopWatchHeading',
|
|
3739
|
-
'Location.watchHeadingAsync.remove',
|
|
3740
|
-
watchId,
|
|
3741
|
-
).catch(() => {});
|
|
3742
|
-
});
|
|
3743
|
-
},
|
|
3744
|
-
async getHeadingAsync() {
|
|
3745
|
-
return normalizeHeadingObject(
|
|
3746
|
-
await invokeNative('getHeading', 'getHeadingAsync'),
|
|
3747
|
-
);
|
|
3748
|
-
},
|
|
3749
|
-
};
|
|
3750
|
-
`;
|
|
3751
|
-
}
|
|
3752
|
-
function renderExpoImagePickerPreviewShim(capability) {
|
|
3753
|
-
return `'use strict';
|
|
3754
|
-
|
|
3755
|
-
const { CodedError } = require('expo-modules-core');
|
|
3756
|
-
|
|
3757
|
-
const PREVIEW_MESSAGE =
|
|
3758
|
-
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but picker and camera flows still need device-side validation.';
|
|
3759
|
-
|
|
3760
|
-
function createPreviewError(operationName) {
|
|
3761
|
-
return new CodedError(
|
|
3762
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3763
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3764
|
-
);
|
|
3765
|
-
}
|
|
3766
|
-
|
|
3767
|
-
async function unavailable(operationName) {
|
|
3768
|
-
throw createPreviewError(operationName);
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
module.exports = {
|
|
3772
|
-
MediaTypeOptions: {
|
|
3773
|
-
All: 'All',
|
|
3774
|
-
Images: 'Images',
|
|
3775
|
-
Videos: 'Videos',
|
|
3776
|
-
},
|
|
3777
|
-
UIImagePickerPresentationStyle: {
|
|
3778
|
-
AUTOMATIC: 'automatic',
|
|
3779
|
-
FULL_SCREEN: 'fullScreen',
|
|
3780
|
-
PAGE_SHEET: 'pageSheet',
|
|
3781
|
-
FORM_SHEET: 'formSheet',
|
|
3782
|
-
CURRENT_CONTEXT: 'currentContext',
|
|
3783
|
-
OVER_FULL_SCREEN: 'overFullScreen',
|
|
3784
|
-
},
|
|
3785
|
-
requestCameraPermissionsAsync() {
|
|
3786
|
-
return unavailable('requestCameraPermissionsAsync');
|
|
3787
|
-
},
|
|
3788
|
-
requestMediaLibraryPermissionsAsync() {
|
|
3789
|
-
return unavailable('requestMediaLibraryPermissionsAsync');
|
|
3790
|
-
},
|
|
3791
|
-
getCameraPermissionsAsync() {
|
|
3792
|
-
return unavailable('getCameraPermissionsAsync');
|
|
3793
|
-
},
|
|
3794
|
-
getMediaLibraryPermissionsAsync() {
|
|
3795
|
-
return unavailable('getMediaLibraryPermissionsAsync');
|
|
3796
|
-
},
|
|
3797
|
-
launchCameraAsync(options) {
|
|
3798
|
-
return unavailable('launchCameraAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3799
|
-
},
|
|
3800
|
-
launchImageLibraryAsync(options) {
|
|
3801
|
-
return unavailable('launchImageLibraryAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3802
|
-
},
|
|
3803
|
-
};
|
|
3804
|
-
`;
|
|
3805
|
-
}
|
|
3806
|
-
function renderExpoLocationPreviewShim(capability) {
|
|
3807
|
-
return `'use strict';
|
|
3808
|
-
|
|
3809
|
-
const { CodedError } = require('expo-modules-core');
|
|
3810
|
-
|
|
3811
|
-
const PREVIEW_MESSAGE =
|
|
3812
|
-
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but foreground permission, current-position, and watch flows still need device-side validation.';
|
|
3813
|
-
const DEFAULT_PERMISSION_RESPONSE = {
|
|
3814
|
-
status: 'undetermined',
|
|
3815
|
-
granted: false,
|
|
3816
|
-
canAskAgain: true,
|
|
3817
|
-
expires: 'never',
|
|
3818
|
-
};
|
|
3819
|
-
|
|
3820
|
-
function createPreviewError(operationName) {
|
|
3821
|
-
return new CodedError(
|
|
3822
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3823
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3824
|
-
);
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
async function unavailable(operationName) {
|
|
3828
|
-
throw createPreviewError(operationName);
|
|
3829
|
-
}
|
|
3830
|
-
|
|
3831
|
-
function getPermissionResponse() {
|
|
3832
|
-
return { ...DEFAULT_PERMISSION_RESPONSE };
|
|
3833
|
-
}
|
|
3834
|
-
|
|
3835
|
-
module.exports = {
|
|
3836
|
-
Accuracy: {
|
|
3837
|
-
Lowest: 1,
|
|
3838
|
-
Low: 2,
|
|
3839
|
-
Balanced: 3,
|
|
3840
|
-
High: 4,
|
|
3841
|
-
Highest: 5,
|
|
3842
|
-
BestForNavigation: 6,
|
|
3843
|
-
},
|
|
3844
|
-
PermissionStatus: {
|
|
3845
|
-
DENIED: 'denied',
|
|
3846
|
-
GRANTED: 'granted',
|
|
3847
|
-
UNDETERMINED: 'undetermined',
|
|
3848
|
-
},
|
|
3849
|
-
requestForegroundPermissionsAsync() {
|
|
3850
|
-
return unavailable('requestForegroundPermissionsAsync');
|
|
3851
|
-
},
|
|
3852
|
-
getForegroundPermissionsAsync() {
|
|
3853
|
-
return Promise.resolve(getPermissionResponse());
|
|
3854
|
-
},
|
|
3855
|
-
requestBackgroundPermissionsAsync() {
|
|
3856
|
-
return unavailable('requestBackgroundPermissionsAsync');
|
|
3857
|
-
},
|
|
3858
|
-
getBackgroundPermissionsAsync() {
|
|
3859
|
-
return Promise.resolve(getPermissionResponse());
|
|
3860
|
-
},
|
|
3861
|
-
hasServicesEnabledAsync() {
|
|
3862
|
-
return Promise.resolve(false);
|
|
3863
|
-
},
|
|
3864
|
-
getCurrentPositionAsync(options) {
|
|
3865
|
-
return unavailable('getCurrentPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3866
|
-
},
|
|
3867
|
-
getLastKnownPositionAsync(options) {
|
|
3868
|
-
return unavailable('getLastKnownPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3869
|
-
},
|
|
3870
|
-
watchPositionAsync(options) {
|
|
3871
|
-
return unavailable('watchPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3872
|
-
},
|
|
3873
|
-
geocodeAsync(address) {
|
|
3874
|
-
return unavailable('geocodeAsync(' + JSON.stringify(address ?? null) + ')');
|
|
3875
|
-
},
|
|
3876
|
-
reverseGeocodeAsync(location) {
|
|
3877
|
-
return unavailable('reverseGeocodeAsync(' + JSON.stringify(location ?? null) + ')');
|
|
3878
|
-
},
|
|
3879
|
-
};
|
|
3880
|
-
`;
|
|
3881
|
-
}
|
|
3882
|
-
function renderExpoCameraHarmonyAdapterShim(capability) {
|
|
3883
|
-
return `'use strict';
|
|
3884
|
-
|
|
3885
|
-
const React = require('react');
|
|
3886
|
-
const { TurboModuleRegistry, requireNativeComponent } = require('react-native');
|
|
3887
|
-
const { CodedError } = require('expo-modules-core');
|
|
3888
|
-
|
|
3889
|
-
const NATIVE_MODULE_NAME = 'ExpoHarmonyCamera';
|
|
3890
|
-
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3891
|
-
const NativeCameraView = requireNativeComponent('ExpoHarmonyCameraView');
|
|
3892
|
-
const DEFAULT_PERMISSION_RESPONSE = {
|
|
3893
|
-
status: 'undetermined',
|
|
3894
|
-
granted: false,
|
|
3895
|
-
canAskAgain: true,
|
|
3896
|
-
expires: 'never',
|
|
3897
|
-
};
|
|
3898
|
-
let nextCameraViewId = 1;
|
|
3899
|
-
|
|
3900
|
-
function createError(code, message) {
|
|
3901
|
-
return new CodedError(code, message);
|
|
3902
|
-
}
|
|
3903
|
-
|
|
3904
|
-
function createUnsupportedError(operationName) {
|
|
3905
|
-
return createError(
|
|
3906
|
-
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
3907
|
-
'${capability.packageName} does not implement ' + operationName + ' on HarmonyOS yet.',
|
|
3908
|
-
);
|
|
3909
|
-
}
|
|
3910
|
-
|
|
3911
|
-
function requireNativeModule(operationName) {
|
|
3912
|
-
if (NATIVE_MODULE) {
|
|
3913
|
-
return NATIVE_MODULE;
|
|
3914
|
-
}
|
|
3915
|
-
|
|
3916
|
-
throw createError(
|
|
3917
|
-
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3918
|
-
'${capability.packageName} expected the ' +
|
|
3919
|
-
NATIVE_MODULE_NAME +
|
|
3920
|
-
' TurboModule to be registered, but it was missing while running ' +
|
|
3921
|
-
operationName +
|
|
3922
|
-
'.',
|
|
3923
|
-
);
|
|
3924
|
-
}
|
|
3925
|
-
|
|
3926
|
-
function normalizeNativeError(error) {
|
|
3927
|
-
if (error instanceof Error) {
|
|
3928
|
-
return error;
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
if (error && typeof error === 'object') {
|
|
3932
|
-
const code =
|
|
3933
|
-
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3934
|
-
? String(error.code)
|
|
3935
|
-
: null;
|
|
3936
|
-
const message =
|
|
3937
|
-
typeof error.message === 'string' && error.message.length > 0
|
|
3938
|
-
? error.message
|
|
3939
|
-
: typeof error.name === 'string' && error.name.length > 0
|
|
3940
|
-
? error.name
|
|
3941
|
-
: JSON.stringify(error);
|
|
3942
|
-
|
|
3943
|
-
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3944
|
-
}
|
|
3945
|
-
|
|
3946
|
-
return new Error(String(error));
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
async function invokeNative(methodName, operationName, ...args) {
|
|
3950
|
-
try {
|
|
3951
|
-
return await requireNativeModule(operationName)[methodName](...args);
|
|
3952
|
-
} catch (error) {
|
|
3953
|
-
throw normalizeNativeError(error);
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
function normalizePermissionResponse(permissionResponse) {
|
|
3958
|
-
return {
|
|
3959
|
-
status: permissionResponse?.status ?? DEFAULT_PERMISSION_RESPONSE.status,
|
|
3960
|
-
granted: permissionResponse?.granted === true,
|
|
3961
|
-
canAskAgain: permissionResponse?.canAskAgain !== false,
|
|
3962
|
-
expires: permissionResponse?.expires ?? DEFAULT_PERMISSION_RESPONSE.expires,
|
|
3963
|
-
};
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
function normalizeCameraFacing(facing) {
|
|
3967
|
-
return facing === 'front' ? 'front' : 'back';
|
|
3968
|
-
}
|
|
3969
|
-
|
|
3970
|
-
const CameraView = React.forwardRef(function ExpoHarmonyCameraView(props, ref) {
|
|
3971
|
-
const viewIdRef = React.useRef(null);
|
|
3972
|
-
|
|
3973
|
-
if (!viewIdRef.current) {
|
|
3974
|
-
viewIdRef.current = 'expo-harmony-camera-view-' + String(nextCameraViewId++);
|
|
3975
|
-
}
|
|
3976
|
-
|
|
3977
|
-
const viewId = viewIdRef.current;
|
|
3978
|
-
|
|
3979
|
-
React.useEffect(() => {
|
|
3980
|
-
void invokeNative('createPreview', 'CameraView.mount', {
|
|
3981
|
-
viewId,
|
|
3982
|
-
facing: normalizeCameraFacing(props.facing),
|
|
3983
|
-
mode: props.mode ?? 'picture',
|
|
3984
|
-
}).catch(() => {});
|
|
3985
|
-
|
|
3986
|
-
return () => {
|
|
3987
|
-
void invokeNative('disposePreview', 'CameraView.unmount', {
|
|
3988
|
-
viewId,
|
|
3989
|
-
}).catch(() => {});
|
|
3990
|
-
};
|
|
3991
|
-
}, [props.facing, props.mode, viewId]);
|
|
3992
|
-
|
|
3993
|
-
React.useImperativeHandle(
|
|
3994
|
-
ref,
|
|
3995
|
-
() => ({
|
|
3996
|
-
async takePictureAsync(options) {
|
|
3997
|
-
return invokeNative('takePicture', 'CameraView.takePictureAsync', {
|
|
3998
|
-
viewId,
|
|
3999
|
-
cameraType: normalizeCameraFacing(props.facing),
|
|
4000
|
-
...options,
|
|
4001
|
-
});
|
|
4002
|
-
},
|
|
4003
|
-
async pausePreview() {
|
|
4004
|
-
return invokeNative('pausePreview', 'CameraView.pausePreview', {
|
|
4005
|
-
viewId,
|
|
4006
|
-
});
|
|
4007
|
-
},
|
|
4008
|
-
async resumePreview() {
|
|
4009
|
-
return invokeNative('resumePreview', 'CameraView.resumePreview', {
|
|
4010
|
-
viewId,
|
|
4011
|
-
});
|
|
4012
|
-
},
|
|
4013
|
-
async getAvailablePictureSizesAsync() {
|
|
4014
|
-
throw createUnsupportedError('CameraView.getAvailablePictureSizesAsync');
|
|
4015
|
-
},
|
|
4016
|
-
async getAvailableLensesAsync() {
|
|
4017
|
-
throw createUnsupportedError('CameraView.getAvailableLensesAsync');
|
|
4018
|
-
},
|
|
4019
|
-
async recordAsync(options) {
|
|
4020
|
-
return invokeNative('startRecording', 'CameraView.recordAsync', {
|
|
4021
|
-
viewId,
|
|
4022
|
-
cameraType: normalizeCameraFacing(props.facing),
|
|
4023
|
-
...options,
|
|
4024
|
-
});
|
|
4025
|
-
},
|
|
4026
|
-
async stopRecording() {
|
|
4027
|
-
return invokeNative('stopRecording', 'CameraView.stopRecording', {
|
|
4028
|
-
viewId,
|
|
4029
|
-
});
|
|
4030
|
-
},
|
|
4031
|
-
async toggleRecordingAsync(options) {
|
|
4032
|
-
return invokeNative('toggleRecording', 'CameraView.toggleRecordingAsync', {
|
|
4033
|
-
viewId,
|
|
4034
|
-
cameraType: normalizeCameraFacing(props.facing),
|
|
4035
|
-
...options,
|
|
4036
|
-
});
|
|
4037
|
-
},
|
|
4038
|
-
}),
|
|
4039
|
-
[props.facing, viewId],
|
|
4040
|
-
);
|
|
4041
|
-
|
|
4042
|
-
return React.createElement(NativeCameraView, {
|
|
4043
|
-
...props,
|
|
4044
|
-
viewId,
|
|
4045
|
-
facing: normalizeCameraFacing(props.facing),
|
|
4046
|
-
accessibilityLabel: props.accessibilityLabel ?? 'Expo Harmony embedded camera preview',
|
|
4047
|
-
style: [
|
|
4048
|
-
{
|
|
4049
|
-
minHeight: 220,
|
|
4050
|
-
overflow: 'hidden',
|
|
4051
|
-
backgroundColor: '#111827',
|
|
4052
|
-
},
|
|
4053
|
-
props.style,
|
|
4054
|
-
],
|
|
4055
|
-
});
|
|
4056
|
-
});
|
|
4057
|
-
|
|
4058
|
-
CameraView.displayName = 'ExpoHarmonyCameraView';
|
|
4059
|
-
|
|
4060
|
-
async function getCameraPermissionsAsync() {
|
|
4061
|
-
return normalizePermissionResponse(
|
|
4062
|
-
await invokeNative('getCameraPermissionStatus', 'getCameraPermissionsAsync'),
|
|
4063
|
-
);
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
|
-
async function requestCameraPermissionsAsync() {
|
|
4067
|
-
return normalizePermissionResponse(
|
|
4068
|
-
await invokeNative('requestCameraPermission', 'requestCameraPermissionsAsync'),
|
|
4069
|
-
);
|
|
4070
|
-
}
|
|
4071
|
-
|
|
4072
|
-
async function getMicrophonePermissionsAsync() {
|
|
4073
|
-
return normalizePermissionResponse(
|
|
4074
|
-
await invokeNative('getMicrophonePermissionStatus', 'getMicrophonePermissionsAsync'),
|
|
4075
|
-
);
|
|
4076
|
-
}
|
|
4077
|
-
|
|
4078
|
-
async function requestMicrophonePermissionsAsync() {
|
|
4079
|
-
return normalizePermissionResponse(
|
|
4080
|
-
await invokeNative('requestMicrophonePermission', 'requestMicrophonePermissionsAsync'),
|
|
4081
|
-
);
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
|
-
module.exports = {
|
|
4085
|
-
CameraType: {
|
|
4086
|
-
front: 'front',
|
|
4087
|
-
back: 'back',
|
|
4088
|
-
},
|
|
4089
|
-
FlashMode: {
|
|
4090
|
-
off: 'off',
|
|
4091
|
-
on: 'on',
|
|
4092
|
-
auto: 'auto',
|
|
4093
|
-
torch: 'torch',
|
|
4094
|
-
},
|
|
4095
|
-
CameraView,
|
|
4096
|
-
Camera: {
|
|
4097
|
-
CameraType: {
|
|
4098
|
-
front: 'front',
|
|
4099
|
-
back: 'back',
|
|
4100
|
-
},
|
|
4101
|
-
Constants: {
|
|
4102
|
-
Type: {
|
|
4103
|
-
front: 'front',
|
|
4104
|
-
back: 'back',
|
|
4105
|
-
},
|
|
4106
|
-
FlashMode: {
|
|
4107
|
-
off: 'off',
|
|
4108
|
-
on: 'on',
|
|
4109
|
-
auto: 'auto',
|
|
4110
|
-
torch: 'torch',
|
|
4111
|
-
},
|
|
4112
|
-
},
|
|
4113
|
-
getCameraPermissionsAsync,
|
|
4114
|
-
requestCameraPermissionsAsync,
|
|
4115
|
-
getMicrophonePermissionsAsync,
|
|
4116
|
-
requestMicrophonePermissionsAsync,
|
|
4117
|
-
},
|
|
4118
|
-
getCameraPermissionsAsync,
|
|
4119
|
-
requestCameraPermissionsAsync,
|
|
4120
|
-
getMicrophonePermissionsAsync,
|
|
4121
|
-
requestMicrophonePermissionsAsync,
|
|
4122
|
-
async scanFromURLAsync() {
|
|
4123
|
-
throw createUnsupportedError('scanFromURLAsync');
|
|
4124
|
-
},
|
|
4125
|
-
};
|
|
4126
|
-
`;
|
|
4127
|
-
}
|
|
4128
|
-
function renderExpoCameraPreviewShim(capability) {
|
|
4129
|
-
return `'use strict';
|
|
4130
|
-
|
|
4131
|
-
const React = require('react');
|
|
4132
|
-
const { Text, View } = require('react-native');
|
|
4133
|
-
const { CodedError } = require('expo-modules-core');
|
|
4134
|
-
|
|
4135
|
-
const PREVIEW_MESSAGE =
|
|
4136
|
-
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, and a managed preview surface can render, but device-side camera permission and capture flows still need validation.';
|
|
4137
|
-
const DEFAULT_PERMISSION_RESPONSE = {
|
|
4138
|
-
status: 'undetermined',
|
|
4139
|
-
granted: false,
|
|
4140
|
-
canAskAgain: true,
|
|
4141
|
-
expires: 'never',
|
|
4142
|
-
};
|
|
4143
|
-
|
|
4144
|
-
function createPreviewError(operationName) {
|
|
4145
|
-
return new CodedError(
|
|
4146
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
4147
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
4148
|
-
);
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
async function unavailable(operationName) {
|
|
4152
|
-
throw createPreviewError(operationName);
|
|
4153
|
-
}
|
|
4154
|
-
|
|
4155
|
-
function getPermissionResponse() {
|
|
4156
|
-
return { ...DEFAULT_PERMISSION_RESPONSE };
|
|
4157
|
-
}
|
|
4158
|
-
|
|
4159
|
-
const CameraView = React.forwardRef(function ExpoHarmonyCameraPreview(props, ref) {
|
|
4160
|
-
React.useImperativeHandle(ref, () => ({
|
|
4161
|
-
takePictureAsync(options) {
|
|
4162
|
-
return unavailable('CameraView.takePictureAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
4163
|
-
},
|
|
4164
|
-
pausePreview() {
|
|
4165
|
-
return unavailable('CameraView.pausePreview()');
|
|
4166
|
-
},
|
|
4167
|
-
resumePreview() {
|
|
4168
|
-
return unavailable('CameraView.resumePreview()');
|
|
4169
|
-
},
|
|
4170
|
-
}));
|
|
4171
|
-
|
|
4172
|
-
return React.createElement(
|
|
4173
|
-
View,
|
|
4174
|
-
{
|
|
4175
|
-
style: [
|
|
4176
|
-
{
|
|
4177
|
-
minHeight: 220,
|
|
4178
|
-
alignItems: 'center',
|
|
4179
|
-
justifyContent: 'center',
|
|
4180
|
-
borderRadius: 20,
|
|
4181
|
-
borderWidth: 1,
|
|
4182
|
-
borderStyle: 'dashed',
|
|
4183
|
-
borderColor: '#14b8a6',
|
|
4184
|
-
backgroundColor: '#ccfbf1',
|
|
4185
|
-
padding: 20,
|
|
4186
|
-
},
|
|
4187
|
-
props.style,
|
|
4188
|
-
],
|
|
4189
|
-
accessibilityLabel: 'Expo Harmony preview camera surface',
|
|
4190
|
-
},
|
|
4191
|
-
React.createElement(
|
|
4192
|
-
Text,
|
|
4193
|
-
{
|
|
4194
|
-
style: {
|
|
4195
|
-
color: '#0f766e',
|
|
4196
|
-
fontSize: 14,
|
|
4197
|
-
fontWeight: '600',
|
|
4198
|
-
textAlign: 'center',
|
|
4199
|
-
},
|
|
4200
|
-
},
|
|
4201
|
-
'Expo Harmony preview camera surface',
|
|
4202
|
-
),
|
|
4203
|
-
);
|
|
4204
|
-
});
|
|
4205
|
-
|
|
4206
|
-
CameraView.displayName = 'ExpoHarmonyCameraPreview';
|
|
4207
|
-
|
|
4208
|
-
module.exports = {
|
|
4209
|
-
CameraType: {
|
|
4210
|
-
front: 'front',
|
|
4211
|
-
back: 'back',
|
|
4212
|
-
},
|
|
4213
|
-
FlashMode: {
|
|
4214
|
-
off: 'off',
|
|
4215
|
-
on: 'on',
|
|
4216
|
-
auto: 'auto',
|
|
4217
|
-
torch: 'torch',
|
|
4218
|
-
},
|
|
4219
|
-
CameraView,
|
|
4220
|
-
requestCameraPermissionsAsync() {
|
|
4221
|
-
return unavailable('requestCameraPermissionsAsync');
|
|
4222
|
-
},
|
|
4223
|
-
getCameraPermissionsAsync() {
|
|
4224
|
-
return Promise.resolve(getPermissionResponse());
|
|
4225
|
-
},
|
|
4226
|
-
requestMicrophonePermissionsAsync() {
|
|
4227
|
-
return unavailable('requestMicrophonePermissionsAsync');
|
|
4228
|
-
},
|
|
4229
|
-
getMicrophonePermissionsAsync() {
|
|
4230
|
-
return Promise.resolve(getPermissionResponse());
|
|
4231
|
-
},
|
|
4232
|
-
Camera: {
|
|
4233
|
-
getCameraPermissionsAsync() {
|
|
4234
|
-
return Promise.resolve(getPermissionResponse());
|
|
4235
|
-
},
|
|
4236
|
-
requestCameraPermissionsAsync() {
|
|
4237
|
-
return unavailable('Camera.requestCameraPermissionsAsync');
|
|
4238
|
-
},
|
|
4239
|
-
getMicrophonePermissionsAsync() {
|
|
4240
|
-
return Promise.resolve(getPermissionResponse());
|
|
4241
|
-
},
|
|
4242
|
-
requestMicrophonePermissionsAsync() {
|
|
4243
|
-
return unavailable('Camera.requestMicrophonePermissionsAsync');
|
|
4244
|
-
},
|
|
4245
|
-
},
|
|
4246
|
-
scanFromURLAsync() {
|
|
4247
|
-
return unavailable('scanFromURLAsync');
|
|
4248
|
-
},
|
|
4249
|
-
};
|
|
4250
|
-
`;
|
|
4251
|
-
}
|
|
4252
|
-
function renderExpoModulesCoreHarmonyShim(expoConfig, identifiers) {
|
|
4253
|
-
const embeddedExpoConfig = buildExpoConfigForShim(expoConfig, identifiers);
|
|
4254
|
-
const serializedExpoConfig = JSON.stringify(embeddedExpoConfig, null, 2);
|
|
4255
|
-
const primaryScheme = getPrimarySchemeForShim(embeddedExpoConfig, identifiers);
|
|
4256
|
-
const linkingUri = primaryScheme ? `${primaryScheme}://` : null;
|
|
4257
|
-
const serializedLinkingUri = JSON.stringify(linkingUri);
|
|
4258
|
-
return `'use strict';
|
|
4259
|
-
|
|
4260
|
-
const { Linking, Platform } = require('react-native');
|
|
4261
|
-
|
|
4262
|
-
const embeddedExpoConfig = ${serializedExpoConfig};
|
|
4263
|
-
const nativeModules = Object.create(null);
|
|
4264
|
-
|
|
4265
|
-
class EventSubscription {
|
|
4266
|
-
constructor(remove) {
|
|
4267
|
-
this._remove = remove;
|
|
4268
|
-
}
|
|
4269
|
-
|
|
4270
|
-
remove() {
|
|
4271
|
-
if (!this._remove) {
|
|
4272
|
-
return;
|
|
4273
|
-
}
|
|
4274
|
-
|
|
4275
|
-
const remove = this._remove;
|
|
4276
|
-
this._remove = null;
|
|
4277
|
-
remove();
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
4280
|
-
|
|
4281
|
-
class EventEmitter {
|
|
4282
|
-
constructor() {
|
|
4283
|
-
this._listeners = new Map();
|
|
4284
|
-
}
|
|
4285
|
-
|
|
4286
|
-
addListener(eventName, listener) {
|
|
4287
|
-
const listeners = this._listeners.get(eventName) ?? new Set();
|
|
4288
|
-
listeners.add(listener);
|
|
4289
|
-
this._listeners.set(eventName, listeners);
|
|
4290
|
-
|
|
4291
|
-
return new EventSubscription(() => {
|
|
4292
|
-
listeners.delete(listener);
|
|
4293
|
-
|
|
4294
|
-
if (listeners.size === 0) {
|
|
4295
|
-
this._listeners.delete(eventName);
|
|
4296
|
-
}
|
|
4297
|
-
});
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4300
|
-
removeAllListeners(eventName) {
|
|
4301
|
-
if (typeof eventName === 'string') {
|
|
4302
|
-
this._listeners.delete(eventName);
|
|
4303
|
-
return;
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
|
-
this._listeners.clear();
|
|
4307
|
-
}
|
|
4308
|
-
|
|
4309
|
-
emit(eventName, payload) {
|
|
4310
|
-
const listeners = this._listeners.get(eventName);
|
|
4311
|
-
|
|
4312
|
-
if (!listeners) {
|
|
4313
|
-
return;
|
|
4314
|
-
}
|
|
4315
|
-
|
|
4316
|
-
for (const listener of listeners) {
|
|
4317
|
-
listener(payload);
|
|
4318
|
-
}
|
|
4319
|
-
}
|
|
4320
|
-
}
|
|
4321
|
-
|
|
4322
|
-
class LegacyEventEmitter extends EventEmitter {}
|
|
4323
|
-
|
|
4324
|
-
class NativeModule extends EventEmitter {}
|
|
4325
|
-
|
|
4326
|
-
class SharedObject {}
|
|
4327
|
-
|
|
4328
|
-
class SharedRef extends SharedObject {}
|
|
4329
|
-
|
|
4330
|
-
class CodedError extends Error {
|
|
4331
|
-
constructor(code, message) {
|
|
4332
|
-
super(message);
|
|
4333
|
-
this.code = code;
|
|
4334
|
-
this.name = 'CodedError';
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
|
-
class UnavailabilityError extends CodedError {
|
|
4339
|
-
constructor(moduleName, propertyName) {
|
|
4340
|
-
super(
|
|
4341
|
-
'ERR_UNAVAILABLE',
|
|
4342
|
-
propertyName
|
|
4343
|
-
? moduleName + '.' + propertyName + ' is not available on Harmony.'
|
|
4344
|
-
: moduleName + ' is not available on Harmony.',
|
|
4345
|
-
);
|
|
4346
|
-
this.name = 'UnavailabilityError';
|
|
4347
|
-
}
|
|
4348
|
-
}
|
|
4349
|
-
|
|
4350
|
-
class ExpoLinkingModule extends NativeModule {
|
|
4351
|
-
constructor(initialUrl) {
|
|
4352
|
-
super();
|
|
4353
|
-
this._currentUrl = initialUrl;
|
|
4354
|
-
}
|
|
4355
|
-
|
|
4356
|
-
getLinkingURL() {
|
|
4357
|
-
return this._currentUrl;
|
|
4358
|
-
}
|
|
4359
|
-
|
|
4360
|
-
_setCurrentUrl(url) {
|
|
4361
|
-
this._currentUrl = url;
|
|
4362
|
-
this.emit('onURLReceived', {
|
|
4363
|
-
url,
|
|
4364
|
-
});
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
|
|
4368
|
-
const expoLinkingModule = new ExpoLinkingModule(${serializedLinkingUri});
|
|
4369
|
-
|
|
4370
|
-
if (Linking?.addEventListener) {
|
|
4371
|
-
Linking.addEventListener('url', (event) => {
|
|
4372
|
-
expoLinkingModule._setCurrentUrl(event?.url ?? null);
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
|
|
4376
|
-
nativeModules.ExpoLinking = expoLinkingModule;
|
|
4377
|
-
nativeModules.ExponentConstants = {
|
|
4378
|
-
manifest: embeddedExpoConfig,
|
|
4379
|
-
appOwnership: null,
|
|
4380
|
-
executionEnvironment: 'standalone',
|
|
4381
|
-
experienceUrl: ${serializedLinkingUri},
|
|
4382
|
-
linkingUri: ${serializedLinkingUri},
|
|
4383
|
-
statusBarHeight: 0,
|
|
4384
|
-
systemVersion: 'HarmonyOS',
|
|
4385
|
-
platform: {
|
|
4386
|
-
android: embeddedExpoConfig.android ?? null,
|
|
4387
|
-
ios: embeddedExpoConfig.ios ?? null,
|
|
4388
|
-
web: null,
|
|
4389
|
-
},
|
|
4390
|
-
};
|
|
4391
|
-
nativeModules.ExpoAsset = {
|
|
4392
|
-
async downloadAsync(url) {
|
|
4393
|
-
return url;
|
|
4394
|
-
},
|
|
4395
|
-
};
|
|
4396
|
-
nativeModules.ExpoFetchModule = {
|
|
4397
|
-
NativeRequest: class NativeRequest {
|
|
4398
|
-
constructor(_response) {
|
|
4399
|
-
this._response = _response;
|
|
4400
|
-
}
|
|
4401
|
-
|
|
4402
|
-
async start() {
|
|
4403
|
-
throw new UnavailabilityError('ExpoFetchModule', 'NativeRequest.start');
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
|
-
cancel() {}
|
|
4407
|
-
},
|
|
4408
|
-
};
|
|
4409
|
-
|
|
4410
|
-
function requireOptionalNativeModule(name) {
|
|
4411
|
-
return nativeModules[name] ?? null;
|
|
4412
|
-
}
|
|
4413
|
-
|
|
4414
|
-
function requireNativeModule(name) {
|
|
4415
|
-
const nativeModule = requireOptionalNativeModule(name);
|
|
4416
|
-
|
|
4417
|
-
if (nativeModule) {
|
|
4418
|
-
return nativeModule;
|
|
4419
|
-
}
|
|
4420
|
-
|
|
4421
|
-
throw new UnavailabilityError(name);
|
|
4422
|
-
}
|
|
4423
|
-
|
|
4424
|
-
function requireNativeViewManager(name) {
|
|
4425
|
-
throw new UnavailabilityError(name, 'viewManager');
|
|
4426
|
-
}
|
|
4427
|
-
|
|
4428
|
-
function registerWebModule() {}
|
|
4429
|
-
|
|
4430
|
-
async function reloadAppAsync() {}
|
|
4431
|
-
|
|
4432
|
-
function installOnUIRuntime() {}
|
|
4433
|
-
|
|
4434
|
-
globalThis.expo = {
|
|
4435
|
-
...(globalThis.expo ?? {}),
|
|
4436
|
-
EventEmitter,
|
|
4437
|
-
LegacyEventEmitter,
|
|
4438
|
-
NativeModule,
|
|
4439
|
-
SharedObject,
|
|
4440
|
-
SharedRef,
|
|
4441
|
-
modules: {
|
|
4442
|
-
...(globalThis.expo?.modules ?? {}),
|
|
4443
|
-
...nativeModules,
|
|
4444
|
-
},
|
|
4445
|
-
};
|
|
4446
|
-
|
|
4447
|
-
module.exports = {
|
|
4448
|
-
Platform,
|
|
4449
|
-
CodedError,
|
|
4450
|
-
UnavailabilityError,
|
|
4451
|
-
EventEmitter,
|
|
4452
|
-
LegacyEventEmitter,
|
|
4453
|
-
NativeModule,
|
|
4454
|
-
SharedObject,
|
|
4455
|
-
SharedRef,
|
|
4456
|
-
requireNativeModule,
|
|
4457
|
-
requireOptionalNativeModule,
|
|
4458
|
-
requireNativeViewManager,
|
|
4459
|
-
registerWebModule,
|
|
4460
|
-
reloadAppAsync,
|
|
4461
|
-
installOnUIRuntime,
|
|
4462
|
-
};
|
|
4463
|
-
`;
|
|
4464
|
-
}
|
|
4465
|
-
function renderReactNativeSafeAreaContextHarmonyShim() {
|
|
4466
|
-
return `'use strict';
|
|
4467
|
-
|
|
4468
|
-
const React = require('react');
|
|
4469
|
-
const { Dimensions, View } = require('react-native');
|
|
4470
|
-
|
|
4471
|
-
function getWindowMetrics() {
|
|
4472
|
-
const metrics = Dimensions.get('window') ?? { width: 0, height: 0 };
|
|
4473
|
-
|
|
4474
|
-
return {
|
|
4475
|
-
frame: {
|
|
4476
|
-
x: 0,
|
|
4477
|
-
y: 0,
|
|
4478
|
-
width: typeof metrics.width === 'number' ? metrics.width : 0,
|
|
4479
|
-
height: typeof metrics.height === 'number' ? metrics.height : 0,
|
|
4480
|
-
},
|
|
4481
|
-
insets: {
|
|
4482
|
-
top: 0,
|
|
4483
|
-
right: 0,
|
|
4484
|
-
bottom: 0,
|
|
4485
|
-
left: 0,
|
|
4486
|
-
},
|
|
4487
|
-
};
|
|
4488
|
-
}
|
|
4489
|
-
|
|
4490
|
-
const initialWindowMetrics = getWindowMetrics();
|
|
4491
|
-
const initialWindowSafeAreaInsets = initialWindowMetrics.insets;
|
|
4492
|
-
const SafeAreaInsetsContext = React.createContext(initialWindowMetrics.insets);
|
|
4493
|
-
const SafeAreaFrameContext = React.createContext(initialWindowMetrics.frame);
|
|
4494
|
-
|
|
4495
|
-
function SafeAreaProvider({ children, initialMetrics = initialWindowMetrics, style }) {
|
|
4496
|
-
const metrics = initialMetrics ?? initialWindowMetrics;
|
|
4497
|
-
|
|
4498
|
-
return React.createElement(
|
|
4499
|
-
SafeAreaFrameContext.Provider,
|
|
4500
|
-
{ value: metrics.frame },
|
|
4501
|
-
React.createElement(
|
|
4502
|
-
SafeAreaInsetsContext.Provider,
|
|
4503
|
-
{ value: metrics.insets },
|
|
4504
|
-
React.createElement(View, { style: [{ flex: 1 }, style] }, children),
|
|
4505
|
-
),
|
|
4506
|
-
);
|
|
4507
|
-
}
|
|
4508
|
-
|
|
4509
|
-
function NativeSafeAreaProvider(props) {
|
|
4510
|
-
return React.createElement(SafeAreaProvider, props);
|
|
4511
|
-
}
|
|
4512
|
-
|
|
4513
|
-
function SafeAreaView({ children, style, ...rest }) {
|
|
4514
|
-
return React.createElement(View, { ...rest, style }, children);
|
|
4515
|
-
}
|
|
4516
|
-
|
|
4517
|
-
function SafeAreaListener({ children }) {
|
|
4518
|
-
return typeof children === 'function' ? children(initialWindowMetrics) : null;
|
|
4519
|
-
}
|
|
4520
|
-
|
|
4521
|
-
function useSafeAreaInsets() {
|
|
4522
|
-
return React.useContext(SafeAreaInsetsContext);
|
|
4523
|
-
}
|
|
4524
|
-
|
|
4525
|
-
function useSafeAreaFrame() {
|
|
4526
|
-
return React.useContext(SafeAreaFrameContext);
|
|
4527
|
-
}
|
|
4528
|
-
|
|
4529
|
-
function useSafeArea() {
|
|
4530
|
-
return useSafeAreaInsets();
|
|
4531
|
-
}
|
|
4532
|
-
|
|
4533
|
-
function withSafeAreaInsets(Component) {
|
|
4534
|
-
return React.forwardRef((props, ref) =>
|
|
4535
|
-
React.createElement(Component, {
|
|
4536
|
-
...props,
|
|
4537
|
-
ref,
|
|
4538
|
-
insets: useSafeAreaInsets(),
|
|
4539
|
-
}),
|
|
4540
|
-
);
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
module.exports = {
|
|
4544
|
-
EdgeInsets: undefined,
|
|
4545
|
-
initialWindowMetrics,
|
|
4546
|
-
initialWindowSafeAreaInsets,
|
|
4547
|
-
NativeSafeAreaProvider,
|
|
4548
|
-
SafeAreaConsumer: SafeAreaInsetsContext.Consumer,
|
|
4549
|
-
SafeAreaFrameContext,
|
|
4550
|
-
SafeAreaInsetsContext,
|
|
4551
|
-
SafeAreaListener,
|
|
4552
|
-
SafeAreaProvider,
|
|
4553
|
-
SafeAreaView,
|
|
4554
|
-
useSafeArea,
|
|
4555
|
-
useSafeAreaFrame,
|
|
4556
|
-
useSafeAreaInsets,
|
|
4557
|
-
withSafeAreaInsets,
|
|
4558
|
-
};
|
|
4559
|
-
`;
|
|
4560
|
-
}
|
|
4561
|
-
function renderHarmonyRuntimePrelude() {
|
|
4562
|
-
return `'use strict';
|
|
4563
|
-
|
|
4564
|
-
require('react-native/Libraries/Core/InitializeCore');
|
|
4565
|
-
|
|
4566
|
-
function requireReactNativeBaseViewConfigHarmony() {
|
|
4567
|
-
try {
|
|
4568
|
-
return require('react-native/Libraries/NativeComponent/BaseViewConfig.harmony');
|
|
4569
|
-
} catch (_error) {
|
|
4570
|
-
return null;
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
|
|
4574
|
-
function requireRnohBaseViewConfigHarmony() {
|
|
4575
|
-
try {
|
|
4576
|
-
return require('@react-native-oh/react-native-harmony/Libraries/NativeComponent/BaseViewConfig.harmony');
|
|
4577
|
-
} catch (_error) {
|
|
4578
|
-
return null;
|
|
4579
|
-
}
|
|
4580
|
-
}
|
|
4581
|
-
|
|
4582
|
-
function requireReactNativeBaseViewConfig() {
|
|
4583
|
-
try {
|
|
4584
|
-
return require('react-native/Libraries/NativeComponent/BaseViewConfig');
|
|
4585
|
-
} catch (_error) {
|
|
4586
|
-
return null;
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
function requireReactNativePlatformBaseViewConfig() {
|
|
4591
|
-
try {
|
|
4592
|
-
return require('react-native/Libraries/NativeComponent/PlatformBaseViewConfig');
|
|
4593
|
-
} catch (_error) {
|
|
4594
|
-
return null;
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
|
|
4598
|
-
function requireRnohBaseViewConfig() {
|
|
4599
|
-
try {
|
|
4600
|
-
return require('@react-native-oh/react-native-harmony/Libraries/NativeComponent/BaseViewConfig');
|
|
4601
|
-
} catch (_error) {
|
|
4602
|
-
return null;
|
|
4603
|
-
}
|
|
4604
|
-
}
|
|
4605
|
-
|
|
4606
|
-
function requireRnohPlatformBaseViewConfig() {
|
|
4607
|
-
try {
|
|
4608
|
-
return require('@react-native-oh/react-native-harmony/Libraries/NativeComponent/PlatformBaseViewConfig');
|
|
4609
|
-
} catch (_error) {
|
|
4610
|
-
return null;
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
|
|
4614
|
-
function patchNativeComponentViewConfigDefaults() {
|
|
4615
|
-
const harmonyBaseViewConfigModule =
|
|
4616
|
-
requireReactNativeBaseViewConfigHarmony() ?? requireRnohBaseViewConfigHarmony();
|
|
4617
|
-
const harmonyBaseViewConfig = harmonyBaseViewConfigModule?.default ?? harmonyBaseViewConfigModule;
|
|
4618
|
-
|
|
4619
|
-
if (!harmonyBaseViewConfig) {
|
|
4620
|
-
return;
|
|
4621
|
-
}
|
|
4622
|
-
|
|
4623
|
-
for (const moduleExports of [
|
|
4624
|
-
requireReactNativeBaseViewConfig(),
|
|
4625
|
-
requireReactNativePlatformBaseViewConfig(),
|
|
4626
|
-
requireRnohBaseViewConfig(),
|
|
4627
|
-
requireRnohPlatformBaseViewConfig(),
|
|
4628
|
-
]) {
|
|
4629
|
-
if (moduleExports && typeof moduleExports === 'object') {
|
|
4630
|
-
moduleExports.default = harmonyBaseViewConfig;
|
|
4631
|
-
}
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
|
|
4635
|
-
function installGlobalIfMissing(name, factory) {
|
|
4636
|
-
if (typeof globalThis[name] !== 'undefined') {
|
|
4637
|
-
return;
|
|
4638
|
-
}
|
|
4639
|
-
|
|
4640
|
-
const value = factory();
|
|
4641
|
-
|
|
4642
|
-
if (typeof value !== 'undefined') {
|
|
4643
|
-
globalThis[name] = value;
|
|
4644
|
-
}
|
|
4645
|
-
}
|
|
4646
|
-
|
|
4647
|
-
patchNativeComponentViewConfigDefaults();
|
|
4648
|
-
installGlobalIfMissing('FormData', () => require('react-native/Libraries/Network/FormData').default);
|
|
4649
|
-
installGlobalIfMissing('Blob', () => require('react-native/Libraries/Blob/Blob').default);
|
|
4650
|
-
installGlobalIfMissing('FileReader', () => require('react-native/Libraries/Blob/FileReader').default);
|
|
4651
|
-
`;
|
|
4652
|
-
}
|
|
4653
|
-
function isBinaryTemplate(relativePath) {
|
|
4654
|
-
return ['.png'].includes(path_1.default.extname(relativePath));
|
|
4655
|
-
}
|
|
4656
|
-
function contentsEqual(currentContents, nextContents, binary = false) {
|
|
4657
|
-
if (binary || Buffer.isBuffer(nextContents)) {
|
|
4658
|
-
return currentContents.equals(Buffer.isBuffer(nextContents) ? nextContents : Buffer.from(nextContents));
|
|
4659
|
-
}
|
|
4660
|
-
return currentContents.toString('utf8') === nextContents;
|
|
4661
|
-
}
|
|
4662
|
-
function sortRecordByKey(record) {
|
|
4663
|
-
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
4664
|
-
}
|
|
4665
|
-
function isEquivalentToolkitScript(scriptName, currentCommand, desiredCommand) {
|
|
4666
|
-
if (currentCommand === desiredCommand) {
|
|
4667
|
-
return true;
|
|
4668
|
-
}
|
|
4669
|
-
const compatibilityPatterns = {
|
|
4670
|
-
'harmony:doctor': /\bexpo-harmony(?:\.js)?\s+doctor\b/,
|
|
4671
|
-
'harmony:init': /\bexpo-harmony(?:\.js)?\s+init\b/,
|
|
4672
|
-
'harmony:sync-template': /\bexpo-harmony(?:\.js)?\s+sync-template\b/,
|
|
4673
|
-
'harmony:env': /\bexpo-harmony(?:\.js)?\s+env\b/,
|
|
4674
|
-
'harmony:bundle': /\bexpo-harmony(?:\.js)?\s+bundle\b/,
|
|
4675
|
-
'harmony:build:debug': /\bexpo-harmony(?:\.js)?\s+build-hap\b[\s\S]*--mode\s+debug\b/,
|
|
4676
|
-
'harmony:build:release': /\bexpo-harmony(?:\.js)?\s+build-hap\b[\s\S]*--mode\s+release\b/,
|
|
4677
|
-
};
|
|
4678
|
-
const compatibilityPattern = compatibilityPatterns[scriptName];
|
|
4679
|
-
return compatibilityPattern ? compatibilityPattern.test(currentCommand) : false;
|
|
4680
|
-
}
|
|
4681
|
-
function buildDesiredPackageScripts(packageJson) {
|
|
4682
|
-
return {
|
|
4683
|
-
...constants_1.DESIRED_PACKAGE_SCRIPTS,
|
|
4684
|
-
};
|
|
4685
|
-
}
|
|
4686
|
-
function buildDesiredPnpmOverrides(packageJson) {
|
|
4687
|
-
const overrides = {};
|
|
4688
|
-
for (const adapter of uiStack_1.UI_STACK_VALIDATED_ADAPTERS) {
|
|
4689
|
-
const declaredSpecifier = packageJson.dependencies?.[adapter.canonicalPackageName] ??
|
|
4690
|
-
packageJson.devDependencies?.[adapter.canonicalPackageName] ??
|
|
4691
|
-
packageJson.peerDependencies?.[adapter.canonicalPackageName];
|
|
4692
|
-
if (!declaredSpecifier) {
|
|
4693
|
-
continue;
|
|
4694
|
-
}
|
|
4695
|
-
const declaredRange = semver_1.default.validRange(declaredSpecifier);
|
|
4696
|
-
if (!declaredRange || !semver_1.default.satisfies(adapter.canonicalVersion, declaredRange)) {
|
|
4697
|
-
continue;
|
|
4698
|
-
}
|
|
4699
|
-
overrides[adapter.canonicalPackageName] = adapter.canonicalVersion;
|
|
4700
|
-
}
|
|
4701
|
-
return sortRecordByKey(overrides);
|
|
4702
|
-
}
|
|
4703
|
-
function usesExpoRouter(packageJson) {
|
|
4704
|
-
return (0, project_1.hasDeclaredDependency)(packageJson, 'expo-router');
|
|
4705
|
-
}
|
|
4706
|
-
function resolveHarmonyBundleEntryFile(packageJson) {
|
|
4707
|
-
return usesExpoRouter(packageJson) ? constants_1.HARMONY_ROUTER_ENTRY_FILENAME : 'index.js';
|
|
4708
|
-
}
|
|
4709
|
-
function renderRouterHarmonyEntry(identifiers) {
|
|
4710
|
-
return `require('./${constants_1.HARMONY_RUNTIME_PRELUDE_RELATIVE_PATH}');
|
|
4711
|
-
|
|
4712
|
-
const React = require('react');
|
|
4713
|
-
const { AppRegistry } = require('react-native');
|
|
4714
|
-
const { registerRootComponent } = require('expo');
|
|
4715
|
-
const { ExpoRoot } = require('expo-router');
|
|
4716
|
-
|
|
4717
|
-
const context = require.context('./app', true, /\\.[jt]sx?$/);
|
|
4718
|
-
|
|
4719
|
-
function App() {
|
|
4720
|
-
return React.createElement(ExpoRoot, {
|
|
4721
|
-
context,
|
|
4722
|
-
});
|
|
4723
|
-
}
|
|
4724
|
-
|
|
4725
|
-
registerRootComponent(App);
|
|
4726
|
-
AppRegistry.registerComponent(${JSON.stringify(identifiers.slug)}, () => App);
|
|
4727
|
-
`;
|
|
4728
|
-
}
|
|
4729
|
-
function buildExpoConfigForShim(expoConfig, identifiers) {
|
|
4730
|
-
const normalized = toSerializableValue(expoConfig);
|
|
4731
|
-
const config = normalized && typeof normalized === 'object' && !Array.isArray(normalized)
|
|
4732
|
-
? { ...normalized }
|
|
4733
|
-
: {};
|
|
4734
|
-
config.name = config.name ?? identifiers.appName;
|
|
4735
|
-
config.slug = config.slug ?? identifiers.slug;
|
|
4736
|
-
config.version = config.version ?? '1.0.0';
|
|
4737
|
-
if (!config.scheme) {
|
|
4738
|
-
config.scheme = getPrimarySchemeForShim(config, identifiers);
|
|
4739
|
-
}
|
|
4740
|
-
const android = config.android && typeof config.android === 'object' && !Array.isArray(config.android)
|
|
4741
|
-
? { ...config.android }
|
|
4742
|
-
: {};
|
|
4743
|
-
const ios = config.ios && typeof config.ios === 'object' && !Array.isArray(config.ios)
|
|
4744
|
-
? { ...config.ios }
|
|
4745
|
-
: {};
|
|
4746
|
-
android.package = android.package ?? identifiers.androidPackage ?? identifiers.bundleName;
|
|
4747
|
-
ios.bundleIdentifier =
|
|
4748
|
-
ios.bundleIdentifier ?? identifiers.iosBundleIdentifier ?? identifiers.bundleName;
|
|
4749
|
-
config.android = android;
|
|
4750
|
-
config.ios = ios;
|
|
4751
|
-
return config;
|
|
4752
|
-
}
|
|
4753
|
-
function getPrimarySchemeForShim(expoConfig, identifiers) {
|
|
4754
|
-
const scheme = expoConfig.scheme;
|
|
4755
|
-
if (typeof scheme === 'string' && scheme.trim().length > 0) {
|
|
4756
|
-
return scheme.trim();
|
|
4757
|
-
}
|
|
4758
|
-
if (Array.isArray(scheme)) {
|
|
4759
|
-
const firstScheme = scheme.find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
4760
|
-
if (firstScheme) {
|
|
4761
|
-
return firstScheme.trim();
|
|
4762
|
-
}
|
|
4763
|
-
}
|
|
4764
|
-
return identifiers.androidPackage ?? identifiers.iosBundleIdentifier ?? identifiers.bundleName;
|
|
4765
|
-
}
|
|
4766
|
-
function toSerializableValue(value) {
|
|
4767
|
-
if (value === null ||
|
|
4768
|
-
typeof value === 'string' ||
|
|
4769
|
-
typeof value === 'number' ||
|
|
4770
|
-
typeof value === 'boolean') {
|
|
4771
|
-
return value;
|
|
4772
|
-
}
|
|
4773
|
-
if (Array.isArray(value)) {
|
|
4774
|
-
return value
|
|
4775
|
-
.map((entry) => toSerializableValue(entry))
|
|
4776
|
-
.filter((entry) => entry !== undefined);
|
|
4777
|
-
}
|
|
4778
|
-
if (typeof value === 'object') {
|
|
4779
|
-
const result = {};
|
|
4780
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
4781
|
-
const serializedEntry = toSerializableValue(entry);
|
|
4782
|
-
if (serializedEntry !== undefined) {
|
|
4783
|
-
result[key] = serializedEntry;
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
return result;
|
|
4787
|
-
}
|
|
4788
|
-
return undefined;
|
|
4789
|
-
}
|
|
4790
|
-
function collectMetadataWarnings(previousManifest, previousToolkitConfig) {
|
|
4791
|
-
const warnings = [];
|
|
4792
|
-
if (previousManifest && previousManifest.templateVersion !== constants_1.TEMPLATE_VERSION) {
|
|
4793
|
-
warnings.push(`Existing manifest template version ${previousManifest.templateVersion} does not match current template ${constants_1.TEMPLATE_VERSION}. Sync will refresh managed metadata.`);
|
|
4794
|
-
}
|
|
4795
|
-
if (previousManifest && previousManifest.matrixId !== validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID) {
|
|
4796
|
-
warnings.push(`Existing manifest matrix ${previousManifest.matrixId ?? 'unknown'} does not match current matrix ${validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID}. Sync will refresh managed metadata.`);
|
|
4797
|
-
}
|
|
4798
|
-
if (previousToolkitConfig && previousToolkitConfig.templateVersion !== constants_1.TEMPLATE_VERSION) {
|
|
4799
|
-
warnings.push(`Existing toolkit-config template version ${previousToolkitConfig.templateVersion} does not match current template ${constants_1.TEMPLATE_VERSION}. Sync will refresh managed metadata.`);
|
|
4800
|
-
}
|
|
4801
|
-
if (previousToolkitConfig && previousToolkitConfig.matrixId !== validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID) {
|
|
4802
|
-
warnings.push(`Existing toolkit-config matrix ${previousToolkitConfig.matrixId ?? 'unknown'} does not match current matrix ${validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID}. Sync will refresh managed metadata.`);
|
|
4803
|
-
}
|
|
4804
|
-
return warnings;
|
|
4805
|
-
}
|
|
4806
|
-
function stabilizeToolkitConfigTimestamp(previousToolkitConfig, nextToolkitConfig) {
|
|
4807
|
-
if (!previousToolkitConfig) {
|
|
4808
|
-
return nextToolkitConfig;
|
|
4809
|
-
}
|
|
4810
|
-
const { generatedAt: previousGeneratedAt, ...previousComparable } = previousToolkitConfig;
|
|
4811
|
-
const { generatedAt: nextGeneratedAt, ...nextComparable } = nextToolkitConfig;
|
|
4812
|
-
if (JSON.stringify(previousComparable) === JSON.stringify(nextComparable)) {
|
|
4813
|
-
return {
|
|
4814
|
-
...nextToolkitConfig,
|
|
4815
|
-
generatedAt: previousGeneratedAt,
|
|
4816
|
-
};
|
|
4817
|
-
}
|
|
4818
|
-
return nextToolkitConfig;
|
|
4819
|
-
}
|
|
4820
|
-
function escapeRegExp(value) {
|
|
4821
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
4822
|
-
}
|