leiao 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/package.json +32 -0
- package/src/analytics.ts +261 -0
- package/src/auth.ts +93 -0
- package/src/core/config.ts +122 -0
- package/src/core/index.ts +4 -0
- package/src/core/module.ts +5 -0
- package/src/core/request.ts +84 -0
- package/src/device.ts +373 -0
- package/src/identity.ts +112 -0
- package/src/index.ts +28 -0
- package/src/modules/push.ts +2 -0
- package/src/push.ts +239 -0
- package/src/runtime.ts +124 -0
- package/src/storage.ts +82 -0
- package/src/update-ui.tsx +315 -0
- package/src/updates/client.ts +50 -0
- package/src/updates/index.ts +162 -0
- package/src/updates/installer.ts +130 -0
- package/src/updates/native.ts +53 -0
- package/src/updates/state.ts +37 -0
- package/src/updates/store.ts +43 -0
- package/src/updates/types.ts +61 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {UpdateState, UpdateStateListener} from './types';
|
|
2
|
+
|
|
3
|
+
let current: UpdateState = {phase: 'idle'};
|
|
4
|
+
const listeners = new Set<UpdateStateListener>();
|
|
5
|
+
|
|
6
|
+
export function getState(): UpdateState {
|
|
7
|
+
return current;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function setState(next: UpdateState) {
|
|
11
|
+
current = next;
|
|
12
|
+
for (const listener of listeners) {
|
|
13
|
+
try {
|
|
14
|
+
listener(current);
|
|
15
|
+
} catch {
|
|
16
|
+
// 订阅方异常不影响状态机
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 订阅状态变化,立即回放一次当前状态。返回取消函数。 */
|
|
22
|
+
export function onState(listener: UpdateStateListener): () => void {
|
|
23
|
+
listeners.add(listener);
|
|
24
|
+
try {
|
|
25
|
+
listener(current);
|
|
26
|
+
} catch {
|
|
27
|
+
// 同上
|
|
28
|
+
}
|
|
29
|
+
return () => {
|
|
30
|
+
listeners.delete(listener);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resetState() {
|
|
35
|
+
current = {phase: 'idle'};
|
|
36
|
+
listeners.clear();
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {native, platformOf} from './native';
|
|
2
|
+
|
|
3
|
+
/** 打开商店:优先原生商店 scheme,回落 https。 */
|
|
4
|
+
export async function openStoreUrl(url: string): Promise<void> {
|
|
5
|
+
const target = String(url || '').trim();
|
|
6
|
+
if (!target) {
|
|
7
|
+
throw new Error('没有商店地址');
|
|
8
|
+
}
|
|
9
|
+
const Linking = native()?.Linking;
|
|
10
|
+
if (!Linking) {
|
|
11
|
+
throw new Error('当前环境不能打开商店');
|
|
12
|
+
}
|
|
13
|
+
const candidates: string[] = [];
|
|
14
|
+
const platform = platformOf();
|
|
15
|
+
if (platform === 'ios') {
|
|
16
|
+
const storeId = target.match(/id(\d+)/)?.[1] || (/^\d+$/.test(target) ? target : '');
|
|
17
|
+
if (storeId) {
|
|
18
|
+
candidates.push(`itms-apps://itunes.apple.com/app/id${storeId}`);
|
|
19
|
+
candidates.push(`https://apps.apple.com/app/id${storeId}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (platform === 'android') {
|
|
23
|
+
const pkg = target.match(/[?&]id=([^&]+)/)?.[1] || '';
|
|
24
|
+
if (pkg) {
|
|
25
|
+
candidates.push(`market://details?id=${pkg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!candidates.includes(target)) {
|
|
29
|
+
candidates.push(target);
|
|
30
|
+
}
|
|
31
|
+
for (const href of candidates) {
|
|
32
|
+
try {
|
|
33
|
+
const ok = Linking.canOpenURL ? await Linking.canOpenURL(href) : true;
|
|
34
|
+
if (ok) {
|
|
35
|
+
await Linking.openURL(href);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// 试下一个地址
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
await Linking.openURL(candidates[0] || target);
|
|
43
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type PromptTheme = 'dialog' | 'sheet' | 'banner';
|
|
2
|
+
|
|
3
|
+
/** /v2/check 的判别联合响应:要么没更新,要么热更,要么去商店。 */
|
|
4
|
+
export type HotUpdateInfo = {
|
|
5
|
+
kind: 'hot';
|
|
6
|
+
releaseId: string;
|
|
7
|
+
bundleVersion: number;
|
|
8
|
+
appVersion: string;
|
|
9
|
+
downloadUrl: string;
|
|
10
|
+
sha256: string;
|
|
11
|
+
size: number;
|
|
12
|
+
mandatory: boolean;
|
|
13
|
+
notes: string;
|
|
14
|
+
downgrade: boolean;
|
|
15
|
+
gray: boolean;
|
|
16
|
+
publishedAt?: string;
|
|
17
|
+
deviceId?: string;
|
|
18
|
+
userId?: string;
|
|
19
|
+
eventId?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type StoreUpdateInfo = {
|
|
23
|
+
kind: 'store';
|
|
24
|
+
releaseId: string;
|
|
25
|
+
appVersion: string;
|
|
26
|
+
storeUrl: string;
|
|
27
|
+
mandatory: boolean;
|
|
28
|
+
notes: string;
|
|
29
|
+
promptTheme: PromptTheme;
|
|
30
|
+
publishedAt?: string;
|
|
31
|
+
deviceId?: string;
|
|
32
|
+
userId?: string;
|
|
33
|
+
eventId?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type NoUpdateInfo = {
|
|
37
|
+
kind: 'none';
|
|
38
|
+
reason: string;
|
|
39
|
+
notes?: string;
|
|
40
|
+
deviceId?: string;
|
|
41
|
+
userId?: string;
|
|
42
|
+
eventId?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type CheckResponse = HotUpdateInfo | StoreUpdateInfo | NoUpdateInfo;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 更新状态机。热更全程静默:checking → downloading → installing → ready。
|
|
49
|
+
* store-required 是唯一需要 UI 介入的状态。
|
|
50
|
+
*/
|
|
51
|
+
export type UpdateState =
|
|
52
|
+
| {phase: 'idle'}
|
|
53
|
+
| {phase: 'checking'}
|
|
54
|
+
| {phase: 'up-to-date'; reason: string}
|
|
55
|
+
| {phase: 'downloading'; info: HotUpdateInfo; progress: number}
|
|
56
|
+
| {phase: 'installing'; info: HotUpdateInfo}
|
|
57
|
+
| {phase: 'ready'; info: HotUpdateInfo; restarting: boolean}
|
|
58
|
+
| {phase: 'store-required'; info: StoreUpdateInfo}
|
|
59
|
+
| {phase: 'error'; message: string; info?: HotUpdateInfo};
|
|
60
|
+
|
|
61
|
+
export type UpdateStateListener = (state: UpdateState) => void;
|