domains007 1.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 ADDED
@@ -0,0 +1,160 @@
1
+ # keep-alive-domains
2
+
3
+ Base64 编码的 API 域名配置包,用于 Android App 域名容灾。
4
+
5
+ ## 核心思路
6
+
7
+ > **用 npm + CDN 作为免费、高可用的域名配置分发通道,让 Android App 在域名挂了的时候能自我修复。**
8
+
9
+ ```
10
+ 你的操作(域名挂了时):
11
+ node encode.js → 改 domains.json → npm publish
12
+ │
13
+ ▼
14
+ jsdelivr / unpkg CDN 自动更新(1-5分钟)
15
+ │
16
+ ▼
17
+ Android App:
18
+ KeepAliveManager.request { domain -> ... }
19
+ │
20
+ ├─ 1. 用域名发起 okhttp 请求
21
+ ├─ 2. 失败 → CDN 拉取 domains.json
22
+ ├─ 3. base64 解码 payload → 获得新域名
23
+ ├─ 4. 新域名重试 → 成功
24
+ └─ 5. SharedPreferences 缓存
25
+ ```
26
+
27
+ ## 为什么这个方案成立
28
+
29
+ npm + CDN(unpkg / jsdelivr)的基础设施可用性远超你自己某个 API 域名:
30
+
31
+ | 渠道 | 被墙/挂掉概率 | 速度 | 成本 |
32
+ | ------ | :---------: | ------ | ------ |
33
+ | 你自己的 API 域名 | 高(这就是问题) | — | 服务器成本 |
34
+ | `npmjs.com` | 极低 | 全球 CDN | **免费** |
35
+ | `unpkg.com` | 极低 | Cloudflare | **免费** |
36
+ | `jsdelivr.net` | 极低(国内可用) | 全球 + 国内节点 | **免费** |
37
+
38
+ App 同时配 3 个 CDN 地址作为 fallback,几乎不可能全挂。
39
+
40
+ ## 项目结构
41
+
42
+ ```
43
+ keep-alive-domains/
44
+ ├── package.json # name, version, 无依赖
45
+ ├── domains.json # ★ 核心:base64 编码的域名配置
46
+ ├── index.js # CommonJS 入口,自动解码
47
+ ├── index.mjs # ESM 入口
48
+ ├── index.d.ts # TypeScript 类型定义
49
+ ├── encode.js # 工具脚本:编码新域名
50
+ ├── .npmignore
51
+ └── README.md
52
+ ```
53
+
54
+ ## 关键设计
55
+
56
+ | 设计点 | 实现 |
57
+ | ------ | ------ |
58
+ | **域名不暴露** | `domains.json` 的 `payload` 是 base64 编码的 JSON,CDN 上浏览看不到明文 |
59
+ | **多 CDN 容灾** | jsdelivr + jsdelivr@latest + unpkg,三个地址依次尝试 |
60
+ | **三级降级** | CDN → 本地 SharedPreferences 缓存 → 编译期内置兜底 |
61
+ | **版本比较** | `domains.json` 的 `version` 递增,App 比较后才更新本地缓存 |
62
+ | **Android 单例** | `KeepAliveManager` 是 Kotlin `object`,启动时 init 一次,全局可用 |
63
+
64
+ ## domains.json 设计
65
+
66
+ ```json
67
+ {
68
+ "version": 1,
69
+ "updatedAt": "2026-07-25T00:00:00Z",
70
+ "payload": "eyJwcmltYXJ5Ijoi..."
71
+ }
72
+ ```
73
+
74
+ `payload` 是 base64 编码的 JSON,解码后:
75
+
76
+ ```json
77
+ {
78
+ "primary": "https://api.example.com",
79
+ "fallbacks": [
80
+ "https://api2.example.com",
81
+ "https://api3.example.com"
82
+ ]
83
+ }
84
+ ```
85
+
86
+ ## Usage (Node.js)
87
+
88
+ ```js
89
+ const domains = require('keep-alive-domains');
90
+ console.log(domains.getPrimary()); // "https://api.example.com"
91
+ console.log(domains.getAll()); // ["https://api.example.com", ...]
92
+ console.log(domains.getFallbacks()); // ["https://api2.example.com", ...]
93
+ console.log(domains.getVersion()); // 1
94
+ ```
95
+
96
+ ## 发布步骤
97
+
98
+ ```bash
99
+ # 1. 修改域名
100
+ node encode.js # 先编辑 encode.js 里的 config 对象,再运行,复制输出
101
+
102
+ # 2. 更新 domains.json
103
+ # 粘贴上面输出的内容,递增 version
104
+
105
+ # 3. 发版
106
+ npm version patch # 1.0.0 → 1.0.1
107
+ npm publish
108
+ ```
109
+
110
+ ## Android: Fetch from CDN
111
+
112
+ ```
113
+ https://cdn.jsdelivr.net/npm/keep-alive-domains/domains.json
114
+ https://cdn.jsdelivr.net/npm/keep-alive-domains@latest/domains.json
115
+ https://unpkg.com/keep-alive-domains@latest/domains.json
116
+ ```
117
+
118
+ ## 进阶方向
119
+
120
+ - **签名校验**:对 payload 加 HMAC 签名,App 内置公钥验签,防止 CDN 被劫持篡改
121
+ - **多渠道**:按 `production` / `staging` 区分不同域名
122
+ - **灰度发布**:利用 npm dist-tag(`npm publish --tag beta`)实现按 tag 拉不同版本
123
+
124
+ ## License
125
+
126
+ MIT
127
+
128
+
129
+ ┌─────────────────────────────────────────────────────────┐
130
+ │ 你的本地(仅你可见) │
131
+ │ │
132
+ │ 1. 编辑 encode.js │
133
+ │ const config = { │
134
+ │ primary: 'https://你的新域名.com', │
135
+ │ fallbacks: [...] │
136
+ │ } │
137
+ │ │
138
+ │ 2. node encode.js │
139
+ │ → 输出新的 base64 payload │
140
+ │ │
141
+ │ 3. 粘贴到 domains.json,version +1 │
142
+ │ │
143
+ │ 4. npm version patch && npm publish │
144
+ │ → 发布到 npmjs.com(domains.json 被打包进去) │
145
+ │ │
146
+ │ 5. git add . && git commit && git push │
147
+ │ → 推送到 Gitee(不含 encode.js / domains.json / README) │
148
+ └─────────────────────────────────────────────────────────┘
149
+ │
150
+ ▼
151
+ npmjs.com ──▶ CDN 自动同步 ──▶ Android App 拉取新域名
152
+
153
+ 步骤 命令 说明
154
+ ① 编辑 encode.js 改 config 里的域名
155
+ ② node encode.js 生成 base64 payload
156
+ ③ 粘贴到 domains.json 替换 payload,version 递增
157
+ ④ npm version patch 包版本号自动 +1(如 1.0.1 → 1.0.2)
158
+ ⑤ npm publish 发布,domains.json 随包上传
159
+ ⑥ git add . && git commit && git push Gitee 只接收骨架代码变化
160
+ 关键点:npm publish 看的是本地文件系统,不是 git。所以 domains.json 即使被 .gitignore 排除,它仍然会被 package.json 的 "files": [...] 打包发布到 npm。Gitee 干净,npm 正常用。
package/domains.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "version": 1,
3
+ "updatedAt": "2026-08-14T11:34:38.550Z",
4
+ "payload": "eyJjaGFubmVscyI6eyJid2NqIjp7InByaW1hcnkiOiJodHRwczovL3dzLTA4MDgtbG9uZ3RpbWUtem4tYndjai15bWFwaS5rbHF0dnhkLmNvbSIsImZhbGxiYWNrcyI6WyJodHRwczovL3dzLTA4MDgtbG9uZ3RpbWUtem4tYndjai15bWFwaS5rbHF0dnhkLmNvbSJdfSwiZHlwaiI6eyJwcmltYXJ5IjoiaHR0cHM6Ly9hcGktb3RoZXIuZXhhbXBsZS5jb20iLCJmYWxsYmFja3MiOlsiaHR0cHM6Ly9hcGktb3RoZXIyLmV4YW1wbGUuY29tIl19LCJwZGQiOnsicHJpbWFyeSI6Imh0dHBzOi8vYXBpLW90aGVyLmV4YW1wbGUuY29tIiwiZmFsbGJhY2tzIjpbImh0dHBzOi8vYXBpLW90aGVyMi5leGFtcGxlLmNvbSJdfSwieGNoYXQiOnsicHJpbWFyeSI6Imh0dHBzOi8vd3MtMDgwOC1sb25ndGltZS16bi14Y2hhdC15bWFwaS5rbHF0dnhkLmNvbSIsImZhbGxiYWNrcyI6WyJodHRwczovL3dzLTA4MDgtbG9uZ3RpbWUtem4teGNoYXQteW1hcGkua2xxdHZ4ZC5jb20iXX0sInhzbSI6eyJwcmltYXJ5IjoiaHR0cHM6Ly9hcGktb3RoZXIuZXhhbXBsZS5jb20iLCJmYWxsYmFja3MiOlsiaHR0cHM6Ly9hcGktb3RoZXIyLmV4YW1wbGUuY29tIl19fX0="
5
+ }
package/index.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ declare module 'keep-alive-domains' {
2
+ /** Single channel domain config */
3
+ interface ChannelConfig {
4
+ primary: string;
5
+ fallbacks: string[];
6
+ }
7
+
8
+ /**
9
+ * Get config for a specific channel.
10
+ * @returns null if channel not found
11
+ */
12
+ export function getChannel(name: string): ChannelConfig | null;
13
+
14
+ /**
15
+ * Get all channel names.
16
+ */
17
+ export function getChannelNames(): string[];
18
+
19
+ /**
20
+ * Get the config version number for cache comparison.
21
+ */
22
+ export function getVersion(): number;
23
+
24
+ /** Config version */
25
+ export const version: number;
26
+
27
+ /** Last update timestamp (ISO 8601) */
28
+ export const updatedAt: string;
29
+
30
+ /** Raw channels map */
31
+ export const channels: Record<string, ChannelConfig>;
32
+
33
+ const _default: {
34
+ version: number;
35
+ updatedAt: string;
36
+ channels: Record<string, ChannelConfig>;
37
+ };
38
+ export default _default;
39
+ }
package/index.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * keep-alive-domains
3
+ *
4
+ * Decodes base64-encoded channel-based domain configuration from domains.json.
5
+ * Usage:
6
+ * const domains = require('keep-alive-domains');
7
+ * const ch = domains.getChannel('channel1');
8
+ * console.log(ch.primary); // "https://api.example.com"
9
+ * console.log(ch.fallbacks); // ["https://api2.example.com", ...]
10
+ */
11
+
12
+ const raw = require('./domains.json');
13
+
14
+ function decodeBase64(str) {
15
+ if (typeof Buffer !== 'undefined') {
16
+ return Buffer.from(str, 'base64').toString('utf-8');
17
+ }
18
+ if (typeof atob !== 'undefined') {
19
+ return atob(str);
20
+ }
21
+ throw new Error('No base64 decoder available');
22
+ }
23
+
24
+ function parseConfig(rawConfig) {
25
+ if (!rawConfig || !rawConfig.payload) {
26
+ throw new Error('Invalid domains.json: missing payload');
27
+ }
28
+ try {
29
+ const decoded = decodeBase64(rawConfig.payload);
30
+ const config = JSON.parse(decoded);
31
+ return {
32
+ version: rawConfig.version,
33
+ updatedAt: rawConfig.updatedAt,
34
+ ...config,
35
+ };
36
+ } catch (e) {
37
+ throw new Error('Failed to decode domain config: ' + e.message);
38
+ }
39
+ }
40
+
41
+ const config = parseConfig(raw);
42
+
43
+ /**
44
+ * Get config for a specific channel.
45
+ * @param {string} name - channel name, e.g. "channel1"
46
+ * @returns {{ primary: string, fallbacks: string[] } | null}
47
+ */
48
+ function getChannel(name) {
49
+ if (!config.channels || !config.channels[name]) return null;
50
+ return config.channels[name];
51
+ }
52
+
53
+ /**
54
+ * Get all channel names.
55
+ * @returns {string[]}
56
+ */
57
+ function getChannelNames() {
58
+ return Object.keys(config.channels || {});
59
+ }
60
+
61
+ /**
62
+ * Get the config version number (for cache comparison).
63
+ */
64
+ function getVersion() {
65
+ return config.version;
66
+ }
67
+
68
+ module.exports = {
69
+ getChannel,
70
+ getChannelNames,
71
+ getVersion,
72
+ version: config.version,
73
+ updatedAt: config.updatedAt,
74
+ // Raw access if needed
75
+ channels: config.channels || {},
76
+ };
package/index.mjs ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * keep-alive-domains — ESM entry
3
+ */
4
+
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, join } from 'node:path';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const raw = JSON.parse(readFileSync(join(__dirname, 'domains.json'), 'utf-8'));
11
+
12
+ function decodeBase64(str) {
13
+ return Buffer.from(str, 'base64').toString('utf-8');
14
+ }
15
+
16
+ function parseConfig(rawConfig) {
17
+ if (!rawConfig || !rawConfig.payload) {
18
+ throw new Error('Invalid domains.json: missing payload');
19
+ }
20
+ try {
21
+ const decoded = decodeBase64(rawConfig.payload);
22
+ const config = JSON.parse(decoded);
23
+ return { version: rawConfig.version, updatedAt: rawConfig.updatedAt, ...config };
24
+ } catch (e) {
25
+ throw new Error('Failed to decode domain config: ' + e.message);
26
+ }
27
+ }
28
+
29
+ const config = parseConfig(raw);
30
+
31
+ export function getChannel(name) {
32
+ if (!config.channels || !config.channels[name]) return null;
33
+ return config.channels[name];
34
+ }
35
+
36
+ export function getChannelNames() {
37
+ return Object.keys(config.channels || {});
38
+ }
39
+
40
+ export function getVersion() {
41
+ return config.version;
42
+ }
43
+
44
+ export const version = config.version;
45
+ export const updatedAt = config.updatedAt;
46
+ export const channels = config.channels || {};
47
+ export default config;
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "domains007",
3
+ "version": "1.0.1",
4
+ "description": "API domain configuration with base64 encoding for Android app failover",
5
+ "main": "index.js",
6
+ "module": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.mjs",
11
+ "index.d.ts",
12
+ "domains.json"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://gitee.com/ggkk23/mydomains.git"
17
+ },
18
+ "publishConfig": {
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "keywords": [
22
+ "keep-alive",
23
+ "domain",
24
+ "failover",
25
+ "api",
26
+ "android"
27
+ ],
28
+ "license": "MIT"
29
+ }