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.
Files changed (89) hide show
  1. package/README.md +22 -32
  2. package/android/src/main/java/vn/reactnativebspatch/BsPatchModule.kt +15 -102
  3. package/android/src/main/java/vn/reactnativebspatch/OtaBundleResolver.kt +21 -1
  4. package/cpp/bsdiff/bspatch.c +1 -0
  5. package/ios/OtaBundleResolver.swift +16 -1
  6. package/ios/RNBsPatch.mm +61 -154
  7. package/lib/commonjs/NativeBsPatch.js.map +1 -1
  8. package/lib/commonjs/index.js +4 -4
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/ota/OtaProvider.js +26 -22
  11. package/lib/commonjs/ota/OtaProvider.js.map +1 -1
  12. package/lib/commonjs/ota/otaAssetResolver.js +8 -2
  13. package/lib/commonjs/ota/otaAssetResolver.js.map +1 -1
  14. package/lib/commonjs/ota/otaCore.js +89 -141
  15. package/lib/commonjs/ota/otaCore.js.map +1 -1
  16. package/lib/commonjs/ota/otaLogger.js +0 -2
  17. package/lib/commonjs/ota/otaLogger.js.map +1 -1
  18. package/lib/commonjs/ota/otaMetadata.js +18 -7
  19. package/lib/commonjs/ota/otaMetadata.js.map +1 -1
  20. package/lib/commonjs/ota/storage/GitHubRawStorage.js +3 -1
  21. package/lib/commonjs/ota/storage/GitHubRawStorage.js.map +1 -1
  22. package/lib/commonjs/ota/types.js +1 -10
  23. package/lib/commonjs/ota/types.js.map +1 -1
  24. package/lib/module/NativeBsPatch.js.map +1 -1
  25. package/lib/module/index.js +1 -1
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/ota/OtaProvider.js +26 -22
  28. package/lib/module/ota/OtaProvider.js.map +1 -1
  29. package/lib/module/ota/otaAssetResolver.js +8 -2
  30. package/lib/module/ota/otaAssetResolver.js.map +1 -1
  31. package/lib/module/ota/otaCore.js +88 -139
  32. package/lib/module/ota/otaCore.js.map +1 -1
  33. package/lib/module/ota/otaLogger.js +0 -2
  34. package/lib/module/ota/otaLogger.js.map +1 -1
  35. package/lib/module/ota/otaMetadata.js +18 -7
  36. package/lib/module/ota/otaMetadata.js.map +1 -1
  37. package/lib/module/ota/storage/GitHubRawStorage.js +3 -1
  38. package/lib/module/ota/storage/GitHubRawStorage.js.map +1 -1
  39. package/lib/module/ota/types.js +0 -9
  40. package/lib/module/ota/types.js.map +1 -1
  41. package/lib/typescript/NativeBsPatch.d.ts +3 -15
  42. package/lib/typescript/NativeBsPatch.d.ts.map +1 -1
  43. package/lib/typescript/index.d.ts +1 -1
  44. package/lib/typescript/index.d.ts.map +1 -1
  45. package/lib/typescript/ota/OtaProvider.d.ts.map +1 -1
  46. package/lib/typescript/ota/otaAssetResolver.d.ts.map +1 -1
  47. package/lib/typescript/ota/otaCore.d.ts +5 -42
  48. package/lib/typescript/ota/otaCore.d.ts.map +1 -1
  49. package/lib/typescript/ota/otaLogger.d.ts +1 -3
  50. package/lib/typescript/ota/otaLogger.d.ts.map +1 -1
  51. package/lib/typescript/ota/otaMetadata.d.ts +2 -2
  52. package/lib/typescript/ota/otaMetadata.d.ts.map +1 -1
  53. package/lib/typescript/ota/types.d.ts +18 -54
  54. package/lib/typescript/ota/types.d.ts.map +1 -1
  55. package/package.json +3 -6
  56. package/react-native-bspatch.podspec +0 -1
  57. package/src/NativeBsPatch.ts +3 -16
  58. package/src/index.ts +1 -1
  59. package/src/ota/OtaProvider.tsx +24 -23
  60. package/src/ota/otaAssetResolver.ts +5 -2
  61. package/src/ota/otaCore.ts +118 -176
  62. package/src/ota/otaLogger.ts +0 -2
  63. package/src/ota/otaMetadata.ts +18 -8
  64. package/src/ota/storage/GitHubRawStorage.ts +1 -1
  65. package/src/ota/types.ts +18 -57
  66. package/tools/build_ota.sh +117 -183
  67. package/tools/setup_project.js +33 -14
  68. package/tools/update_registry.js +24 -28
  69. package/tools/{upload_ota_custom.sh → upload_ota.sh} +33 -114
  70. package/lib/commonjs/crypto-js.d.js +0 -2
  71. package/lib/commonjs/crypto-js.d.js.map +0 -1
  72. package/lib/commonjs/ota/storage/CustomStorage.js +0 -160
  73. package/lib/commonjs/ota/storage/CustomStorage.js.map +0 -1
  74. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js +0 -99
  75. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js.map +0 -1
  76. package/lib/module/crypto-js.d.js +0 -2
  77. package/lib/module/crypto-js.d.js.map +0 -1
  78. package/lib/module/ota/storage/CustomStorage.js +0 -154
  79. package/lib/module/ota/storage/CustomStorage.js.map +0 -1
  80. package/lib/module/ota/storage/GitHubReleaseStorage.js +0 -93
  81. package/lib/module/ota/storage/GitHubReleaseStorage.js.map +0 -1
  82. package/lib/typescript/ota/storage/CustomStorage.d.ts +0 -12
  83. package/lib/typescript/ota/storage/CustomStorage.d.ts.map +0 -1
  84. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts +0 -20
  85. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts.map +0 -1
  86. package/src/crypto-js.d.ts +0 -1
  87. package/src/ota/storage/CustomStorage.ts +0 -166
  88. package/src/ota/storage/GitHubReleaseStorage.ts +0 -103
  89. package/tools/bsdiff +0 -0
@@ -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 \\"$DEST_FILE\\")"',
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
- ' mkdir -p "$PROJECT_DIR/output"',
66
- ' cp "$BUNDLE_RESULT" "$PROJECT_DIR/output/bundle_${APP_VER}_v1_ios_production.hbc"',
67
- ' cp "$BUNDLE_RESULT" "$PROJECT_DIR/output/bundle_${APP_VER}_v1_ios_development.hbc" 2>/dev/null || true',
68
- ' echo "[react-native-bspatch] ✓ Auto-captured iOS base bundle v1 to output/"',
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
- if (bundleFile.exists()) {
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
- if (content.includes(ANDROID_GRADLE_MARKER)) return content;
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
- // Already patched?
157
- if (content.includes(XCODE_BUNDLE_SYNC_MARKER)) return content;
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";');
@@ -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
- const mappedNewPatches = newPatches.map(p => {
89
- // Naming convention from build_ota.sh
90
- const fullBundleName = `bundle_${appVersion}_v${toOtaVersion}_${platform}_${flavor}.hbc`;
91
- const manifestName = path.basename(_assetManifestFile);
92
-
93
- let signature = undefined;
94
- if (signingKey) {
95
- try {
96
- const patchFileCandidates = [
97
- path.join(path.dirname(registryPath), p.file),
98
- path.join(process.cwd(), p.file),
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
- # Custom OTA Upload Script for Sync Server & GitHub Storage
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
- export $(grep -v '^#' "$ROOT_DIR/.env" | grep -v '^$' | xargs 2>/dev/null)
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 "=== Custom OTA Upload | App: $APP_VERSION | Channel: $CHANNEL | V$OTA_VERSION | Platform: $PLATFORM ==="
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" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
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=$(echo "$BASE_URL" | sed 's#https://raw.githubusercontent.com/##' | sed 's#https://github.com/##')
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://x-access-token:${TOKEN}@github.com/${OWNER}/${REPO}.git"
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
- rm -rf "$GH_REPO_DIR/$BASE_FOLDER/$APP_VERSION/$CHANNEL"
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"/*_${CHANNEL}.json "$OUTPUT_DIR"/*_${CHANNEL}.hbc "$OUTPUT_DIR"/*_${CHANNEL}_*.patch "$OUTPUT_DIR"/*_${CHANNEL}.patch; do
122
+ for FILE in "$OUTPUT_DIR"/*.{patch,hbc,json}; do
108
123
  [[ -f "$FILE" ]] || continue
109
124
  FNAME=$(basename "$FILE")
110
- if [[ "$FNAME" == "patch_"* ]] || [[ "$FNAME" == "bundle_"* ]]; then
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
- echo " ⚠ Git push failed. Please verify write permissions for ${OWNER}/${REPO}."
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,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=crypto-js.d.js.map
@@ -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":[]}