react-native-bspatch 1.1.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -32
- package/android/src/main/java/vn/reactnativebspatch/BsPatchModule.kt +15 -102
- package/android/src/main/java/vn/reactnativebspatch/OtaBundleResolver.kt +21 -1
- package/cpp/bsdiff/bspatch.c +1 -0
- package/ios/OtaBundleResolver.swift +16 -1
- package/ios/RNBsPatch.mm +61 -154
- package/lib/commonjs/NativeBsPatch.js.map +1 -1
- package/lib/commonjs/index.js +4 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ota/OtaProvider.js +26 -22
- package/lib/commonjs/ota/OtaProvider.js.map +1 -1
- package/lib/commonjs/ota/otaAssetResolver.js +8 -2
- package/lib/commonjs/ota/otaAssetResolver.js.map +1 -1
- package/lib/commonjs/ota/otaCore.js +89 -141
- package/lib/commonjs/ota/otaCore.js.map +1 -1
- package/lib/commonjs/ota/otaLogger.js +0 -2
- package/lib/commonjs/ota/otaLogger.js.map +1 -1
- package/lib/commonjs/ota/otaMetadata.js +18 -7
- package/lib/commonjs/ota/otaMetadata.js.map +1 -1
- package/lib/commonjs/ota/storage/GitHubRawStorage.js +3 -1
- package/lib/commonjs/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/commonjs/ota/types.js +1 -10
- package/lib/commonjs/ota/types.js.map +1 -1
- package/lib/module/NativeBsPatch.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ota/OtaProvider.js +26 -22
- package/lib/module/ota/OtaProvider.js.map +1 -1
- package/lib/module/ota/otaAssetResolver.js +8 -2
- package/lib/module/ota/otaAssetResolver.js.map +1 -1
- package/lib/module/ota/otaCore.js +88 -139
- package/lib/module/ota/otaCore.js.map +1 -1
- package/lib/module/ota/otaLogger.js +0 -2
- package/lib/module/ota/otaLogger.js.map +1 -1
- package/lib/module/ota/otaMetadata.js +18 -7
- package/lib/module/ota/otaMetadata.js.map +1 -1
- package/lib/module/ota/storage/GitHubRawStorage.js +3 -1
- package/lib/module/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/module/ota/types.js +0 -9
- package/lib/module/ota/types.js.map +1 -1
- package/lib/typescript/NativeBsPatch.d.ts +3 -15
- package/lib/typescript/NativeBsPatch.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ota/OtaProvider.d.ts.map +1 -1
- package/lib/typescript/ota/otaAssetResolver.d.ts.map +1 -1
- package/lib/typescript/ota/otaCore.d.ts +5 -42
- package/lib/typescript/ota/otaCore.d.ts.map +1 -1
- package/lib/typescript/ota/otaLogger.d.ts +1 -3
- package/lib/typescript/ota/otaLogger.d.ts.map +1 -1
- package/lib/typescript/ota/otaMetadata.d.ts +2 -2
- package/lib/typescript/ota/otaMetadata.d.ts.map +1 -1
- package/lib/typescript/ota/types.d.ts +18 -54
- package/lib/typescript/ota/types.d.ts.map +1 -1
- package/package.json +3 -6
- package/react-native-bspatch.podspec +0 -1
- package/src/NativeBsPatch.ts +3 -16
- package/src/index.ts +1 -1
- package/src/ota/OtaProvider.tsx +24 -23
- package/src/ota/otaAssetResolver.ts +5 -2
- package/src/ota/otaCore.ts +118 -176
- package/src/ota/otaLogger.ts +0 -2
- package/src/ota/otaMetadata.ts +18 -8
- package/src/ota/storage/GitHubRawStorage.ts +1 -1
- package/src/ota/types.ts +18 -57
- package/tools/build_ota.sh +117 -183
- package/tools/setup_project.js +33 -14
- package/tools/update_registry.js +24 -28
- package/tools/{upload_ota_custom.sh → upload_ota.sh} +33 -114
- package/lib/commonjs/crypto-js.d.js +0 -2
- package/lib/commonjs/crypto-js.d.js.map +0 -1
- package/lib/commonjs/ota/storage/CustomStorage.js +0 -160
- package/lib/commonjs/ota/storage/CustomStorage.js.map +0 -1
- package/lib/commonjs/ota/storage/GitHubReleaseStorage.js +0 -99
- package/lib/commonjs/ota/storage/GitHubReleaseStorage.js.map +0 -1
- package/lib/module/crypto-js.d.js +0 -2
- package/lib/module/crypto-js.d.js.map +0 -1
- package/lib/module/ota/storage/CustomStorage.js +0 -154
- package/lib/module/ota/storage/CustomStorage.js.map +0 -1
- package/lib/module/ota/storage/GitHubReleaseStorage.js +0 -93
- package/lib/module/ota/storage/GitHubReleaseStorage.js.map +0 -1
- package/lib/typescript/ota/storage/CustomStorage.d.ts +0 -12
- package/lib/typescript/ota/storage/CustomStorage.d.ts.map +0 -1
- package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts +0 -20
- package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts.map +0 -1
- package/src/crypto-js.d.ts +0 -1
- package/src/ota/storage/CustomStorage.ts +0 -166
- package/src/ota/storage/GitHubReleaseStorage.ts +0 -103
- package/tools/bsdiff +0 -0
package/tools/setup_project.js
CHANGED
|
@@ -50,6 +50,7 @@ function patchFile(filePath, label, patchFn) {
|
|
|
50
50
|
|
|
51
51
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
52
52
|
|
|
53
|
+
// Plain shell; escaped below for the pbxproj shellScript string literal
|
|
53
54
|
const XCODE_BUNDLE_SYNC_SNIPPET = [
|
|
54
55
|
'',
|
|
55
56
|
'# [react-native-bspatch] Sync compiled bundle to output/ for OTA builds',
|
|
@@ -57,20 +58,24 @@ const XCODE_BUNDLE_SYNC_SNIPPET = [
|
|
|
57
58
|
'BUNDLE_RESULT="$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/main.jsbundle"',
|
|
58
59
|
'',
|
|
59
60
|
'if [ -f "$BUNDLE_RESULT" ]; then',
|
|
60
|
-
' mkdir -p "$(dirname
|
|
61
|
+
' mkdir -p "$(dirname "$DEST_FILE")"',
|
|
61
62
|
' cp "$BUNDLE_RESULT" "$DEST_FILE"',
|
|
62
63
|
' echo "Bundle synced to $DEST_FILE"',
|
|
63
64
|
' if [ "$CONFIGURATION" = "Release" ]; then',
|
|
64
65
|
' APP_VER="${MARKETING_VERSION:-1.0.0}"',
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
66
|
+
' # v1 = the store build; never overwrite it (delete the file to re-capture)',
|
|
67
|
+
' for FLAVOR in development staging production; do',
|
|
68
|
+
' DEST_V1="$PROJECT_DIR/output/bundle_${APP_VER}_v1_ios_${FLAVOR}.hbc"',
|
|
69
|
+
' if [ ! -f "$DEST_V1" ]; then',
|
|
70
|
+
' cp "$BUNDLE_RESULT" "$DEST_V1"',
|
|
71
|
+
' echo "[react-native-bspatch] ✓ Auto-captured iOS base bundle v1 ($FLAVOR)"',
|
|
72
|
+
' fi',
|
|
73
|
+
' done',
|
|
69
74
|
' fi',
|
|
70
75
|
'else',
|
|
71
76
|
' echo "Bundle not found at $BUNDLE_RESULT (skipping sync)"',
|
|
72
77
|
'fi',
|
|
73
|
-
].join('\\n');
|
|
78
|
+
].join('\n').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
74
79
|
|
|
75
80
|
// The escaped version as it appears in pbxproj shellScript value
|
|
76
81
|
const XCODE_BUNDLE_SYNC_MARKER = 'react-native-bspatch] Sync compiled bundle to output/';
|
|
@@ -93,7 +98,10 @@ afterEvaluate {
|
|
|
93
98
|
def bundleDir = file("\$buildDir/generated/assets/react/\${variant.name}")
|
|
94
99
|
def bundleFile = file("\$bundleDir/index.android.bundle")
|
|
95
100
|
def outputDir = file("\$rootDir/output")
|
|
96
|
-
|
|
101
|
+
// v1 = the store build; never overwrite it (delete the file to re-capture).
|
|
102
|
+
// build_ota.sh runs this same bundle task for every OTA build.
|
|
103
|
+
def destFile = file("\$outputDir/bundle_\${versionName}_v1_android_\${flavorName}.hbc")
|
|
104
|
+
if (bundleFile.exists() && !destFile.exists()) {
|
|
97
105
|
outputDir.mkdirs()
|
|
98
106
|
def destName = "bundle_\${versionName}_v1_android_\${flavorName}.hbc"
|
|
99
107
|
copy {
|
|
@@ -113,8 +121,13 @@ afterEvaluate {
|
|
|
113
121
|
`;
|
|
114
122
|
|
|
115
123
|
function patchAppBuildGradle(content) {
|
|
116
|
-
|
|
117
|
-
return content.trimEnd() + '\n' + ANDROID_GRADLE_SNIPPET;
|
|
124
|
+
const markerIdx = content.indexOf('// ' + ANDROID_GRADLE_MARKER);
|
|
125
|
+
if (markerIdx === -1) return content.trimEnd() + '\n' + ANDROID_GRADLE_SNIPPET;
|
|
126
|
+
|
|
127
|
+
// Replace an older snippet in place: from the marker to the afterEvaluate block's closing brace
|
|
128
|
+
const end = findClosingParen(content, content.indexOf('{', content.indexOf('afterEvaluate', markerIdx)), '{', '}');
|
|
129
|
+
if (end === -1) return content;
|
|
130
|
+
return content.slice(0, markerIdx) + ANDROID_GRADLE_SNIPPET.trim() + content.slice(end + 1);
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
const IOS_IMPORT = 'import react_native_bspatch';
|
|
@@ -134,13 +147,13 @@ const IOS_BUNDLE_URL_OVERRIDE = `
|
|
|
134
147
|
const ANDROID_IMPORT = 'import vn.reactnativebspatch.OtaBundleResolver';
|
|
135
148
|
const ANDROID_JS_BUNDLE = 'jsBundleFilePath = OtaBundleResolver.resolve(applicationContext)';
|
|
136
149
|
|
|
137
|
-
function findClosingParen(source, openParenIdx) {
|
|
150
|
+
function findClosingParen(source, openParenIdx, open = '(', close = ')') {
|
|
138
151
|
let depth = 0;
|
|
139
152
|
for (let i = openParenIdx; i < source.length; i += 1) {
|
|
140
153
|
const char = source[i];
|
|
141
|
-
if (char ===
|
|
154
|
+
if (char === open) {
|
|
142
155
|
depth += 1;
|
|
143
|
-
} else if (char ===
|
|
156
|
+
} else if (char === close) {
|
|
144
157
|
depth -= 1;
|
|
145
158
|
if (depth === 0) {
|
|
146
159
|
return i;
|
|
@@ -153,8 +166,14 @@ function findClosingParen(source, openParenIdx) {
|
|
|
153
166
|
// ── iOS: project.pbxproj ─────────────────────────────────────────────────────
|
|
154
167
|
|
|
155
168
|
function patchPbxproj(content) {
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
if (content.includes(XCODE_BUNDLE_SYNC_SNIPPET)) return content;
|
|
170
|
+
|
|
171
|
+
// Older snippet present → replace it in place (it lacked the base bundle auto-capture)
|
|
172
|
+
const oldIdx = content.indexOf('\\n# [' + XCODE_BUNDLE_SYNC_MARKER);
|
|
173
|
+
if (oldIdx !== -1) {
|
|
174
|
+
const endIdx = content.indexOf('";', oldIdx);
|
|
175
|
+
return content.slice(0, oldIdx) + XCODE_BUNDLE_SYNC_SNIPPET + content.slice(endIdx);
|
|
176
|
+
}
|
|
158
177
|
|
|
159
178
|
// Search by phase name
|
|
160
179
|
const nameIdx = content.indexOf('name = "Bundle React Native code and images";');
|
package/tools/update_registry.js
CHANGED
|
@@ -85,35 +85,30 @@ if (newPatches.length > 0) {
|
|
|
85
85
|
// We merge patches by platform to avoid overwriting between Android and iOS
|
|
86
86
|
const otherPlatformPatches = flavorGroup.patches.filter(p => p.platform !== platform);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (signingKey)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
];
|
|
100
|
-
let patchData = null;
|
|
101
|
-
for (const pfc of patchFileCandidates) {
|
|
102
|
-
if (fs.existsSync(pfc)) {
|
|
103
|
-
patchData = fs.readFileSync(pfc);
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (patchData) {
|
|
108
|
-
const signer = crypto.createSign('SHA256');
|
|
109
|
-
signer.update(patchData);
|
|
110
|
-
signature = signer.sign(signingKey, 'base64');
|
|
111
|
-
console.log(` ✓ Signed patch ${p.file} with asymmetric private key`);
|
|
112
|
-
}
|
|
113
|
-
} catch (signErr) {
|
|
114
|
-
console.warn(` ⚠ Could not sign patch ${p.file}: ${signErr.message}`);
|
|
115
|
-
}
|
|
88
|
+
// Patches and bundles live next to the asset manifest (android/output or ios/output)
|
|
89
|
+
const outputDir = path.dirname(_assetManifestFile);
|
|
90
|
+
const fullBundleName = `bundle_${appVersion}_v${toOtaVersion}_${platform}_${flavor}.hbc`;
|
|
91
|
+
const manifestName = path.basename(_assetManifestFile);
|
|
92
|
+
|
|
93
|
+
const sign = (file) => {
|
|
94
|
+
if (!signingKey) return undefined;
|
|
95
|
+
const filePath = path.join(outputDir, file);
|
|
96
|
+
if (!fs.existsSync(filePath)) {
|
|
97
|
+
console.error(` ❌ Cannot sign ${file}: not found at ${filePath}`);
|
|
98
|
+
process.exit(1);
|
|
116
99
|
}
|
|
100
|
+
const signer = crypto.createSign('SHA256');
|
|
101
|
+
signer.update(fs.readFileSync(filePath));
|
|
102
|
+
console.log(` ✓ Signed ${file}`);
|
|
103
|
+
return signer.sign(signingKey, 'base64');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Signature of the resulting bundle: verified by the client on BOTH the patch
|
|
107
|
+
// path and the full-bundle fallback, so a tampered registry can't swap bundles.
|
|
108
|
+
const bundleSignature = sign(fullBundleName);
|
|
109
|
+
|
|
110
|
+
const mappedNewPatches = newPatches.map(p => {
|
|
111
|
+
const signature = sign(p.file);
|
|
117
112
|
|
|
118
113
|
const patchObj = {
|
|
119
114
|
id: `v${p.from}-to-v${p.to}-${flavor}-${platform}`,
|
|
@@ -132,6 +127,7 @@ if (newPatches.length > 0) {
|
|
|
132
127
|
|
|
133
128
|
if (signature) {
|
|
134
129
|
patchObj.signature = signature;
|
|
130
|
+
patchObj.bundleSignature = bundleSignature;
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
return patchObj;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
3
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
-
#
|
|
4
|
+
# OTA Upload Script — GitHub storage repository
|
|
5
5
|
# Uploads: 1) All .patch & .hbc files → {baseFolder}/{appVersion}/{channel}/
|
|
6
6
|
# 2) ota_registry.json → {baseFolder}/ota_registry.json
|
|
7
7
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
# Load environment variables if .env exists
|
|
10
10
|
ROOT_DIR=$(pwd)
|
|
11
11
|
if [ -f "$ROOT_DIR/.env" ]; then
|
|
12
|
-
|
|
12
|
+
set -a; . "$ROOT_DIR/.env"; set +a
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
15
|
BASE_URL="${OTA_BASE_URL:-}"
|
|
16
|
-
UPCODE_USER="${OTA_UPLOAD_USER:-}"
|
|
17
|
-
UPCODE_PASS="${OTA_UPLOAD_PASSWORD:-}"
|
|
18
16
|
BASE_FOLDER="${OTA_FOLDER:-${6:-ota}}"
|
|
19
17
|
APP_VERSION="$1"
|
|
20
18
|
CHANNEL="$2"
|
|
@@ -29,7 +27,7 @@ if [[ "$PLATFORM" == "ios" ]]; then
|
|
|
29
27
|
fi
|
|
30
28
|
|
|
31
29
|
echo ""
|
|
32
|
-
echo "===
|
|
30
|
+
echo "=== OTA Upload | App: $APP_VERSION | Channel: $CHANNEL | V$OTA_VERSION | Platform: $PLATFORM ==="
|
|
33
31
|
echo " Output dir: $OUTPUT_DIR"
|
|
34
32
|
|
|
35
33
|
# Validate
|
|
@@ -50,11 +48,23 @@ for ARG in "$@"; do
|
|
|
50
48
|
fi
|
|
51
49
|
done
|
|
52
50
|
|
|
51
|
+
# Files of this app version + channel only (bundle_/patch_/asset-manifest_ carry both;
|
|
52
|
+
# base-assets_<platform>_<channel>.json carries just the channel)
|
|
53
|
+
is_release_file() {
|
|
54
|
+
local FNAME="$1"
|
|
55
|
+
if [[ "$FNAME" == "base-assets_"*"_${CHANNEL}.json" ]]; then return 0; fi
|
|
56
|
+
[[ "$FNAME" == *"_${APP_VERSION}_"* ]] && [[ "$FNAME" == *"_${CHANNEL}."* || "$FNAME" == *"_${CHANNEL}_"* ]]
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
# ── GitHub Auto-Push Integration ──────────────────────────────────────────────
|
|
54
|
-
if [[ "$BASE_URL"
|
|
60
|
+
if [[ "$BASE_URL" != "https://raw.githubusercontent.com/"* ]]; then
|
|
61
|
+
echo "ERROR: OTA_BASE_URL must be https://raw.githubusercontent.com/<owner>/<repo>/<branch>"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
55
65
|
# Parse OWNER, REPO, BRANCH from URL
|
|
56
66
|
# Example: https://raw.githubusercontent.com/hungbv-taureau/baseform-storage/main
|
|
57
|
-
CLEAN_URL
|
|
67
|
+
CLEAN_URL="${BASE_URL#https://raw.githubusercontent.com/}"
|
|
58
68
|
OWNER=$(echo "$CLEAN_URL" | cut -d'/' -f1)
|
|
59
69
|
REPO=$(echo "$CLEAN_URL" | cut -d'/' -f2)
|
|
60
70
|
BRANCH=$(echo "$CLEAN_URL" | cut -d'/' -f3)
|
|
@@ -65,8 +75,11 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
|
|
|
65
75
|
TOKEN="${GH_CLI_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
|
|
66
76
|
if [[ "$TOKEN" == "baseform" ]]; then TOKEN=""; fi
|
|
67
77
|
|
|
78
|
+
# Token goes in a per-command header, never into the clone's .git/config
|
|
79
|
+
GIT_AUTH=()
|
|
68
80
|
if [[ -n "$TOKEN" ]]; then
|
|
69
|
-
GIT_REMOTE="https://
|
|
81
|
+
GIT_REMOTE="https://github.com/${OWNER}/${REPO}.git"
|
|
82
|
+
GIT_AUTH=(-c "http.https://github.com/.extraheader=AUTHORIZATION: basic $(printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n')")
|
|
70
83
|
echo " Using authenticated gh CLI token for ${OWNER}/${REPO}"
|
|
71
84
|
else
|
|
72
85
|
GIT_REMOTE="git@github.com:${OWNER}/${REPO}.git"
|
|
@@ -83,19 +96,21 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
|
|
|
83
96
|
if [ -d "$GH_REPO_DIR/.git" ]; then
|
|
84
97
|
echo " Syncing local storage clone with remote..."
|
|
85
98
|
git -C "$GH_REPO_DIR" remote set-url origin "$GIT_REMOTE" 2>/dev/null || true
|
|
86
|
-
git -C "$GH_REPO_DIR" fetch origin "$BRANCH" 2>/dev/null || true
|
|
99
|
+
git "${GIT_AUTH[@]}" -C "$GH_REPO_DIR" fetch origin "$BRANCH" 2>/dev/null || true
|
|
87
100
|
git -C "$GH_REPO_DIR" checkout "$BRANCH" 2>/dev/null || true
|
|
88
101
|
git -C "$GH_REPO_DIR" reset --hard "origin/$BRANCH" 2>/dev/null || true
|
|
89
102
|
else
|
|
90
103
|
echo " Cloning storage repository..."
|
|
91
104
|
rm -rf "$GH_REPO_DIR"
|
|
92
|
-
git clone --branch "$BRANCH" --depth 1 "$GIT_REMOTE" "$GH_REPO_DIR" 2>/dev/null || git clone "$GIT_REMOTE" "$GH_REPO_DIR"
|
|
105
|
+
git "${GIT_AUTH[@]}" clone --branch "$BRANCH" --depth 1 "$GIT_REMOTE" "$GH_REPO_DIR" 2>/dev/null || git "${GIT_AUTH[@]}" clone "$GIT_REMOTE" "$GH_REPO_DIR" || exit 1
|
|
106
|
+
chmod 700 "$GH_REPO_DIR"
|
|
93
107
|
fi
|
|
94
108
|
|
|
95
109
|
# 2. Copy generated files into storage repo (or reset remote)
|
|
96
110
|
if [ "$CLEAN_REMOTE" = true ]; then
|
|
97
|
-
echo " Resetting remote storage state for channel '$CHANNEL'..."
|
|
98
|
-
|
|
111
|
+
echo " Resetting remote storage state for channel '$CHANNEL' (base bundle v1 + base-assets kept)..."
|
|
112
|
+
find "$GH_REPO_DIR/$BASE_FOLDER/$APP_VERSION/$CHANNEL" -type f \
|
|
113
|
+
-not -name "bundle_${APP_VERSION}_v1_*" -not -name 'base-assets_*' -delete 2>/dev/null || true
|
|
99
114
|
mkdir -p "$GH_REPO_DIR/$BASE_FOLDER"
|
|
100
115
|
cp "$REGISTRY_FILE" "$GH_REPO_DIR/$BASE_FOLDER/ota_registry.json"
|
|
101
116
|
cp "$REGISTRY_FILE" "$GH_REPO_DIR/ota_registry.json" 2>/dev/null || true
|
|
@@ -104,18 +119,10 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
|
|
|
104
119
|
mkdir -p "$TARGET_RELEASE_DIR"
|
|
105
120
|
|
|
106
121
|
echo " Copying release files to storage repo for channel '$CHANNEL'..."
|
|
107
|
-
for FILE in "$OUTPUT_DIR"
|
|
122
|
+
for FILE in "$OUTPUT_DIR"/*.{patch,hbc,json}; do
|
|
108
123
|
[[ -f "$FILE" ]] || continue
|
|
109
124
|
FNAME=$(basename "$FILE")
|
|
110
|
-
|
|
111
|
-
if [[ "$FNAME" != *"_${APP_VERSION}_"* ]]; then
|
|
112
|
-
continue
|
|
113
|
-
fi
|
|
114
|
-
fi
|
|
115
|
-
|
|
116
|
-
if [[ "$FNAME" != *"_${APP_VERSION}_"* ]] && [[ "$FNAME" != *"_${CHANNEL}_"* ]] && [[ "$FNAME" != "asset-manifest"* ]] && [[ "$FNAME" != "base-assets"* ]]; then
|
|
117
|
-
continue
|
|
118
|
-
fi
|
|
125
|
+
is_release_file "$FNAME" || continue
|
|
119
126
|
cp "$FILE" "$TARGET_RELEASE_DIR/$FNAME"
|
|
120
127
|
done
|
|
121
128
|
|
|
@@ -148,102 +155,14 @@ if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"
|
|
|
148
155
|
git commit -m "$COMMIT_MSG"
|
|
149
156
|
echo " Pushing to GitHub (${BRANCH})..."
|
|
150
157
|
|
|
151
|
-
if git push origin "$BRANCH"; then
|
|
158
|
+
if git "${GIT_AUTH[@]}" push origin "$BRANCH"; then
|
|
152
159
|
echo " ✓ Successfully pushed OTA release to GitHub!"
|
|
153
160
|
else
|
|
154
|
-
|
|
161
|
+
# Usually someone else pushed first: re-run so the registry is re-synced, never force-push
|
|
162
|
+
echo " ❌ Git push failed (remote changed or no write access to ${OWNER}/${REPO}). Re-run the build."
|
|
163
|
+
exit 1
|
|
155
164
|
fi
|
|
156
165
|
fi
|
|
157
166
|
cd "$ROOT_DIR"
|
|
158
167
|
echo ""
|
|
159
168
|
echo "=== GitHub Sync Complete ==="
|
|
160
|
-
exit 0
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
# ── MinIO Server Integration ──────────────────────────────────────────────────
|
|
164
|
-
echo "Logging in to MinIO Server..."
|
|
165
|
-
LOGIN_RES=$(curl -s -X "POST" "$BASE_URL/public/authen/login" \
|
|
166
|
-
-H 'Content-Type: application/json; charset=utf-8' \
|
|
167
|
-
-d "{\"username\": \"$UPCODE_USER\", \"password\": \"$UPCODE_PASS\"}")
|
|
168
|
-
|
|
169
|
-
TOKEN=$(echo "$LOGIN_RES" | node -e "
|
|
170
|
-
try {
|
|
171
|
-
const r = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
172
|
-
console.log(r.data?.accessTokenInfo?.accessToken || '');
|
|
173
|
-
} catch(e) { console.log(''); }
|
|
174
|
-
")
|
|
175
|
-
if [[ -z "$TOKEN" ]]; then
|
|
176
|
-
echo " ⚠ Notice: Remote MinIO server login skipped."
|
|
177
|
-
echo " ✓ Bundles & registry generated at: $OUTPUT_DIR"
|
|
178
|
-
echo " Please commit and push local changes to your GitHub storage repository."
|
|
179
|
-
exit 0
|
|
180
|
-
fi
|
|
181
|
-
echo " ✓ Login successful"
|
|
182
|
-
|
|
183
|
-
remote_delete() {
|
|
184
|
-
local path=$1
|
|
185
|
-
local encoded
|
|
186
|
-
encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$path'))")
|
|
187
|
-
curl -s -X "DELETE" "$BASE_URL/api/minio/delete?path=$encoded" \
|
|
188
|
-
-H "Authorization: Bearer $TOKEN" > /dev/null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
remote_upload() {
|
|
192
|
-
local local_file=$1
|
|
193
|
-
local remote_path=$2
|
|
194
|
-
echo " uploading: $remote_path"
|
|
195
|
-
local RES
|
|
196
|
-
RES=$(curl -s -X "POST" "$BASE_URL/api/minio/upload" \
|
|
197
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
198
|
-
-H 'Content-Type: multipart/form-data' \
|
|
199
|
-
-F "file=@$local_file" \
|
|
200
|
-
-F "path=$remote_path")
|
|
201
|
-
if [[ "$RES" != *"\"success\":true"* ]] && [[ "$RES" != *"\"code\":1"* ]] && [[ "$RES" != *"\"status\":200"* ]] && [[ "$RES" != *"\"code\":200"* ]]; then
|
|
202
|
-
echo " ⚠ Upload potential issue: $RES"
|
|
203
|
-
else
|
|
204
|
-
echo " ✓ Upload success"
|
|
205
|
-
fi
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
echo "Uploading files for channel: $CHANNEL..."
|
|
209
|
-
FILE_COUNT=0
|
|
210
|
-
for FILE in "$OUTPUT_DIR"/*.{patch,hbc,json}; do
|
|
211
|
-
[[ -f "$FILE" ]] || continue
|
|
212
|
-
FNAME=$(basename "$FILE")
|
|
213
|
-
if [[ "$FNAME" == "patch_"* ]] || [[ "$FNAME" == "bundle_"* ]]; then
|
|
214
|
-
if [[ "$FNAME" != *"_${APP_VERSION}_"* ]]; then
|
|
215
|
-
continue
|
|
216
|
-
fi
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
if [[ "$FNAME" != *"_${APP_VERSION}_"* ]] && [[ "$FNAME" != *"_${CHANNEL}_"* ]] && [[ "$FNAME" != *"_${PLATFORM}_"* ]] && [[ "$FNAME" != "asset-manifest"* ]] && [[ "$FNAME" != "base-assets"* ]]; then
|
|
220
|
-
continue
|
|
221
|
-
fi
|
|
222
|
-
|
|
223
|
-
REMOTE_PATH="$BASE_FOLDER/$APP_VERSION/$CHANNEL/$FNAME"
|
|
224
|
-
remote_delete "$REMOTE_PATH"
|
|
225
|
-
remote_upload "$FILE" "$REMOTE_PATH"
|
|
226
|
-
FILE_COUNT=$((FILE_COUNT + 1))
|
|
227
|
-
done
|
|
228
|
-
|
|
229
|
-
if [[ -d "$OUTPUT_DIR/asset-store" ]]; then
|
|
230
|
-
echo "Uploading hashed assets..."
|
|
231
|
-
for FILE in "$OUTPUT_DIR/asset-store"/*; do
|
|
232
|
-
[[ -f "$FILE" ]] || continue
|
|
233
|
-
FNAME=$(basename "$FILE")
|
|
234
|
-
REMOTE_PATH="$BASE_FOLDER/assets/$FNAME"
|
|
235
|
-
remote_upload "$FILE" "$REMOTE_PATH"
|
|
236
|
-
FILE_COUNT=$((FILE_COUNT + 1))
|
|
237
|
-
done
|
|
238
|
-
fi
|
|
239
|
-
echo " ✓ $FILE_COUNT file(s) uploaded (patches + bundles + assets)"
|
|
240
|
-
|
|
241
|
-
echo "Uploading ota_registry.json..."
|
|
242
|
-
REMOTE_REGISTRY="$BASE_FOLDER/ota_registry.json"
|
|
243
|
-
remote_delete "$REMOTE_REGISTRY"
|
|
244
|
-
remote_upload "$REGISTRY_FILE" "$REMOTE_REGISTRY"
|
|
245
|
-
echo " ✓ Registry uploaded"
|
|
246
|
-
|
|
247
|
-
echo ""
|
|
248
|
-
echo "=== Upload Complete ==="
|
|
249
|
-
echo " Registry : $REMOTE_REGISTRY"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":[],"sourceRoot":"../../src","sources":["crypto-js.d.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.CustomStorageProvider = void 0;
|
|
7
|
-
var _reactNative = require("react-native");
|
|
8
|
-
var _reactNativeBlobUtil = _interopRequireDefault(require("react-native-blob-util"));
|
|
9
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
/** Cache tokens per server endpoint so switching configs invalidates stale tokens. */
|
|
11
|
-
const tokenCache = new Map();
|
|
12
|
-
/** Cache registry ETag and parsed patches to avoid redownloading unmodified manifests. */
|
|
13
|
-
const registryCache = new Map();
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Implementation for the custom MinIO-based storage server.
|
|
17
|
-
*/
|
|
18
|
-
class CustomStorageProvider {
|
|
19
|
-
getEndpoint(config, key, fallback) {
|
|
20
|
-
return config.customServer?.endpoints?.[key] ?? fallback;
|
|
21
|
-
}
|
|
22
|
-
async getAccessToken(config) {
|
|
23
|
-
if (!config.customServer) {
|
|
24
|
-
throw new Error('customServer configuration missing');
|
|
25
|
-
}
|
|
26
|
-
const {
|
|
27
|
-
baseUrl,
|
|
28
|
-
username,
|
|
29
|
-
password
|
|
30
|
-
} = config.customServer;
|
|
31
|
-
const cacheKey = `${baseUrl}|${username}`;
|
|
32
|
-
const now = Date.now();
|
|
33
|
-
const cached = tokenCache.get(cacheKey);
|
|
34
|
-
if (cached && now < cached.expiresAt - 30000) {
|
|
35
|
-
return cached.token;
|
|
36
|
-
}
|
|
37
|
-
const loginPath = this.getEndpoint(config, 'login', 'public/authen/login');
|
|
38
|
-
const loginUrl = baseUrl.endsWith('/') ? `${baseUrl}${loginPath}` : `${baseUrl}/${loginPath}`;
|
|
39
|
-
try {
|
|
40
|
-
const res = await _reactNativeBlobUtil.default.fetch('POST', loginUrl, {
|
|
41
|
-
'Content-Type': 'application/json; charset=utf-8'
|
|
42
|
-
}, JSON.stringify({
|
|
43
|
-
username,
|
|
44
|
-
password
|
|
45
|
-
}));
|
|
46
|
-
const json = JSON.parse(res.data);
|
|
47
|
-
const token = json.data?.accessTokenInfo?.accessToken;
|
|
48
|
-
if (!token) throw new Error('Login failed: token not found');
|
|
49
|
-
tokenCache.set(cacheKey, {
|
|
50
|
-
token,
|
|
51
|
-
expiresAt: now + 3600000
|
|
52
|
-
}); // 1 hour
|
|
53
|
-
return token;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
throw new Error(`Authentication failed: ${error.message}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async fetchManifest(config) {
|
|
59
|
-
if (!config.customServer) return config.bundledPatches || [];
|
|
60
|
-
const {
|
|
61
|
-
baseUrl,
|
|
62
|
-
channel,
|
|
63
|
-
baseFolder = 'bspatch'
|
|
64
|
-
} = config.customServer;
|
|
65
|
-
const registryPath = `${baseFolder}/ota_registry.json`;
|
|
66
|
-
const regCacheKey = `${baseUrl}|${registryPath}|${config.appVersion}|${channel}`;
|
|
67
|
-
const cachedReg = registryCache.get(regCacheKey);
|
|
68
|
-
try {
|
|
69
|
-
const token = await this.getAccessToken(config);
|
|
70
|
-
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
71
|
-
const genLinkPath = this.getEndpoint(config, 'genDownloadLink', 'api/minio/gen-download-link');
|
|
72
|
-
const genLinkUrl = `${normalizedBaseUrl}/${genLinkPath}?pathFile=${encodeURIComponent(registryPath)}`;
|
|
73
|
-
const linkRes = await _reactNativeBlobUtil.default.fetch('GET', genLinkUrl, {
|
|
74
|
-
Authorization: `Bearer ${token}`
|
|
75
|
-
});
|
|
76
|
-
const linkJson = JSON.parse(linkRes.data);
|
|
77
|
-
const registryUrl = linkJson.data;
|
|
78
|
-
if (!registryUrl) throw new Error('Failed to generate registry download link');
|
|
79
|
-
const reqHeaders = {};
|
|
80
|
-
if (cachedReg?.etag) {
|
|
81
|
-
reqHeaders['If-None-Match'] = cachedReg.etag;
|
|
82
|
-
}
|
|
83
|
-
const registryRes = await _reactNativeBlobUtil.default.fetch('GET', registryUrl, reqHeaders);
|
|
84
|
-
if (registryRes.respInfo.status === 304 && cachedReg) {
|
|
85
|
-
return cachedReg.patches;
|
|
86
|
-
}
|
|
87
|
-
if (registryRes.respInfo.status !== 200) {
|
|
88
|
-
throw new Error(`Registry fetch failed with status ${registryRes.respInfo.status}`);
|
|
89
|
-
}
|
|
90
|
-
const etag = registryRes.respInfo.headers?.['etag'] || registryRes.respInfo.headers?.['ETag'];
|
|
91
|
-
if (!config.appVersion) {
|
|
92
|
-
throw new Error('[CustomStorage] config.appVersion is required to query OTA registry');
|
|
93
|
-
}
|
|
94
|
-
const registry = JSON.parse(registryRes.data);
|
|
95
|
-
const appGroup = registry?.apps?.[config.appVersion];
|
|
96
|
-
const flavorGroup = appGroup?.flavors?.[channel.toLowerCase()];
|
|
97
|
-
if (!flavorGroup || !flavorGroup.patches?.length) {
|
|
98
|
-
return config.bundledPatches || [];
|
|
99
|
-
}
|
|
100
|
-
const matchedPatches = (flavorGroup.patches || []).filter(p => p.platform === _reactNative.Platform.OS);
|
|
101
|
-
registryCache.set(regCacheKey, {
|
|
102
|
-
etag,
|
|
103
|
-
patches: matchedPatches
|
|
104
|
-
});
|
|
105
|
-
return matchedPatches;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.warn('[CustomStorage] Manifest fetch error:', error.message);
|
|
108
|
-
return cachedReg?.patches || config.bundledPatches || [];
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async getUpdateFileUrl(filename, config) {
|
|
112
|
-
if (!config.customServer) throw new Error('customServer config missing');
|
|
113
|
-
const {
|
|
114
|
-
baseUrl,
|
|
115
|
-
baseFolder = 'bspatch',
|
|
116
|
-
useMinio
|
|
117
|
-
} = config.customServer;
|
|
118
|
-
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
119
|
-
const channel = config.customServer.channel.toLowerCase();
|
|
120
|
-
const remotePath = `${baseFolder}/${config.appVersion}/${channel}/${filename}`;
|
|
121
|
-
if (useMinio) {
|
|
122
|
-
const token = await this.getAccessToken(config);
|
|
123
|
-
const genLinkPath = this.getEndpoint(config, 'genDownloadLink', 'api/minio/gen-download-link');
|
|
124
|
-
const genLinkUrl = `${normalizedBaseUrl}/${genLinkPath}?pathFile=${encodeURIComponent(remotePath)}`;
|
|
125
|
-
const res = await _reactNativeBlobUtil.default.fetch('GET', genLinkUrl, {
|
|
126
|
-
Authorization: `Bearer ${token}`
|
|
127
|
-
});
|
|
128
|
-
const json = JSON.parse(res.data);
|
|
129
|
-
if (!json.data) throw new Error(`Failed to generate link for: ${filename}`);
|
|
130
|
-
return json.data;
|
|
131
|
-
}
|
|
132
|
-
const downloadPath = this.getEndpoint(config, 'downloadFile', 'files/downloadStreamFile');
|
|
133
|
-
return `${normalizedBaseUrl}/${downloadPath}?filepath=${encodeURIComponent(remotePath)}`;
|
|
134
|
-
}
|
|
135
|
-
async getAssetUrl(hash, ext, config) {
|
|
136
|
-
if (!config.customServer) return '';
|
|
137
|
-
const {
|
|
138
|
-
baseUrl,
|
|
139
|
-
baseFolder = 'bspatch',
|
|
140
|
-
useMinio
|
|
141
|
-
} = config.customServer;
|
|
142
|
-
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
143
|
-
const filename = `${hash}.${ext}`;
|
|
144
|
-
const remotePath = `${baseFolder}/assets/${filename}`;
|
|
145
|
-
if (useMinio) {
|
|
146
|
-
const token = await this.getAccessToken(config);
|
|
147
|
-
const genLinkPath = this.getEndpoint(config, 'genDownloadLink', 'api/minio/gen-download-link');
|
|
148
|
-
const genLinkUrl = `${normalizedBaseUrl}/${genLinkPath}?pathFile=${encodeURIComponent(remotePath)}`;
|
|
149
|
-
const res = await _reactNativeBlobUtil.default.fetch('GET', genLinkUrl, {
|
|
150
|
-
Authorization: `Bearer ${token}`
|
|
151
|
-
});
|
|
152
|
-
const json = JSON.parse(res.data);
|
|
153
|
-
return json.data || '';
|
|
154
|
-
}
|
|
155
|
-
const downloadPath = this.getEndpoint(config, 'downloadFile', 'files/downloadStreamFile');
|
|
156
|
-
return `${normalizedBaseUrl}/${downloadPath}?filepath=${encodeURIComponent(remotePath)}`;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
exports.CustomStorageProvider = CustomStorageProvider;
|
|
160
|
-
//# sourceMappingURL=CustomStorage.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_reactNativeBlobUtil","_interopRequireDefault","e","__esModule","default","tokenCache","Map","registryCache","CustomStorageProvider","getEndpoint","config","key","fallback","customServer","endpoints","getAccessToken","Error","baseUrl","username","password","cacheKey","now","Date","cached","get","expiresAt","token","loginPath","loginUrl","endsWith","res","ReactNativeBlobUtil","fetch","JSON","stringify","json","parse","data","accessTokenInfo","accessToken","set","error","message","fetchManifest","bundledPatches","channel","baseFolder","registryPath","regCacheKey","appVersion","cachedReg","normalizedBaseUrl","slice","genLinkPath","genLinkUrl","encodeURIComponent","linkRes","Authorization","linkJson","registryUrl","reqHeaders","etag","registryRes","respInfo","status","patches","headers","registry","appGroup","apps","flavorGroup","flavors","toLowerCase","length","matchedPatches","filter","p","platform","Platform","OS","console","warn","getUpdateFileUrl","filename","useMinio","remotePath","downloadPath","getAssetUrl","hash","ext","exports"],"sourceRoot":"../../../../src","sources":["ota/storage/CustomStorage.ts"],"mappings":"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","ignoreList":[]}
|