+1
-1
atproto-notifications/index.html
+1
-1
atproto-notifications/index.html
+30
-6
atproto-notifications/src/components/Feed.tsx
+30
-6
atproto-notifications/src/components/Feed.tsx
···
1
1
import { useEffect, useState } from 'react';
2
-
import { getNotifications } from '../db';
2
+
import { getNotifications, getSecondary } from '../db';
3
+
4
+
function Asdf({ inc, secondary }) {
5
+
const [secondaries, setSecondaries] = useState([]);
6
+
useEffect(() => {
7
+
(async () => {
8
+
const secondaries = await getSecondary(secondary);
9
+
secondaries.sort((a, b) => b.unread - a.unread);
10
+
setSecondaries(secondaries);
11
+
})();
12
+
}, [inc, secondary]);
13
+
14
+
return (
15
+
<div>
16
+
<p>secondaries: ({secondaries.length})</p>
17
+
{secondaries.map(a => (
18
+
<p key={a.k}>asdf {a.k} ({a.unread}/{a.total})</p>
19
+
))}
20
+
</div>
21
+
);
22
+
}
3
23
4
24
export function Feed() {
5
25
···
16
36
// this could be combined with the broadcast thing above, but for now just chain deps
17
37
const [feed, setFeed] = useState([]);
18
38
useEffect(() => {
19
-
(async () => setFeed((await getNotifications())))();
39
+
(async () => setFeed(await getNotifications()))();
20
40
}, [inc]);
21
41
22
42
if (feed.length === 0) {
23
43
return 'no notifications loaded';
24
44
}
25
-
return feed.map(([k, n]) => (
26
-
<p key={k}>{k}: {n.source} ({n.source_record}) <code>{JSON.stringify(n)}</code></p>
27
-
));
28
-
45
+
return (
46
+
<div className="feed">
47
+
<Asdf inc={inc} secondary='source' />
48
+
{feed.map(([k, n]) => (
49
+
<p key={k}>{k}: {n.source} ({n.source_record}) <code>{JSON.stringify(n)}</code></p>
50
+
))}
51
+
</div>
52
+
);
29
53
}
+22
-6
atproto-notifications/src/db.ts
+22
-6
atproto-notifications/src/db.ts
···
18
18
// primary store for notifications
19
19
try {
20
20
// upgrade is a reset: entirely remove the store (ignore errors if it didn't exist)
21
-
db.deleteObjectStore('notifs');
21
+
db.deleteObjectStore(NOTIFICATIONS);
22
22
} catch (e) {}
23
23
const notifStore = db.createObjectStore(NOTIFICATIONS, {
24
-
key: 'id',
24
+
keyPath: 'id',
25
25
autoIncrement: true,
26
26
});
27
27
// subject prob doesn't need an index, could just query constellation
···
44
44
db.deleteObjectStore(secondary);
45
45
} catch (e) {}
46
46
const store = db.createObjectStore(secondary, {
47
-
key: 'k',
47
+
keyPath: 'k',
48
48
});
49
49
store.createIndex('total', 'total', { unique: false });
50
50
store.createIndex('unread', 'unread', { unique: false });
51
51
}
52
52
53
-
}, 3);
53
+
}, 4);
54
54
55
55
export async function insertNotification(notif: {
56
56
subject: String,
···
71
71
const store = tx.objectStore(secondary);
72
72
const key = secondary === 'all' ? 'all' : notif[secondary];
73
73
store.get(key).onsuccess = ev => {
74
-
let count = ev.target.result ?? { total: 0, unread: 0 };
74
+
let count = ev.target.result ?? {
75
+
k: key,
76
+
total: 0,
77
+
unread: 0,
78
+
};
75
79
count.total += 1;
76
80
count.unread += 1;
77
-
store.put(count, key);
81
+
store.put(count);
78
82
};
79
83
}
80
84
···
104
108
}
105
109
});
106
110
}
111
+
112
+
export async function getSecondary(secondary) {
113
+
const db = await getDB();
114
+
const obj = db
115
+
.transaction([secondary])
116
+
.objectStore(secondary)
117
+
.getAll();
118
+
return new Promise((resolve, reject) => {
119
+
obj.onerror = () => reject(obj.error);
120
+
obj.onsuccess = ev => resolve(ev.target.result);
121
+
});
122
+
}
-1
atproto-notifications/src/service-worker.ts
-1
atproto-notifications/src/service-worker.ts
···
36
36
// TODO: user pref for alt client -> prefer that client's icon
37
37
const lex = lexicons[appPrefix];
38
38
const icon = lex?.clients[0]?.icon;
39
-
console.log('app', app, 'lex', lex, lexicons);
40
39
const title = lex?.known_sources[source.slice(app.length + 1)] ?? source;
41
40
const body = `from @${handle} on ${lex?.name ?? app}`;
42
41
+1
-1
atproto-notifications/vite.config.ts
+1
-1
atproto-notifications/vite.config.ts
+9
-1
server/index.js
+9
-1
server/index.js
···
25
25
if (!subs.has(did)) {
26
26
subs.set(did, []);
27
27
}
28
+
sub.t = new Date();
28
29
subs.get(did).push(sub);
29
30
updateSubs();
30
31
};
···
70
71
}
71
72
72
73
const expiredSubs = [];
73
-
for (const sub of subs.get(did) ?? []) { try {
74
+
const now = new Date();
75
+
for (const sub of subs.get(did) ?? []) {
76
+
try {
77
+
if (now - sub.t < 1500) {
78
+
console.warn('skipping for rate limit');
79
+
continue;
80
+
}
81
+
sub.t = now;
74
82
await webpush.sendNotification(sub, JSON.stringify({ subject, source, source_record }));
75
83
} catch (err) {
76
84
if (400 <= err.statusCode && err.statusCode < 500) {