@codeimplants/version-control 1.0.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/.turbo/cache/27fad7e4fc35ffd5-meta.json +1 -0
- package/.turbo/cache/27fad7e4fc35ffd5.tar.zst +0 -0
- package/.turbo/cache/3a784e60ef4fd5a6-meta.json +1 -0
- package/.turbo/cache/3a784e60ef4fd5a6.tar.zst +0 -0
- package/.turbo/cache/5ff842ce2cdfa953-meta.json +1 -0
- package/.turbo/cache/5ff842ce2cdfa953.tar.zst +0 -0
- package/.turbo/cache/60a231c552bcee53-meta.json +1 -0
- package/.turbo/cache/60a231c552bcee53.tar.zst +0 -0
- package/.turbo/cache/6c7d12bf8eda1401-meta.json +1 -0
- package/.turbo/cache/6c7d12bf8eda1401.tar.zst +0 -0
- package/.turbo/cache/b1169bf3a222dd96-meta.json +1 -0
- package/.turbo/cache/b1169bf3a222dd96.tar.zst +0 -0
- package/.turbo/cache/bef44a5355c4c0bf-meta.json +1 -0
- package/.turbo/cache/bef44a5355c4c0bf.tar.zst +0 -0
- package/.turbo/cookies/3.cookie +0 -0
- package/.turbo/cookies/4.cookie +0 -0
- package/.turbo/cookies/5.cookie +0 -0
- package/.turbo/cookies/6.cookie +0 -0
- package/.turbo/cookies/7.cookie +0 -0
- package/.turbo/daemon/16cf72aee83bd4e9-turbo.log.2026-01-29 +1 -0
- package/.turbo/daemon/16cf72aee83bd4e9-turbo.log.2026-01-30 +2 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +22 -0
- package/dist/comparator.d.ts +3 -0
- package/dist/comparator.d.ts.map +1 -0
- package/dist/comparator.js +17 -0
- package/dist/detector.d.ts +5 -0
- package/dist/detector.d.ts.map +1 -0
- package/dist/detector.js +65 -0
- package/dist/engine.d.ts +3 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +33 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/sdk.d.ts +8 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +58 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +14 -0
- package/src/client.ts +26 -0
- package/src/comparator.ts +18 -0
- package/src/detector.ts +67 -0
- package/src/engine.ts +39 -0
- package/src/index.ts +2 -0
- package/src/sdk.ts +65 -0
- package/src/types.ts +39 -0
- package/tsconfig.json +17 -0
- package/turbo.json +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"27fad7e4fc35ffd5","duration":4627}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"3a784e60ef4fd5a6","duration":1325}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"5ff842ce2cdfa953","duration":1270}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"60a231c552bcee53","duration":2326}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"6c7d12bf8eda1401","duration":1164}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"b1169bf3a222dd96","duration":2680}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"bef44a5355c4c0bf","duration":2233}
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2026-01-29T08:29:08.349183Z WARN daemon_server: turborepo_lib::commands::daemon: daemon already running
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,gBAuBhD"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function fetchRules(config) {
|
|
2
|
+
const controller = new AbortController();
|
|
3
|
+
const id = setTimeout(() => controller.abort(), config.timeout || 8000);
|
|
4
|
+
const res = await fetch(`${config.backendUrl}/sdk/version/check`, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: {
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {})
|
|
9
|
+
},
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
appId: config.appId || "unknown",
|
|
12
|
+
platform: config.platform || "web",
|
|
13
|
+
currentVersion: config.version || "1.0.0",
|
|
14
|
+
environment: "prod" // Default to prod
|
|
15
|
+
}),
|
|
16
|
+
signal: controller.signal
|
|
17
|
+
});
|
|
18
|
+
clearTimeout(id);
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new Error("SDK_BACKEND_ERROR");
|
|
21
|
+
return res.json();
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparator.d.ts","sourceRoot":"","sources":["../src/comparator.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7C;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAa5D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function normalize(v) {
|
|
2
|
+
return v.split(".").map(n => parseInt(n || "0", 10));
|
|
3
|
+
}
|
|
4
|
+
export function compareVersions(a, b) {
|
|
5
|
+
const av = normalize(a);
|
|
6
|
+
const bv = normalize(b);
|
|
7
|
+
const len = Math.max(av.length, bv.length);
|
|
8
|
+
for (let i = 0; i < len; i++) {
|
|
9
|
+
const x = av[i] || 0;
|
|
10
|
+
const y = bv[i] || 0;
|
|
11
|
+
if (x > y)
|
|
12
|
+
return 1;
|
|
13
|
+
if (x < y)
|
|
14
|
+
return -1;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,wBAAsB,cAAc,IAAI,OAAO,CAAC,QAAQ,CAAC,CAoBxD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAoBrD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAoBnD"}
|
package/dist/detector.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export async function detectPlatform() {
|
|
2
|
+
var _a, _b;
|
|
3
|
+
if (typeof window === "undefined")
|
|
4
|
+
return "web";
|
|
5
|
+
const win = window;
|
|
6
|
+
// Check for Capacitor
|
|
7
|
+
if ((_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.getPlatform) {
|
|
8
|
+
const platform = win.Capacitor.getPlatform();
|
|
9
|
+
if (platform === "android" || platform === "ios")
|
|
10
|
+
return platform;
|
|
11
|
+
}
|
|
12
|
+
if (((_b = win.navigator) === null || _b === void 0 ? void 0 : _b.product) === "ReactNative") {
|
|
13
|
+
return "all";
|
|
14
|
+
}
|
|
15
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
16
|
+
if (ua.includes("android"))
|
|
17
|
+
return "android";
|
|
18
|
+
if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ipod"))
|
|
19
|
+
return "ios";
|
|
20
|
+
return "web";
|
|
21
|
+
}
|
|
22
|
+
export async function detectVersion() {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const win = window;
|
|
25
|
+
if (typeof window === "undefined")
|
|
26
|
+
return "1.0.0";
|
|
27
|
+
if ((_b = (_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.Plugins) === null || _b === void 0 ? void 0 : _b.App) {
|
|
28
|
+
try {
|
|
29
|
+
const info = await win.Capacitor.Plugins.App.getInfo();
|
|
30
|
+
if (info.version)
|
|
31
|
+
return info.version;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const versionKeys = ["APP_VERSION", "__VERSION__", "VERSION", "packageVersion"];
|
|
38
|
+
for (const key of versionKeys) {
|
|
39
|
+
if (win[key])
|
|
40
|
+
return win[key];
|
|
41
|
+
}
|
|
42
|
+
return "1.0.0";
|
|
43
|
+
}
|
|
44
|
+
export async function detectAppId() {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
const win = window;
|
|
47
|
+
if (typeof window === "undefined")
|
|
48
|
+
return "unknown.app";
|
|
49
|
+
if ((_b = (_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.Plugins) === null || _b === void 0 ? void 0 : _b.App) {
|
|
50
|
+
try {
|
|
51
|
+
const info = await win.Capacitor.Plugins.App.getInfo();
|
|
52
|
+
if (info.id)
|
|
53
|
+
return info.id;
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const idKeys = ["APP_ID", "__APP_ID__", "BUNDLE_ID", "PACKAGE_NAME"];
|
|
60
|
+
for (const key of idKeys) {
|
|
61
|
+
if (win[key])
|
|
62
|
+
return win[key];
|
|
63
|
+
}
|
|
64
|
+
return "unknown.app";
|
|
65
|
+
}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGlD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAmC9E"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { compareVersions } from "./comparator";
|
|
2
|
+
export function evaluate(rule, currentVersion) {
|
|
3
|
+
if (rule.killSwitch) {
|
|
4
|
+
return { action: "KILL_SWITCH", message: rule.message, raw: rule };
|
|
5
|
+
}
|
|
6
|
+
if (rule.maintenance) {
|
|
7
|
+
return { action: "MAINTENANCE", message: rule.message, raw: rule };
|
|
8
|
+
}
|
|
9
|
+
if (rule.forceUpdate && rule.minVersion) {
|
|
10
|
+
if (compareVersions(currentVersion, rule.minVersion) < 0) {
|
|
11
|
+
return {
|
|
12
|
+
action: "FORCE_UPDATE",
|
|
13
|
+
message: rule.message,
|
|
14
|
+
storeUrl: rule.storeUrl,
|
|
15
|
+
minVersion: rule.minVersion,
|
|
16
|
+
latestVersion: rule.latestVersion,
|
|
17
|
+
raw: rule
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (rule.softUpdate && rule.latestVersion) {
|
|
22
|
+
if (compareVersions(currentVersion, rule.latestVersion) < 0) {
|
|
23
|
+
return {
|
|
24
|
+
action: "SOFT_UPDATE",
|
|
25
|
+
message: rule.message,
|
|
26
|
+
storeUrl: rule.storeUrl,
|
|
27
|
+
latestVersion: rule.latestVersion,
|
|
28
|
+
raw: rule
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { action: "NONE", raw: rule };
|
|
33
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC"}
|
package/dist/index.js
ADDED
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VCConfig, VCDecision } from "./types";
|
|
2
|
+
export declare class VersionSDK {
|
|
3
|
+
private config;
|
|
4
|
+
static init(config: VCConfig | string, apiKey?: string): VersionSDK;
|
|
5
|
+
static check(config: VCConfig | string, apiKey?: string): Promise<VCDecision>;
|
|
6
|
+
checkVersion(): Promise<VCDecision>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK/C,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAY;IAE1B,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;WAczC,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAI7E,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;CAsC5C"}
|
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fetchRules } from "./client";
|
|
2
|
+
import { detectAppId, detectPlatform, detectVersion } from "./detector";
|
|
3
|
+
export class VersionSDK {
|
|
4
|
+
static init(config, apiKey) {
|
|
5
|
+
const sdk = new VersionSDK();
|
|
6
|
+
if (typeof config === "string") {
|
|
7
|
+
sdk.config = {
|
|
8
|
+
backendUrl: config,
|
|
9
|
+
apiKey: apiKey,
|
|
10
|
+
debug: false
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
sdk.config = config;
|
|
15
|
+
}
|
|
16
|
+
return sdk;
|
|
17
|
+
}
|
|
18
|
+
static async check(config, apiKey) {
|
|
19
|
+
return VersionSDK.init(config, apiKey).checkVersion();
|
|
20
|
+
}
|
|
21
|
+
async checkVersion() {
|
|
22
|
+
try {
|
|
23
|
+
if (!this.config.platform)
|
|
24
|
+
this.config.platform = await detectPlatform();
|
|
25
|
+
if (!this.config.version)
|
|
26
|
+
this.config.version = await detectVersion();
|
|
27
|
+
if (!this.config.appId)
|
|
28
|
+
this.config.appId = await detectAppId();
|
|
29
|
+
if (this.config.debug) {
|
|
30
|
+
console.log("[VC-SDK] Checking version for:", {
|
|
31
|
+
appId: this.config.appId,
|
|
32
|
+
platform: this.config.platform,
|
|
33
|
+
version: this.config.version
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const response = await fetchRules(this.config);
|
|
37
|
+
if (this.config.debug) {
|
|
38
|
+
console.log("[VC-SDK] Backend Response:", response);
|
|
39
|
+
}
|
|
40
|
+
// The backend now returns the evaluated decision directly
|
|
41
|
+
const decision = {
|
|
42
|
+
action: response.status || "NONE",
|
|
43
|
+
message: response.message || response.title,
|
|
44
|
+
storeUrl: response.storeUrl,
|
|
45
|
+
minVersion: response.minVersion,
|
|
46
|
+
latestVersion: response.latestVersion,
|
|
47
|
+
raw: response
|
|
48
|
+
};
|
|
49
|
+
return decision;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
if (this.config.debug) {
|
|
53
|
+
console.error("[VC-SDK] Error:", e);
|
|
54
|
+
}
|
|
55
|
+
return { action: "NONE" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type Platform = "android" | "ios" | "web" | "all";
|
|
2
|
+
export type VCAction = "NONE" | "SOFT_UPDATE" | "FORCE_UPDATE" | "MAINTENANCE" | "KILL_SWITCH" | "BLOCKED";
|
|
3
|
+
export interface VCConfig {
|
|
4
|
+
backendUrl: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
appId?: string;
|
|
7
|
+
platform?: Platform;
|
|
8
|
+
version?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface BackendRule {
|
|
13
|
+
minVersion?: string;
|
|
14
|
+
latestVersion?: string;
|
|
15
|
+
softUpdate?: boolean;
|
|
16
|
+
forceUpdate?: boolean;
|
|
17
|
+
maintenance?: boolean;
|
|
18
|
+
killSwitch?: boolean;
|
|
19
|
+
message?: string;
|
|
20
|
+
storeUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface VCDecision {
|
|
23
|
+
action: VCAction;
|
|
24
|
+
message?: string;
|
|
25
|
+
storeUrl?: string;
|
|
26
|
+
minVersion?: string;
|
|
27
|
+
latestVersion?: string;
|
|
28
|
+
raw?: any;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAEzD,MAAM,MAAM,QAAQ,GACd,MAAM,GACN,aAAa,GACb,cAAc,GACd,aAAa,GACb,aAAa,GACb,SAAS,CAAC;AAEhB,MAAM,WAAW,QAAQ;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,QAAQ,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,GAAG,CAAC;CACb"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codeimplants/version-control",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"publish:pkg": "pnpm build && pnpm publish --access public"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"typescript": "^5.4.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { VCConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
export async function fetchRules(config: VCConfig) {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const id = setTimeout(() => controller.abort(), config.timeout || 8000);
|
|
6
|
+
|
|
7
|
+
const res = await fetch(`${config.backendUrl}/sdk/version/check`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {})
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify({
|
|
14
|
+
appId: config.appId || "unknown",
|
|
15
|
+
platform: config.platform || "web",
|
|
16
|
+
currentVersion: config.version || "1.0.0",
|
|
17
|
+
environment: "prod" // Default to prod
|
|
18
|
+
}),
|
|
19
|
+
signal: controller.signal
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
clearTimeout(id);
|
|
23
|
+
|
|
24
|
+
if (!res.ok) throw new Error("SDK_BACKEND_ERROR");
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function normalize(v: string): number[] {
|
|
2
|
+
return v.split(".").map(n => parseInt(n || "0", 10));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function compareVersions(a: string, b: string): number {
|
|
6
|
+
const av = normalize(a);
|
|
7
|
+
const bv = normalize(b);
|
|
8
|
+
const len = Math.max(av.length, bv.length);
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < len; i++) {
|
|
12
|
+
const x = av[i] || 0;
|
|
13
|
+
const y = bv[i] || 0;
|
|
14
|
+
if (x > y) return 1;
|
|
15
|
+
if (x < y) return -1;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
package/src/detector.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Platform } from "./types";
|
|
2
|
+
|
|
3
|
+
export async function detectPlatform(): Promise<Platform> {
|
|
4
|
+
if (typeof window === "undefined") return "web";
|
|
5
|
+
|
|
6
|
+
const win = window as any;
|
|
7
|
+
|
|
8
|
+
// Check for Capacitor
|
|
9
|
+
if (win.Capacitor?.getPlatform) {
|
|
10
|
+
const platform = win.Capacitor.getPlatform();
|
|
11
|
+
if (platform === "android" || platform === "ios") return platform as Platform;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (win.navigator?.product === "ReactNative") {
|
|
15
|
+
return "all";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
19
|
+
if (ua.includes("android")) return "android";
|
|
20
|
+
if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ipod")) return "ios";
|
|
21
|
+
|
|
22
|
+
return "web";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function detectVersion(): Promise<string> {
|
|
26
|
+
const win = window as any;
|
|
27
|
+
|
|
28
|
+
if (typeof window === "undefined") return "1.0.0";
|
|
29
|
+
|
|
30
|
+
if (win.Capacitor?.Plugins?.App) {
|
|
31
|
+
try {
|
|
32
|
+
const info = await win.Capacitor.Plugins.App.getInfo();
|
|
33
|
+
if (info.version) return info.version;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// ignore
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const versionKeys = ["APP_VERSION", "__VERSION__", "VERSION", "packageVersion"];
|
|
40
|
+
for (const key of versionKeys) {
|
|
41
|
+
if (win[key]) return win[key];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return "1.0.0";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function detectAppId(): Promise<string> {
|
|
48
|
+
const win = window as any;
|
|
49
|
+
|
|
50
|
+
if (typeof window === "undefined") return "unknown.app";
|
|
51
|
+
|
|
52
|
+
if (win.Capacitor?.Plugins?.App) {
|
|
53
|
+
try {
|
|
54
|
+
const info = await win.Capacitor.Plugins.App.getInfo();
|
|
55
|
+
if (info.id) return info.id;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const idKeys = ["APP_ID", "__APP_ID__", "BUNDLE_ID", "PACKAGE_NAME"];
|
|
62
|
+
for (const key of idKeys) {
|
|
63
|
+
if (win[key]) return win[key];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return "unknown.app";
|
|
67
|
+
}
|
package/src/engine.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BackendRule, VCDecision } from "./types";
|
|
2
|
+
import { compareVersions } from "./comparator";
|
|
3
|
+
|
|
4
|
+
export function evaluate(rule: BackendRule, currentVersion: string): VCDecision {
|
|
5
|
+
if (rule.killSwitch) {
|
|
6
|
+
return { action: "KILL_SWITCH", message: rule.message, raw: rule };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (rule.maintenance) {
|
|
10
|
+
return { action: "MAINTENANCE", message: rule.message, raw: rule };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (rule.forceUpdate && rule.minVersion) {
|
|
14
|
+
if (compareVersions(currentVersion, rule.minVersion) < 0) {
|
|
15
|
+
return {
|
|
16
|
+
action: "FORCE_UPDATE",
|
|
17
|
+
message: rule.message,
|
|
18
|
+
storeUrl: rule.storeUrl,
|
|
19
|
+
minVersion: rule.minVersion,
|
|
20
|
+
latestVersion: rule.latestVersion,
|
|
21
|
+
raw: rule
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (rule.softUpdate && rule.latestVersion) {
|
|
27
|
+
if (compareVersions(currentVersion, rule.latestVersion) < 0) {
|
|
28
|
+
return {
|
|
29
|
+
action: "SOFT_UPDATE",
|
|
30
|
+
message: rule.message,
|
|
31
|
+
storeUrl: rule.storeUrl,
|
|
32
|
+
latestVersion: rule.latestVersion,
|
|
33
|
+
raw: rule
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { action: "NONE", raw: rule };
|
|
39
|
+
}
|
package/src/index.ts
ADDED
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { VCConfig, VCDecision } from "./types";
|
|
2
|
+
import { fetchRules } from "./client";
|
|
3
|
+
import { detectAppId, detectPlatform, detectVersion } from "./detector";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class VersionSDK {
|
|
7
|
+
private config!: VCConfig;
|
|
8
|
+
|
|
9
|
+
static init(config: VCConfig | string, apiKey?: string) {
|
|
10
|
+
const sdk = new VersionSDK();
|
|
11
|
+
if (typeof config === "string") {
|
|
12
|
+
sdk.config = {
|
|
13
|
+
backendUrl: config,
|
|
14
|
+
apiKey: apiKey,
|
|
15
|
+
debug: false
|
|
16
|
+
};
|
|
17
|
+
} else {
|
|
18
|
+
sdk.config = config;
|
|
19
|
+
}
|
|
20
|
+
return sdk;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async check(config: VCConfig | string, apiKey?: string): Promise<VCDecision> {
|
|
24
|
+
return VersionSDK.init(config, apiKey).checkVersion();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async checkVersion(): Promise<VCDecision> {
|
|
28
|
+
try {
|
|
29
|
+
if (!this.config.platform) this.config.platform = await detectPlatform();
|
|
30
|
+
if (!this.config.version) this.config.version = await detectVersion();
|
|
31
|
+
if (!this.config.appId) this.config.appId = await detectAppId();
|
|
32
|
+
|
|
33
|
+
if (this.config.debug) {
|
|
34
|
+
console.log("[VC-SDK] Checking version for:", {
|
|
35
|
+
appId: this.config.appId,
|
|
36
|
+
platform: this.config.platform,
|
|
37
|
+
version: this.config.version
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetchRules(this.config);
|
|
42
|
+
|
|
43
|
+
if (this.config.debug) {
|
|
44
|
+
console.log("[VC-SDK] Backend Response:", response);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The backend now returns the evaluated decision directly
|
|
48
|
+
const decision: VCDecision = {
|
|
49
|
+
action: response.status || "NONE",
|
|
50
|
+
message: response.message || response.title,
|
|
51
|
+
storeUrl: response.storeUrl,
|
|
52
|
+
minVersion: response.minVersion,
|
|
53
|
+
latestVersion: response.latestVersion,
|
|
54
|
+
raw: response
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return decision;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (this.config.debug) {
|
|
60
|
+
console.error("[VC-SDK] Error:", e);
|
|
61
|
+
}
|
|
62
|
+
return { action: "NONE" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type Platform = "android" | "ios" | "web" | "all";
|
|
2
|
+
|
|
3
|
+
export type VCAction =
|
|
4
|
+
| "NONE"
|
|
5
|
+
| "SOFT_UPDATE"
|
|
6
|
+
| "FORCE_UPDATE"
|
|
7
|
+
| "MAINTENANCE"
|
|
8
|
+
| "KILL_SWITCH"
|
|
9
|
+
| "BLOCKED";
|
|
10
|
+
|
|
11
|
+
export interface VCConfig {
|
|
12
|
+
backendUrl: string;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
appId?: string;
|
|
15
|
+
platform?: Platform;
|
|
16
|
+
version?: string;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BackendRule {
|
|
22
|
+
minVersion?: string;
|
|
23
|
+
latestVersion?: string;
|
|
24
|
+
softUpdate?: boolean;
|
|
25
|
+
forceUpdate?: boolean;
|
|
26
|
+
maintenance?: boolean;
|
|
27
|
+
killSwitch?: boolean;
|
|
28
|
+
message?: string;
|
|
29
|
+
storeUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VCDecision {
|
|
33
|
+
action: VCAction;
|
|
34
|
+
message?: string;
|
|
35
|
+
storeUrl?: string;
|
|
36
|
+
minVersion?: string;
|
|
37
|
+
latestVersion?: string;
|
|
38
|
+
raw?: any;
|
|
39
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*"
|
|
16
|
+
]
|
|
17
|
+
}
|