dsh-pocket 1.0.2 → 1.0.4
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/client/api.js +1 -0
- package/client/client.js +39 -9
- package/client/index.jsx +45 -12
- package/lib/push.mjs +19 -1
- package/lib/web-rpc.js +5 -1
- package/package.json +1 -1
package/client/api.js
CHANGED
package/client/client.js
CHANGED
|
@@ -41,7 +41,8 @@ var POCKET_ENDPOINTS = Object.freeze({
|
|
|
41
41
|
pushVapidKey: "push.vapidKey",
|
|
42
42
|
pushSubscribe: "push.subscribe",
|
|
43
43
|
pushUnsubscribe: "push.unsubscribe",
|
|
44
|
-
pushStatus: "push.status"
|
|
44
|
+
pushStatus: "push.status",
|
|
45
|
+
pushSetEnabled: "push.setEnabled"
|
|
45
46
|
});
|
|
46
47
|
function redactStatus(s) {
|
|
47
48
|
return {
|
|
@@ -1285,6 +1286,7 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
1285
1286
|
const [status, setStatus] = (0, import_react2.useState)(null);
|
|
1286
1287
|
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1287
1288
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
1289
|
+
const [pushEnabled, setPushEnabled] = (0, import_react2.useState)(true);
|
|
1288
1290
|
const [pushState, setPushState] = (0, import_react2.useState)("checking");
|
|
1289
1291
|
const call = async (endpoint, payload) => {
|
|
1290
1292
|
const res = await rpcCall(endpoint, payload);
|
|
@@ -1303,8 +1305,31 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
1303
1305
|
return () => clearInterval(t);
|
|
1304
1306
|
}, []);
|
|
1305
1307
|
(0, import_react2.useEffect)(() => {
|
|
1306
|
-
|
|
1308
|
+
call(POCKET_ENDPOINTS.pushStatus, {}).then((s) => setPushEnabled(s.enabled)).catch(() => {
|
|
1309
|
+
});
|
|
1307
1310
|
}, []);
|
|
1311
|
+
const enablePush = async () => {
|
|
1312
|
+
await call(POCKET_ENDPOINTS.pushSetEnabled, { enabled: true });
|
|
1313
|
+
setPushEnabled(true);
|
|
1314
|
+
setPushState(await setupPush(rpcCall));
|
|
1315
|
+
};
|
|
1316
|
+
const disablePush = async () => {
|
|
1317
|
+
try {
|
|
1318
|
+
if ("serviceWorker" in navigator) {
|
|
1319
|
+
const reg = await navigator.serviceWorker.getRegistration("/pocket-sw.js");
|
|
1320
|
+
const sub = await reg?.pushManager?.getSubscription();
|
|
1321
|
+
if (sub) {
|
|
1322
|
+
const endpoint = sub.endpoint;
|
|
1323
|
+
await sub.unsubscribe();
|
|
1324
|
+
await call(POCKET_ENDPOINTS.pushUnsubscribe, { endpoint });
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
} catch {
|
|
1328
|
+
}
|
|
1329
|
+
await call(POCKET_ENDPOINTS.pushSetEnabled, { enabled: false });
|
|
1330
|
+
setPushEnabled(false);
|
|
1331
|
+
setPushState("off");
|
|
1332
|
+
};
|
|
1308
1333
|
const startTunnel = async () => {
|
|
1309
1334
|
setBusy(true);
|
|
1310
1335
|
setError(null);
|
|
@@ -1365,15 +1390,20 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
1365
1390
|
(0, import_react2.createElement)("div", { style: styles.warn, marginTop: 8 }, "\u26A0\uFE0F DSH \u80FD\u6267\u884C\u7535\u8111\u4EE3\u7801\uFF1A\u4E8C\u7EF4\u7801/URL \u5C31\u662F\u94A5\u5319\uFF0C\u8BF7\u52FF\u53D1\u7ED9\u522B\u4EBA")
|
|
1366
1391
|
)
|
|
1367
1392
|
),
|
|
1368
|
-
// Web Push 状态
|
|
1393
|
+
// Web Push 状态 + 开关
|
|
1369
1394
|
(0, import_react2.createElement)(
|
|
1370
1395
|
"div",
|
|
1371
1396
|
{ style: styles.block },
|
|
1372
|
-
(0, import_react2.createElement)(
|
|
1397
|
+
(0, import_react2.createElement)(
|
|
1398
|
+
"div",
|
|
1399
|
+
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between" } },
|
|
1400
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 13 } }, "\u{1F514} \u63A8\u9001\u901A\u77E5 | Push notifications"),
|
|
1401
|
+
pushEnabled ? (0, import_react2.createElement)("button", { style: styles.btn, onClick: disablePush }, "\u5173\u95ED | Off") : (0, import_react2.createElement)("button", { style: styles.primary, onClick: enablePush }, "\u5F00\u542F | On")
|
|
1402
|
+
),
|
|
1373
1403
|
(0, import_react2.createElement)(
|
|
1374
1404
|
"div",
|
|
1375
1405
|
{ style: styles.muted },
|
|
1376
|
-
pushState === "on" ? "\u5DF2\u5F00\u542F\uFF1Aagent \u8DD1\u5B8C/\u51FA\u9519\u65F6\u624B\u673A\u6536\u5230\u901A\u77E5 | on: notified when tasks finish or fail" : pushState === "unsupported" ? "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u63A8\u9001
|
|
1406
|
+
!pushEnabled ? "\u5DF2\u5173\u95ED\uFF1Aagent \u8DD1\u5B8C\u4E0D\u4F1A\u63A8\u9001 | off: no notifications" : pushState === "on" ? "\u5DF2\u5F00\u542F\uFF1Aagent \u8DD1\u5B8C/\u51FA\u9519\u65F6\u624B\u673A\u6536\u5230\u901A\u77E5 | on: notified when tasks finish or fail" : pushState === "unsupported" ? "\u5DF2\u5F00\u542F\uFF08\u4F46\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u63A8\u9001\uFF09| on, but this browser does not support push" : pushState === "insecure" ? "\u5DF2\u5F00\u542F\uFF0C\u4F46\u5F53\u524D\u8DEF\u5F84\u4E0D\u662F HTTPS\u2014\u2014\u63A8\u9001\u9700\u8981\u516C\u7F51\u96A7\u9053\u6216 localhost | on, but push needs HTTPS (public tunnel) or localhost" : pushState === "checking" ? "\u68C0\u67E5\u4E2D\u2026 | checking\u2026" : "\u63A8\u9001\u672A\u751F\u6548 | push not active"
|
|
1377
1407
|
)
|
|
1378
1408
|
),
|
|
1379
1409
|
error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null
|
|
@@ -1383,13 +1413,13 @@ function apply(ctx) {
|
|
|
1383
1413
|
mobileApply(ctx);
|
|
1384
1414
|
const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(POCKET_RPC_CHANNEL, endpoint, payload, signal);
|
|
1385
1415
|
ctx.slots.inject(
|
|
1386
|
-
"settings.
|
|
1416
|
+
"settings.section",
|
|
1387
1417
|
() => ctx.slots.register(
|
|
1388
1418
|
{
|
|
1389
|
-
name: "settings.
|
|
1419
|
+
name: "settings.section",
|
|
1390
1420
|
id: "pocket",
|
|
1391
|
-
order:
|
|
1392
|
-
label: "\u624B\u673A\u8BBF\u95EE",
|
|
1421
|
+
order: 1,
|
|
1422
|
+
label: () => "\u624B\u673A\u8BBF\u95EE",
|
|
1393
1423
|
inject: () => ({ rpcCall })
|
|
1394
1424
|
},
|
|
1395
1425
|
PocketSettingsTab
|
package/client/index.jsx
CHANGED
|
@@ -58,6 +58,7 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
58
58
|
const [status, setStatus] = useState(null);
|
|
59
59
|
const [busy, setBusy] = useState(false);
|
|
60
60
|
const [error, setError] = useState(null);
|
|
61
|
+
const [pushEnabled, setPushEnabled] = useState(true); // 宿主开关
|
|
61
62
|
const [pushState, setPushState] = useState('checking'); // checking|on|unsupported|insecure|off
|
|
62
63
|
|
|
63
64
|
const call = async (endpoint, payload) => {
|
|
@@ -76,11 +77,36 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
76
77
|
return () => clearInterval(t);
|
|
77
78
|
}, []);
|
|
78
79
|
|
|
79
|
-
//
|
|
80
|
+
// 读取宿主开关状态
|
|
80
81
|
useEffect(() => {
|
|
81
|
-
|
|
82
|
+
call(POCKET_ENDPOINTS.pushStatus, {}).then((s) => setPushEnabled(s.enabled)).catch(() => {});
|
|
82
83
|
}, []);
|
|
83
84
|
|
|
85
|
+
// 开启推送:宿主开关开 + 浏览器订阅(安全上下文才有效)
|
|
86
|
+
const enablePush = async () => {
|
|
87
|
+
await call(POCKET_ENDPOINTS.pushSetEnabled, { enabled: true });
|
|
88
|
+
setPushEnabled(true);
|
|
89
|
+
setPushState(await setupPush(rpcCall));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// 关闭推送:取消浏览器订阅 + 宿主开关关
|
|
93
|
+
const disablePush = async () => {
|
|
94
|
+
try {
|
|
95
|
+
if ('serviceWorker' in navigator) {
|
|
96
|
+
const reg = await navigator.serviceWorker.getRegistration('/pocket-sw.js');
|
|
97
|
+
const sub = await reg?.pushManager?.getSubscription();
|
|
98
|
+
if (sub) {
|
|
99
|
+
const endpoint = sub.endpoint;
|
|
100
|
+
await sub.unsubscribe();
|
|
101
|
+
await call(POCKET_ENDPOINTS.pushUnsubscribe, { endpoint });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch { /* 忽略 */ }
|
|
105
|
+
await call(POCKET_ENDPOINTS.pushSetEnabled, { enabled: false });
|
|
106
|
+
setPushEnabled(false);
|
|
107
|
+
setPushState('off');
|
|
108
|
+
};
|
|
109
|
+
|
|
84
110
|
const startTunnel = async () => {
|
|
85
111
|
setBusy(true);
|
|
86
112
|
setError(null);
|
|
@@ -134,15 +160,21 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
134
160
|
),
|
|
135
161
|
),
|
|
136
162
|
|
|
137
|
-
// Web Push 状态
|
|
163
|
+
// Web Push 状态 + 开关
|
|
138
164
|
h('div', { style: styles.block },
|
|
139
|
-
h('div', { style: {
|
|
165
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
|
|
166
|
+
h('div', { style: { fontWeight: 600, fontSize: 13 } }, '🔔 推送通知 | Push notifications'),
|
|
167
|
+
pushEnabled
|
|
168
|
+
? h('button', { style: styles.btn, onClick: disablePush }, '关闭 | Off')
|
|
169
|
+
: h('button', { style: styles.primary, onClick: enablePush }, '开启 | On'),
|
|
170
|
+
),
|
|
140
171
|
h('div', { style: styles.muted },
|
|
141
|
-
|
|
142
|
-
: pushState === '
|
|
143
|
-
: pushState === '
|
|
172
|
+
!pushEnabled ? '已关闭:agent 跑完不会推送 | off: no notifications'
|
|
173
|
+
: pushState === 'on' ? '已开启:agent 跑完/出错时手机收到通知 | on: notified when tasks finish or fail'
|
|
174
|
+
: pushState === 'unsupported' ? '已开启(但当前浏览器不支持推送)| on, but this browser does not support push'
|
|
175
|
+
: pushState === 'insecure' ? '已开启,但当前路径不是 HTTPS——推送需要公网隧道或 localhost | on, but push needs HTTPS (public tunnel) or localhost'
|
|
144
176
|
: pushState === 'checking' ? '检查中… | checking…'
|
|
145
|
-
: '
|
|
177
|
+
: '推送未生效 | push not active'),
|
|
146
178
|
),
|
|
147
179
|
|
|
148
180
|
error ? h('div', { style: { color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 12, marginTop: 8 } }, `❌ ${error}`) : null,
|
|
@@ -156,13 +188,14 @@ export function apply(ctx) {
|
|
|
156
188
|
const rpcCall = (endpoint, payload, signal) =>
|
|
157
189
|
ctx.connection.rpc.call(POCKET_RPC_CHANNEL, endpoint, payload, signal);
|
|
158
190
|
|
|
159
|
-
|
|
191
|
+
// 设置一级入口(与 通用设置/模型/插件 同级,order 1 = 通用之后、最外层)
|
|
192
|
+
ctx.slots.inject('settings.section', () =>
|
|
160
193
|
ctx.slots.register(
|
|
161
194
|
{
|
|
162
|
-
name: 'settings.
|
|
195
|
+
name: 'settings.section',
|
|
163
196
|
id: 'pocket',
|
|
164
|
-
order:
|
|
165
|
-
label: '手机访问',
|
|
197
|
+
order: 1,
|
|
198
|
+
label: () => '手机访问',
|
|
166
199
|
inject: () => ({ rpcCall }),
|
|
167
200
|
},
|
|
168
201
|
PocketSettingsTab,
|
package/lib/push.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { join, dirname } from 'node:path';
|
|
|
17
17
|
const DATA_REL = join('dsh-pocket', 'push');
|
|
18
18
|
const VAPID_FILE = 'vapid.json';
|
|
19
19
|
const SUBS_FILE = 'subscriptions.json';
|
|
20
|
+
const SETTINGS_FILE = 'settings.json';
|
|
20
21
|
|
|
21
22
|
function defaultWebPush() {
|
|
22
23
|
// 动态 require(web-push 是 CJS)
|
|
@@ -42,12 +43,21 @@ export async function createPushService({
|
|
|
42
43
|
const dir = join(dshHome, DATA_REL);
|
|
43
44
|
const vapidPath = join(dir, VAPID_FILE);
|
|
44
45
|
const subsPath = join(dir, SUBS_FILE);
|
|
46
|
+
const settingsPath = join(dir, SETTINGS_FILE);
|
|
45
47
|
const mkdirFn = internals.mkdir ?? mkdir;
|
|
46
48
|
const read = internals.read ?? readFile;
|
|
47
49
|
const write = internals.write ?? writeFile;
|
|
48
50
|
|
|
49
51
|
await mkdirFn(dir, { recursive: true });
|
|
50
52
|
|
|
53
|
+
// 开关状态:默认开,持久化到 settings.json
|
|
54
|
+
let enabled = true;
|
|
55
|
+
try {
|
|
56
|
+
const s = JSON.parse(await read(settingsPath, 'utf8'));
|
|
57
|
+
if (typeof s.enabled === 'boolean') enabled = s.enabled;
|
|
58
|
+
} catch { /* 默认开 */ }
|
|
59
|
+
if (internals.enabled !== undefined) enabled = internals.enabled === true;
|
|
60
|
+
|
|
51
61
|
// VAPID 密钥:生成一次,持久化
|
|
52
62
|
let vapid;
|
|
53
63
|
try {
|
|
@@ -73,6 +83,14 @@ export async function createPushService({
|
|
|
73
83
|
return {
|
|
74
84
|
vapidPublicKey: () => vapid.publicKey,
|
|
75
85
|
count: () => subs.size,
|
|
86
|
+
isEnabled: () => enabled,
|
|
87
|
+
|
|
88
|
+
/** 设置推送总开关(持久化)。 */
|
|
89
|
+
async setEnabled(value) {
|
|
90
|
+
enabled = value === true;
|
|
91
|
+
await write(settingsPath, JSON.stringify({ enabled }, null, 2) + '\n', { mode: 0o600 });
|
|
92
|
+
return enabled;
|
|
93
|
+
},
|
|
76
94
|
|
|
77
95
|
/** 记录浏览器订阅。 */
|
|
78
96
|
async subscribe(subscription) {
|
|
@@ -98,7 +116,7 @@ export async function createPushService({
|
|
|
98
116
|
* @param {object} [opts.data] 额外载荷
|
|
99
117
|
*/
|
|
100
118
|
async notify({ title, body = '', url = '/', data = {} }) {
|
|
101
|
-
if (subs.size === 0) return 0;
|
|
119
|
+
if (!enabled || subs.size === 0) return 0;
|
|
102
120
|
const payload = JSON.stringify({ title, body, url, ...data });
|
|
103
121
|
let sent = 0;
|
|
104
122
|
for (const [endpoint, sub] of [...subs.entries()]) {
|
package/lib/web-rpc.js
CHANGED
|
@@ -49,7 +49,11 @@ export function installPocketRpc(ctx, { service, push, log = console }) {
|
|
|
49
49
|
return ok({ removed, count: push.count() });
|
|
50
50
|
}
|
|
51
51
|
if (endpoint === POCKET_ENDPOINTS.pushStatus) {
|
|
52
|
-
return ok({ enabled: push.
|
|
52
|
+
return ok({ enabled: push.isEnabled(), count: push.count() });
|
|
53
|
+
}
|
|
54
|
+
if (endpoint === POCKET_ENDPOINTS.pushSetEnabled) {
|
|
55
|
+
const enabled = await push.setEnabled(payload?.enabled === true);
|
|
56
|
+
return ok({ enabled, count: push.count() });
|
|
53
57
|
}
|
|
54
58
|
return fail('bad-request', `Unknown endpoint: ${endpoint}`);
|
|
55
59
|
} catch (err) {
|
package/package.json
CHANGED