- 双桥:Mac桥(Claude Code异步) + IDC桥(DeepSeek同步) - 推送:自制定制Animated横幅 + 震动,替代Alert弹窗 - APNs:Key已创建(3VJ5X6V9Q8),桥端apns.js已写好 - 图标:已替换为定制图标 - BlurView/notifee:iOS 26兼容问题已回退 - 记忆库更新 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
65 lines
1.8 KiB
JavaScript
65 lines
1.8 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const jwt = require('jsonwebtoken');
|
|
const http2 = require('http2');
|
|
|
|
const TEAM_ID = 'ZZMLW32PHH';
|
|
const KEY_ID = '3VJ5X6V9Q8';
|
|
const BUNDLE_ID = 'com.shenyan.room';
|
|
const KEY_PATH = path.join(__dirname, 'AuthKey_3VJ5X6V9Q8.p8');
|
|
|
|
// cache JWT for ~50min (max 1hr)
|
|
let cachedToken = null;
|
|
let tokenExpiry = 0;
|
|
|
|
function getJWT() {
|
|
if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
|
|
const key = fs.readFileSync(KEY_PATH, 'utf-8');
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const payload = { iss: TEAM_ID, iat: now };
|
|
const headers = { alg: 'ES256', kid: KEY_ID };
|
|
cachedToken = jwt.sign(payload, key, { algorithm: 'ES256', header: headers, expiresIn: '50m' });
|
|
tokenExpiry = now + 3000; // 50 min
|
|
return cachedToken;
|
|
}
|
|
|
|
function sendAPNs(deviceToken, title, body) {
|
|
return new Promise((resolve, reject) => {
|
|
const payload = JSON.stringify({
|
|
aps: {
|
|
alert: { title: title || '沈晏', body: body || '' },
|
|
sound: 'default',
|
|
badge: 1,
|
|
},
|
|
});
|
|
|
|
const client = http2.connect('https://api.sandbox.push.apple.com:443');
|
|
client.on('error', err => reject(err));
|
|
|
|
const req = client.request({
|
|
':method': 'POST',
|
|
':path': `/3/device/${deviceToken}`,
|
|
'authorization': `bearer ${getJWT()}`,
|
|
'apns-topic': BUNDLE_ID,
|
|
'apns-push-type': 'alert',
|
|
'apns-expiration': '0',
|
|
});
|
|
|
|
req.on('response', (headers) => {
|
|
const status = headers[':status'];
|
|
let data = '';
|
|
req.on('data', c => data += c);
|
|
req.on('end', () => {
|
|
client.close();
|
|
if (status === 200) resolve({ ok: true, status });
|
|
else reject(new Error(`APNs ${status}: ${data}`));
|
|
});
|
|
});
|
|
|
|
req.on('error', err => { client.close(); reject(err); });
|
|
req.end(payload);
|
|
});
|
|
}
|
|
|
|
module.exports = { sendAPNs };
|