react-native-bspatch 1.0.4 → 1.0.6
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/lib/commonjs/ota/storage/GitHubRawStorage.js +22 -3
- package/lib/commonjs/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/module/ota/storage/GitHubRawStorage.js +22 -3
- package/lib/module/ota/storage/GitHubRawStorage.js.map +1 -1
- package/lib/typescript/ota/storage/GitHubRawStorage.d.ts +0 -8
- package/lib/typescript/ota/storage/GitHubRawStorage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ota/storage/GitHubRawStorage.ts +28 -3
- package/tools/build_ota.sh +55 -8
|
@@ -14,7 +14,20 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
14
14
|
* - Manifest: `${baseUrl}/${baseFolder}/ota_registry.json`
|
|
15
15
|
* - Patch file: `${baseUrl}/${baseFolder}/${appVersion}/${channel}/${filename}`
|
|
16
16
|
* - Assets: `${baseUrl}/${baseFolder}/assets/${hash}.${ext}`
|
|
17
|
+
*
|
|
18
|
+
* NOTE: All manifest/registry fetches include Cache-Control: no-cache and a
|
|
19
|
+
* timestamp query param to bypass GitHub CDN's 5-minute (max-age=300) cache.
|
|
20
|
+
* Asset URLs are content-addressed (hash-named) so they are immutable and
|
|
21
|
+
* intentionally NOT cache-busted.
|
|
17
22
|
*/
|
|
23
|
+
|
|
24
|
+
/** Headers to bypass GitHub CDN cache on mutable files (registry, manifests) */
|
|
25
|
+
const NO_CACHE_HEADERS = {
|
|
26
|
+
'Cache-Control': 'no-cache'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Append a timestamp query param to defeat CDN caches on mutable URLs */
|
|
30
|
+
const bustCache = url => `${url}?cb=${Date.now()}`;
|
|
18
31
|
class GitHubRawStorageProvider {
|
|
19
32
|
async fetchManifest(config) {
|
|
20
33
|
if (!config.customServer) return config.bundledPatches || [];
|
|
@@ -24,9 +37,10 @@ class GitHubRawStorageProvider {
|
|
|
24
37
|
baseFolder = 'ota'
|
|
25
38
|
} = config.customServer;
|
|
26
39
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
27
|
-
|
|
40
|
+
// Cache-bust: registry is mutable; must always fetch the latest version
|
|
41
|
+
const registryUrl = bustCache(`${normalizedBaseUrl}/${baseFolder}/ota_registry.json`);
|
|
28
42
|
try {
|
|
29
|
-
const res = await _reactNativeBlobUtil.default.fetch('GET', registryUrl);
|
|
43
|
+
const res = await _reactNativeBlobUtil.default.fetch('GET', registryUrl, NO_CACHE_HEADERS);
|
|
30
44
|
if (res.respInfo.status !== 200) {
|
|
31
45
|
throw new Error(`Registry fetch failed with status ${res.respInfo.status}`);
|
|
32
46
|
}
|
|
@@ -53,7 +67,10 @@ class GitHubRawStorageProvider {
|
|
|
53
67
|
} = config.customServer;
|
|
54
68
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
55
69
|
const channelName = channel.toLowerCase();
|
|
56
|
-
|
|
70
|
+
|
|
71
|
+
// Cache-bust: patch files are versioned by name but GitHub CDN may
|
|
72
|
+
// serve a stale 404 response for newly uploaded files within 5 min.
|
|
73
|
+
return bustCache(`${normalizedBaseUrl}/${baseFolder}/${config.appVersion}/${channelName}/${filename}`);
|
|
57
74
|
}
|
|
58
75
|
async getAssetUrl(hash, ext, config) {
|
|
59
76
|
if (!config.customServer) return '';
|
|
@@ -62,6 +79,8 @@ class GitHubRawStorageProvider {
|
|
|
62
79
|
baseFolder = 'ota'
|
|
63
80
|
} = config.customServer;
|
|
64
81
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
82
|
+
|
|
83
|
+
// Assets are content-addressed (hash = filename) → immutable, no cache-bust needed
|
|
65
84
|
return `${normalizedBaseUrl}/${baseFolder}/assets/${hash}.${ext}`;
|
|
66
85
|
}
|
|
67
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_reactNativeBlobUtil","_interopRequireDefault","e","__esModule","default","GitHubRawStorageProvider","fetchManifest","config","customServer","bundledPatches","baseUrl","channel","baseFolder","normalizedBaseUrl","endsWith","slice","registryUrl","res","ReactNativeBlobUtil","fetch","respInfo","status","Error","registry","data","JSON","parse","appGroup","apps","appVersion","flavorGroup","flavors","toLowerCase","development","patches","length","filter","p","platform","Platform","OS","error","console","warn","message","getUpdateFileUrl","filename","channelName","getAssetUrl","hash","ext","exports"],"sourceRoot":"../../../../src","sources":["ota/storage/GitHubRawStorage.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,oBAAA,GAAAC,sBAAA,CAAAF,OAAA;AAAyD,SAAAE,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAGzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_reactNativeBlobUtil","_interopRequireDefault","e","__esModule","default","NO_CACHE_HEADERS","bustCache","url","Date","now","GitHubRawStorageProvider","fetchManifest","config","customServer","bundledPatches","baseUrl","channel","baseFolder","normalizedBaseUrl","endsWith","slice","registryUrl","res","ReactNativeBlobUtil","fetch","respInfo","status","Error","registry","data","JSON","parse","appGroup","apps","appVersion","flavorGroup","flavors","toLowerCase","development","patches","length","filter","p","platform","Platform","OS","error","console","warn","message","getUpdateFileUrl","filename","channelName","getAssetUrl","hash","ext","exports"],"sourceRoot":"../../../../src","sources":["ota/storage/GitHubRawStorage.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -9,7 +9,20 @@ import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
|
9
9
|
* - Manifest: `${baseUrl}/${baseFolder}/ota_registry.json`
|
|
10
10
|
* - Patch file: `${baseUrl}/${baseFolder}/${appVersion}/${channel}/${filename}`
|
|
11
11
|
* - Assets: `${baseUrl}/${baseFolder}/assets/${hash}.${ext}`
|
|
12
|
+
*
|
|
13
|
+
* NOTE: All manifest/registry fetches include Cache-Control: no-cache and a
|
|
14
|
+
* timestamp query param to bypass GitHub CDN's 5-minute (max-age=300) cache.
|
|
15
|
+
* Asset URLs are content-addressed (hash-named) so they are immutable and
|
|
16
|
+
* intentionally NOT cache-busted.
|
|
12
17
|
*/
|
|
18
|
+
|
|
19
|
+
/** Headers to bypass GitHub CDN cache on mutable files (registry, manifests) */
|
|
20
|
+
const NO_CACHE_HEADERS = {
|
|
21
|
+
'Cache-Control': 'no-cache'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Append a timestamp query param to defeat CDN caches on mutable URLs */
|
|
25
|
+
const bustCache = url => `${url}?cb=${Date.now()}`;
|
|
13
26
|
export class GitHubRawStorageProvider {
|
|
14
27
|
async fetchManifest(config) {
|
|
15
28
|
if (!config.customServer) return config.bundledPatches || [];
|
|
@@ -19,9 +32,10 @@ export class GitHubRawStorageProvider {
|
|
|
19
32
|
baseFolder = 'ota'
|
|
20
33
|
} = config.customServer;
|
|
21
34
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
22
|
-
|
|
35
|
+
// Cache-bust: registry is mutable; must always fetch the latest version
|
|
36
|
+
const registryUrl = bustCache(`${normalizedBaseUrl}/${baseFolder}/ota_registry.json`);
|
|
23
37
|
try {
|
|
24
|
-
const res = await ReactNativeBlobUtil.fetch('GET', registryUrl);
|
|
38
|
+
const res = await ReactNativeBlobUtil.fetch('GET', registryUrl, NO_CACHE_HEADERS);
|
|
25
39
|
if (res.respInfo.status !== 200) {
|
|
26
40
|
throw new Error(`Registry fetch failed with status ${res.respInfo.status}`);
|
|
27
41
|
}
|
|
@@ -48,7 +62,10 @@ export class GitHubRawStorageProvider {
|
|
|
48
62
|
} = config.customServer;
|
|
49
63
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
50
64
|
const channelName = channel.toLowerCase();
|
|
51
|
-
|
|
65
|
+
|
|
66
|
+
// Cache-bust: patch files are versioned by name but GitHub CDN may
|
|
67
|
+
// serve a stale 404 response for newly uploaded files within 5 min.
|
|
68
|
+
return bustCache(`${normalizedBaseUrl}/${baseFolder}/${config.appVersion}/${channelName}/${filename}`);
|
|
52
69
|
}
|
|
53
70
|
async getAssetUrl(hash, ext, config) {
|
|
54
71
|
if (!config.customServer) return '';
|
|
@@ -57,6 +74,8 @@ export class GitHubRawStorageProvider {
|
|
|
57
74
|
baseFolder = 'ota'
|
|
58
75
|
} = config.customServer;
|
|
59
76
|
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
77
|
+
|
|
78
|
+
// Assets are content-addressed (hash = filename) → immutable, no cache-bust needed
|
|
60
79
|
return `${normalizedBaseUrl}/${baseFolder}/assets/${hash}.${ext}`;
|
|
61
80
|
}
|
|
62
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Platform","ReactNativeBlobUtil","GitHubRawStorageProvider","fetchManifest","config","customServer","bundledPatches","baseUrl","channel","baseFolder","normalizedBaseUrl","endsWith","slice","registryUrl","res","fetch","respInfo","status","Error","registry","data","JSON","parse","appGroup","apps","appVersion","flavorGroup","flavors","toLowerCase","development","patches","length","filter","p","platform","OS","error","console","warn","message","getUpdateFileUrl","filename","channelName","getAssetUrl","hash","ext"],"sourceRoot":"../../../../src","sources":["ota/storage/GitHubRawStorage.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,mBAAmB,MAAM,wBAAwB;AAGxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,wBAAwB,CAA+B;EAChE,MAAMC,aAAaA,CAACC,MAAiB,EAAuB;IACxD,IAAI,CAACA,MAAM,CAACC,YAAY,EAAE,OAAOD,MAAM,CAACE,cAAc,IAAI,EAAE;IAE5D,MAAM;MAAEC,OAAO;MAAEC,OAAO;MAAEC,UAAU,GAAG;IAAM,CAAC,GAAGL,MAAM,CAACC,YAAY;IACpE,MAAMK,iBAAiB,GAAGH,OAAO,CAACI,QAAQ,CAAC,GAAG,CAAC,GACzCJ,OAAO,CAACK,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACpBL,OAAO;IACb,MAAMM,WAAW,
|
|
1
|
+
{"version":3,"names":["Platform","ReactNativeBlobUtil","NO_CACHE_HEADERS","bustCache","url","Date","now","GitHubRawStorageProvider","fetchManifest","config","customServer","bundledPatches","baseUrl","channel","baseFolder","normalizedBaseUrl","endsWith","slice","registryUrl","res","fetch","respInfo","status","Error","registry","data","JSON","parse","appGroup","apps","appVersion","flavorGroup","flavors","toLowerCase","development","patches","length","filter","p","platform","OS","error","console","warn","message","getUpdateFileUrl","filename","channelName","getAssetUrl","hash","ext"],"sourceRoot":"../../../../src","sources":["ota/storage/GitHubRawStorage.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,mBAAmB,MAAM,wBAAwB;AAGxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,MAAMC,gBAAgB,GAAG;EAAE,eAAe,EAAE;AAAW,CAAC;;AAExD;AACA,MAAMC,SAAS,GAAIC,GAAW,IAC1B,GAAGA,GAAG,OAAOC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAE;AAE7B,OAAO,MAAMC,wBAAwB,CAA+B;EAChE,MAAMC,aAAaA,CAACC,MAAiB,EAAuB;IACxD,IAAI,CAACA,MAAM,CAACC,YAAY,EAAE,OAAOD,MAAM,CAACE,cAAc,IAAI,EAAE;IAE5D,MAAM;MAAEC,OAAO;MAAEC,OAAO;MAAEC,UAAU,GAAG;IAAM,CAAC,GAAGL,MAAM,CAACC,YAAY;IACpE,MAAMK,iBAAiB,GAAGH,OAAO,CAACI,QAAQ,CAAC,GAAG,CAAC,GACzCJ,OAAO,CAACK,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACpBL,OAAO;IACb;IACA,MAAMM,WAAW,GAAGf,SAAS,CACzB,GAAGY,iBAAiB,IAAID,UAAU,oBACtC,CAAC;IAED,IAAI;MACA,MAAMK,GAAG,GAAG,MAAMlB,mBAAmB,CAACmB,KAAK,CACvC,KAAK,EACLF,WAAW,EACXhB,gBACJ,CAAC;MACD,IAAIiB,GAAG,CAACE,QAAQ,CAACC,MAAM,KAAK,GAAG,EAAE;QAC7B,MAAM,IAAIC,KAAK,CACX,qCAAqCJ,GAAG,CAACE,QAAQ,CAACC,MAAM,EAC5D,CAAC;MACL;MAEA,MAAME,QAAQ,GACV,OAAOL,GAAG,CAACM,IAAI,KAAK,QAAQ,GACtBC,IAAI,CAACC,KAAK,CAACR,GAAG,CAACM,IAAI,CAAC,GACpBN,GAAG,CAACM,IAAI;MAElB,MAAMG,QAAQ,GAAGJ,QAAQ,EAAEK,IAAI,GAAGpB,MAAM,CAACqB,UAAU,CAAC;MACpD,MAAMC,WAAW,GACbH,QAAQ,EAAEI,OAAO,GAAGnB,OAAO,CAACoB,WAAW,CAAC,CAAC,CAAC,IAC1CL,QAAQ,EAAEI,OAAO,EAAEE,WAAW;MAElC,IAAI,CAACH,WAAW,EAAEI,OAAO,EAAEC,MAAM,EAAE;QAC/B,OAAO3B,MAAM,CAACE,cAAc,IAAI,EAAE;MACtC;MAEA,OAAO,CAACoB,WAAW,CAACI,OAAO,IAAI,EAAE,EAAEE,MAAM,CACpCC,CAAW,IACR,CAACA,CAAC,CAACC,QAAQ,IACXD,CAAC,CAACC,QAAQ,CAACN,WAAW,CAAC,CAAC,KAAKjC,QAAQ,CAACwC,EAAE,CAACP,WAAW,CAAC,CAC7D,CAAC;IACL,CAAC,CAAC,OAAOQ,KAAK,EAAE;MACZC,OAAO,CAACC,IAAI,CACR,0CAA0C,EACzCF,KAAK,CAAWG,OACrB,CAAC;MACD,OAAOnC,MAAM,CAACE,cAAc,IAAI,EAAE;IACtC;EACJ;EAEA,MAAMkC,gBAAgBA,CAClBC,QAAgB,EAChBrC,MAAiB,EACF;IACf,IAAI,CAACA,MAAM,CAACC,YAAY,EAAE;MACtB,MAAM,IAAIa,KAAK,CAAC,oCAAoC,CAAC;IACzD;IAEA,MAAM;MAAEX,OAAO;MAAEE,UAAU,GAAG,KAAK;MAAED;IAAQ,CAAC,GAAGJ,MAAM,CAACC,YAAY;IACpE,MAAMK,iBAAiB,GAAGH,OAAO,CAACI,QAAQ,CAAC,GAAG,CAAC,GACzCJ,OAAO,CAACK,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACpBL,OAAO;IACb,MAAMmC,WAAW,GAAGlC,OAAO,CAACoB,WAAW,CAAC,CAAC;;IAEzC;IACA;IACA,OAAO9B,SAAS,CACZ,GAAGY,iBAAiB,IAAID,UAAU,IAAIL,MAAM,CAACqB,UAAU,IAAIiB,WAAW,IAAID,QAAQ,EACtF,CAAC;EACL;EAEA,MAAME,WAAWA,CACbC,IAAY,EACZC,GAAW,EACXzC,MAAiB,EACF;IACf,IAAI,CAACA,MAAM,CAACC,YAAY,EAAE,OAAO,EAAE;IAEnC,MAAM;MAAEE,OAAO;MAAEE,UAAU,GAAG;IAAM,CAAC,GAAGL,MAAM,CAACC,YAAY;IAC3D,MAAMK,iBAAiB,GAAGH,OAAO,CAACI,QAAQ,CAAC,GAAG,CAAC,GACzCJ,OAAO,CAACK,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACpBL,OAAO;;IAEb;IACA,OAAO,GAAGG,iBAAiB,IAAID,UAAU,WAAWmC,IAAI,IAAIC,GAAG,EAAE;EACrE;AACJ","ignoreList":[]}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import type { OtaConfig, OtaPatch, OtaStorageProvider } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* Storage Provider for fetching OTA updates directly from a GitHub Raw repository.
|
|
4
|
-
*
|
|
5
|
-
* Direct URL mapping:
|
|
6
|
-
* - Manifest: `${baseUrl}/${baseFolder}/ota_registry.json`
|
|
7
|
-
* - Patch file: `${baseUrl}/${baseFolder}/${appVersion}/${channel}/${filename}`
|
|
8
|
-
* - Assets: `${baseUrl}/${baseFolder}/assets/${hash}.${ext}`
|
|
9
|
-
*/
|
|
10
2
|
export declare class GitHubRawStorageProvider implements OtaStorageProvider {
|
|
11
3
|
fetchManifest(config: OtaConfig): Promise<OtaPatch[]>;
|
|
12
4
|
getUpdateFileUrl(filename: string, config: OtaConfig): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubRawStorage.d.ts","sourceRoot":"","sources":["../../../../src/ota/storage/GitHubRawStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"GitHubRawStorage.d.ts","sourceRoot":"","sources":["../../../../src/ota/storage/GitHubRawStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAuBxE,qBAAa,wBAAyB,YAAW,kBAAkB;IACzD,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAoDrD,gBAAgB,CAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,GAClB,OAAO,CAAC,MAAM,CAAC;IAkBZ,WAAW,CACb,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,GAClB,OAAO,CAAC,MAAM,CAAC;CAWrB"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,20 @@ import type { OtaConfig, OtaPatch, OtaStorageProvider } from '../types';
|
|
|
9
9
|
* - Manifest: `${baseUrl}/${baseFolder}/ota_registry.json`
|
|
10
10
|
* - Patch file: `${baseUrl}/${baseFolder}/${appVersion}/${channel}/${filename}`
|
|
11
11
|
* - Assets: `${baseUrl}/${baseFolder}/assets/${hash}.${ext}`
|
|
12
|
+
*
|
|
13
|
+
* NOTE: All manifest/registry fetches include Cache-Control: no-cache and a
|
|
14
|
+
* timestamp query param to bypass GitHub CDN's 5-minute (max-age=300) cache.
|
|
15
|
+
* Asset URLs are content-addressed (hash-named) so they are immutable and
|
|
16
|
+
* intentionally NOT cache-busted.
|
|
12
17
|
*/
|
|
18
|
+
|
|
19
|
+
/** Headers to bypass GitHub CDN cache on mutable files (registry, manifests) */
|
|
20
|
+
const NO_CACHE_HEADERS = { 'Cache-Control': 'no-cache' };
|
|
21
|
+
|
|
22
|
+
/** Append a timestamp query param to defeat CDN caches on mutable URLs */
|
|
23
|
+
const bustCache = (url: string): string =>
|
|
24
|
+
`${url}?cb=${Date.now()}`;
|
|
25
|
+
|
|
13
26
|
export class GitHubRawStorageProvider implements OtaStorageProvider {
|
|
14
27
|
async fetchManifest(config: OtaConfig): Promise<OtaPatch[]> {
|
|
15
28
|
if (!config.customServer) return config.bundledPatches || [];
|
|
@@ -18,10 +31,17 @@ export class GitHubRawStorageProvider implements OtaStorageProvider {
|
|
|
18
31
|
const normalizedBaseUrl = baseUrl.endsWith('/')
|
|
19
32
|
? baseUrl.slice(0, -1)
|
|
20
33
|
: baseUrl;
|
|
21
|
-
|
|
34
|
+
// Cache-bust: registry is mutable; must always fetch the latest version
|
|
35
|
+
const registryUrl = bustCache(
|
|
36
|
+
`${normalizedBaseUrl}/${baseFolder}/ota_registry.json`,
|
|
37
|
+
);
|
|
22
38
|
|
|
23
39
|
try {
|
|
24
|
-
const res = await ReactNativeBlobUtil.fetch(
|
|
40
|
+
const res = await ReactNativeBlobUtil.fetch(
|
|
41
|
+
'GET',
|
|
42
|
+
registryUrl,
|
|
43
|
+
NO_CACHE_HEADERS,
|
|
44
|
+
);
|
|
25
45
|
if (res.respInfo.status !== 200) {
|
|
26
46
|
throw new Error(
|
|
27
47
|
`Registry fetch failed with status ${res.respInfo.status}`,
|
|
@@ -70,7 +90,11 @@ export class GitHubRawStorageProvider implements OtaStorageProvider {
|
|
|
70
90
|
: baseUrl;
|
|
71
91
|
const channelName = channel.toLowerCase();
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
// Cache-bust: patch files are versioned by name but GitHub CDN may
|
|
94
|
+
// serve a stale 404 response for newly uploaded files within 5 min.
|
|
95
|
+
return bustCache(
|
|
96
|
+
`${normalizedBaseUrl}/${baseFolder}/${config.appVersion}/${channelName}/${filename}`,
|
|
97
|
+
);
|
|
74
98
|
}
|
|
75
99
|
|
|
76
100
|
async getAssetUrl(
|
|
@@ -85,6 +109,7 @@ export class GitHubRawStorageProvider implements OtaStorageProvider {
|
|
|
85
109
|
? baseUrl.slice(0, -1)
|
|
86
110
|
: baseUrl;
|
|
87
111
|
|
|
112
|
+
// Assets are content-addressed (hash = filename) → immutable, no cache-bust needed
|
|
88
113
|
return `${normalizedBaseUrl}/${baseFolder}/assets/${hash}.${ext}`;
|
|
89
114
|
}
|
|
90
115
|
}
|
package/tools/build_ota.sh
CHANGED
|
@@ -95,17 +95,61 @@ if [ "$ACTION" != "reset" ]; then
|
|
|
95
95
|
|
|
96
96
|
if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
|
|
97
97
|
echo " Downloading remote registry from GitHub..."
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
|
|
99
|
+
# Parse OWNER/REPO/BRANCH from the raw.githubusercontent URL or github.com URL
|
|
100
|
+
# e.g. https://raw.githubusercontent.com/owner/repo/branch OR
|
|
101
|
+
# https://github.com/owner/repo/tree/branch
|
|
102
|
+
CLEAN_URL=$(echo "$BASE_URL" | sed 's#https://raw.githubusercontent.com/##' | sed 's#https://github.com/##' | sed 's#/tree/##')
|
|
103
|
+
GH_OWNER=$(echo "$CLEAN_URL" | cut -d'/' -f1)
|
|
104
|
+
GH_REPO=$(echo "$CLEAN_URL" | cut -d'/' -f2)
|
|
105
|
+
GH_BRANCH=$(echo "$CLEAN_URL" | cut -d'/' -f3)
|
|
106
|
+
if [[ -z "$GH_BRANCH" ]]; then GH_BRANCH="main"; fi
|
|
107
|
+
|
|
108
|
+
# Prefer GitHub CLI token for authenticated requests (higher rate limit, no cache)
|
|
109
|
+
GH_CLI_TOKEN=$(gh auth token 2>/dev/null || true)
|
|
110
|
+
GH_API_TOKEN="${GH_CLI_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
|
|
111
|
+
|
|
112
|
+
# Use GitHub Contents API — NOT raw CDN — to bypass the 5-min CDN cache.
|
|
113
|
+
# The API always returns the latest committed content.
|
|
114
|
+
REGISTRY_API_URL="https://api.github.com/repos/${GH_OWNER}/${GH_REPO}/contents/${BASE_FOLDER}/ota_registry.json?ref=${GH_BRANCH}"
|
|
115
|
+
|
|
116
|
+
if [[ -n "$GH_API_TOKEN" ]]; then
|
|
117
|
+
API_RES=$(curl -s -w "\n---HTTP:%{http_code}" \
|
|
118
|
+
-H "Authorization: Bearer $GH_API_TOKEN" \
|
|
119
|
+
-H "Accept: application/vnd.github.v3+json" \
|
|
120
|
+
"$REGISTRY_API_URL" 2>/dev/null || true)
|
|
121
|
+
else
|
|
122
|
+
API_RES=$(curl -s -w "\n---HTTP:%{http_code}" \
|
|
123
|
+
-H "Accept: application/vnd.github.v3+json" \
|
|
124
|
+
"$REGISTRY_API_URL" 2>/dev/null || true)
|
|
101
125
|
fi
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
126
|
+
|
|
127
|
+
API_HTTP=$(echo "$API_RES" | grep -o '---HTTP:[0-9]*' | cut -d: -f2)
|
|
128
|
+
API_BODY=$(echo "$API_RES" | sed 's/---HTTP:.*//')
|
|
129
|
+
|
|
130
|
+
if [[ "$API_HTTP" = "200" ]]; then
|
|
131
|
+
# Decode base64 content returned by the API
|
|
132
|
+
echo "$API_BODY" | python3 -c "
|
|
133
|
+
import sys, json, base64
|
|
134
|
+
d = json.load(sys.stdin)
|
|
135
|
+
content = d.get('content', '')
|
|
136
|
+
# GitHub API returns base64 with newlines; strip them before decoding
|
|
137
|
+
decoded = base64.b64decode(content.replace('\n', ''))
|
|
138
|
+
sys.stdout.buffer.write(decoded)
|
|
139
|
+
" > "$REGISTRY_SHARED.tmp" 2>/dev/null || true
|
|
140
|
+
|
|
141
|
+
if [ -s "$REGISTRY_SHARED.tmp" ]; then
|
|
142
|
+
mv "$REGISTRY_SHARED.tmp" "$REGISTRY_SHARED"
|
|
143
|
+
echo " ✓ Remote registry synced (via GitHub API)"
|
|
144
|
+
else
|
|
145
|
+
rm -f "$REGISTRY_SHARED.tmp"
|
|
146
|
+
echo " Notice: Remote registry empty or decode failed (starting fresh with local state)."
|
|
147
|
+
fi
|
|
105
148
|
else
|
|
106
149
|
rm -f "$REGISTRY_SHARED.tmp"
|
|
107
|
-
echo " Notice: Remote registry not found on GitHub (starting fresh with local state)."
|
|
150
|
+
echo " Notice: Remote registry not found on GitHub API (HTTP $API_HTTP — starting fresh with local state)."
|
|
108
151
|
fi
|
|
152
|
+
|
|
109
153
|
else
|
|
110
154
|
if [[ -n "$UPCODE_USER" ]] && [[ -n "$UPCODE_PASS" ]]; then
|
|
111
155
|
LOGIN_RES=$(curl -s -X "POST" "$BASE_URL/public/authen/login" \
|
|
@@ -150,9 +194,12 @@ fi
|
|
|
150
194
|
download_from_minio() {
|
|
151
195
|
local REMOTE_PATH="$1"
|
|
152
196
|
local DEST="$2"
|
|
197
|
+
local CB="?cb=$(date +%s)"
|
|
153
198
|
|
|
154
199
|
if [[ "$BASE_URL" == *"github"* ]] || [[ "$BASE_URL" == *"raw.githubusercontent"* ]]; then
|
|
155
|
-
|
|
200
|
+
# Use Cache-Control: no-cache + timestamp query to bypass GitHub CDN 5-min cache
|
|
201
|
+
curl -s -f -H "Cache-Control: no-cache" -o "$DEST" "$BASE_URL/$REMOTE_PATH${CB}" 2>/dev/null || \
|
|
202
|
+
curl -s -f -H "Cache-Control: no-cache" -o "$DEST" "$BASE_URL/$(basename "$REMOTE_PATH")${CB}" 2>/dev/null
|
|
156
203
|
return $?
|
|
157
204
|
fi
|
|
158
205
|
|