octiqflow-linux-x64 0.1.52
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 +3 -0
- package/bin/octiq-server +0 -0
- package/bin/v2/apple-touch-icon.png +0 -0
- package/bin/v2/assets/clike-B9uivgTg.js +1 -0
- package/bin/v2/assets/diff-DbItnlRl.js +1 -0
- package/bin/v2/assets/dockerfile-ZL4hHokT.js +1 -0
- package/bin/v2/assets/go-CTD25R5P.js +1 -0
- package/bin/v2/assets/index-B3amz3YD.js +3 -0
- package/bin/v2/assets/index-B6nZyhSd.js +1 -0
- package/bin/v2/assets/index-BJPOH430.js +1 -0
- package/bin/v2/assets/index-BkI_sJNx.js +6 -0
- package/bin/v2/assets/index-C3qkbCLt.js +1 -0
- package/bin/v2/assets/index-CAT3p_Ey.js +1 -0
- package/bin/v2/assets/index-CkSsGSUZ.js +13 -0
- package/bin/v2/assets/index-CrP4aOLs.js +1 -0
- package/bin/v2/assets/index-Czi1vLYr.js +1 -0
- package/bin/v2/assets/index-DecdvRvX.js +1 -0
- package/bin/v2/assets/index-DpgjZUbN.css +1 -0
- package/bin/v2/assets/index-MhXUMNK1.js +291 -0
- package/bin/v2/assets/index-nLNkR2xP.js +2 -0
- package/bin/v2/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/bin/v2/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/bin/v2/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/bin/v2/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/bin/v2/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/bin/v2/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/bin/v2/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/bin/v2/assets/lua-VAEuO923.js +1 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
- package/bin/v2/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
- package/bin/v2/assets/powershell-CFHJl5sT.js +1 -0
- package/bin/v2/assets/properties-C78fOPTZ.js +1 -0
- package/bin/v2/assets/ruby-B2Rjki9n.js +1 -0
- package/bin/v2/assets/shell-CjFT_Tl9.js +1 -0
- package/bin/v2/assets/swift-BzpIVaGY.js +1 -0
- package/bin/v2/assets/toml-Bm5Em-hy.js +1 -0
- package/bin/v2/assets/xml-BQQZnNBQ.js +1 -0
- package/bin/v2/favicon.ico +0 -0
- package/bin/v2/fonts/lxgw-wenkai-screen/LICENSE +21 -0
- package/bin/v2/fonts/lxgw-wenkai-screen/OFL.txt +91 -0
- package/bin/v2/fonts/lxgw-wenkai-screen/README.md +11 -0
- package/bin/v2/icon-192.png +0 -0
- package/bin/v2/icon-32.png +0 -0
- package/bin/v2/icon-512.png +0 -0
- package/bin/v2/index.html +54 -0
- package/bin/v2/manifest.webmanifest +15 -0
- package/bin/v2/sw.js +189 -0
- package/package.json +23 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# LXGW WenKai Screen
|
|
2
|
+
|
|
3
|
+
OctiqFlow bundles the Screen face from `lxgw-wenkai-screen-webfont@1.7.0`
|
|
4
|
+
(upstream font version v1.250.2), using its WOFF2 Unicode subsets for on-demand loading.
|
|
5
|
+
|
|
6
|
+
- Font project: https://github.com/lxgw/LxgwWenKai-Screen
|
|
7
|
+
- Webfont package: https://github.com/chawyehsu/lxgw-wenkai-webfont
|
|
8
|
+
- Font license: SIL Open Font License 1.1, reproduced in `OFL.txt`.
|
|
9
|
+
- Webfont packaging license: MIT, reproduced in `LICENSE`.
|
|
10
|
+
|
|
11
|
+
Only `lxgwwenkaiscreen.css` is imported; the package's other faces are not bundled.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta
|
|
6
|
+
name="viewport"
|
|
7
|
+
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
|
8
|
+
/>
|
|
9
|
+
<meta name="theme-color" content="#1c1c1e" />
|
|
10
|
+
|
|
11
|
+
<!-- Relative hrefs, like the asset URLs: `base: "./"` in vite.config.ts
|
|
12
|
+
keeps the bundle working wherever it is mounted, and these follow the
|
|
13
|
+
same rule so nothing depends on the page sitting at the root. -->
|
|
14
|
+
<link rel="icon" href="./favicon.ico" sizes="any" />
|
|
15
|
+
<link rel="icon" type="image/png" href="./icon-32.png" sizes="32x32" />
|
|
16
|
+
|
|
17
|
+
<!-- Reached from a phone through a tunnel, so it is meant to be added to a
|
|
18
|
+
home screen. A tab cannot be told to shrink its own toolbar, so the
|
|
19
|
+
home screen is the only way the address bar comes off the screen.
|
|
20
|
+
|
|
21
|
+
`standalone` is chromeless: no address bar, and no navigation controls
|
|
22
|
+
either. `minimal-ui` is the mode that keeps native back/reload, and it
|
|
23
|
+
was tried here — iOS did not give the controls, so the reload lives in
|
|
24
|
+
our own top bar instead (`InstalledReload`). Do not reach for
|
|
25
|
+
`minimal-ui` again without testing it on a real phone; and note that
|
|
26
|
+
iOS caches the manifest at INSTALL time, so testing it means deleting
|
|
27
|
+
the home-screen icon and adding it again, not reloading.
|
|
28
|
+
|
|
29
|
+
The status-bar-style is not part of that choice and must not be
|
|
30
|
+
removed with it: it is what puts the page UNDER the status bar. The
|
|
31
|
+
top bar is sized `48px + env(safe-area-inset-top)` (styles.css), so
|
|
32
|
+
dropping it moves the inset out from under the layout and the bar
|
|
33
|
+
comes out wrong.
|
|
34
|
+
|
|
35
|
+
iOS reads none of the manifest for the ICON: it wants its own, and it
|
|
36
|
+
composites transparency onto black, so this one is already flattened
|
|
37
|
+
onto the app's own background. -->
|
|
38
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
39
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
40
|
+
<!-- The same thing, un-prefixed. Chrome/Android warns on the Apple name
|
|
41
|
+
alone, and reads this one. -->
|
|
42
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
43
|
+
<meta name="apple-mobile-web-app-title" content="OctiqFlow" />
|
|
44
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
45
|
+
<link rel="manifest" href="./manifest.webmanifest" />
|
|
46
|
+
|
|
47
|
+
<title>OctiqFlow</title>
|
|
48
|
+
<script type="module" crossorigin src="./assets/index-MhXUMNK1.js"></script>
|
|
49
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DpgjZUbN.css">
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div id="root"></div>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "OctiqFlow",
|
|
3
|
+
"short_name": "OctiqFlow",
|
|
4
|
+
"description": "Drive Claude Code and Codex from anywhere.",
|
|
5
|
+
"start_url": "./",
|
|
6
|
+
"scope": "./",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#1c1c1e",
|
|
9
|
+
"theme_color": "#1c1c1e",
|
|
10
|
+
"icons": [
|
|
11
|
+
{ "src": "./icon-192.png", "sizes": "192x192", "type": "image/png" },
|
|
12
|
+
{ "src": "./icon-512.png", "sizes": "512x512", "type": "image/png" },
|
|
13
|
+
{ "src": "./icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
|
14
|
+
]
|
|
15
|
+
}
|
package/bin/v2/sw.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// The part of OctiqFlow that runs when OctiqFlow does not.
|
|
2
|
+
//
|
|
3
|
+
// A service worker outlives the page. The phone's OS wakes it on a push with
|
|
4
|
+
// nothing of ours open — no tab, no home-screen app, a locked screen — draws
|
|
5
|
+
// the banner, and lets it go again. That is the whole reason this file exists,
|
|
6
|
+
// and it is why the notification path moved to the server: `lib/notify.ts` can
|
|
7
|
+
// only raise a banner while the page is running, which on a phone is almost
|
|
8
|
+
// never.
|
|
9
|
+
//
|
|
10
|
+
// Deliberately not a cache. Every offline-first instinct is wrong here: the app
|
|
11
|
+
// is a window onto a machine at the end of a tunnel, and a cached shell that
|
|
12
|
+
// loads while the backend is unreachable is worse than a page that fails
|
|
13
|
+
// honestly. `fetch` is not handled at all, so the network is always the answer.
|
|
14
|
+
|
|
15
|
+
/** Where the reader is, as the last open page described it.
|
|
16
|
+
*
|
|
17
|
+
* The rule "say nothing about the chat you are already looking at" needs to
|
|
18
|
+
* know which chat that is, and only the page knows. It tells us on every
|
|
19
|
+
* change; we keep the latest.
|
|
20
|
+
*
|
|
21
|
+
* This is memory, and a service worker is killed and restarted freely — so it
|
|
22
|
+
* is routinely empty even though a page is open. That is why an empty value
|
|
23
|
+
* means SHOW: a banner you did not need is a smaller failure than a permission
|
|
24
|
+
* ask you never saw. */
|
|
25
|
+
let reading = null;
|
|
26
|
+
|
|
27
|
+
self.addEventListener("message", (event) => {
|
|
28
|
+
const data = event.data;
|
|
29
|
+
if (data && data.type === "reading") {
|
|
30
|
+
reading = data.conversationId ?? null;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
self.addEventListener("install", () => {
|
|
35
|
+
// Take over on the first load rather than waiting for every old page to go.
|
|
36
|
+
// There is no old version to be careful of — nothing is cached.
|
|
37
|
+
self.skipWaiting();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
self.addEventListener("activate", (event) => {
|
|
41
|
+
event.waitUntil(self.clients.claim());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/** Is a window open, focused, AND showing this chat? */
|
|
45
|
+
async function watching(conversationId) {
|
|
46
|
+
const windows = await self.clients.matchAll({
|
|
47
|
+
type: "window",
|
|
48
|
+
includeUncontrolled: true,
|
|
49
|
+
});
|
|
50
|
+
const focused = windows.some((c) => c.focused && c.visibilityState === "visible");
|
|
51
|
+
// Reading a DIFFERENT chat in a focused window still counts as away: the news
|
|
52
|
+
// is not in front of you either way. This mirrors `isWatching` in
|
|
53
|
+
// lib/notify.ts, which is the same rule for the other route.
|
|
54
|
+
return focused && reading !== null && reading === conversationId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
self.addEventListener("push", (event) => {
|
|
58
|
+
event.waitUntil(
|
|
59
|
+
(async () => {
|
|
60
|
+
let notice = null;
|
|
61
|
+
try {
|
|
62
|
+
notice = event.data ? event.data.json() : null;
|
|
63
|
+
} catch {
|
|
64
|
+
// A push whose body we cannot read still means somebody wants you.
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const title = (notice && notice.title) || "OctiqFlow";
|
|
68
|
+
const body = (notice && notice.body) || "Something needs you.";
|
|
69
|
+
const id = notice && notice.conversationId;
|
|
70
|
+
|
|
71
|
+
// iOS revokes the permission of an app that takes pushes and shows
|
|
72
|
+
// nothing, so the one case we stay silent for is the one that is
|
|
73
|
+
// genuinely redundant: the chat is on screen in front of you.
|
|
74
|
+
if (id && (await watching(id))) return;
|
|
75
|
+
|
|
76
|
+
await self.registration.showNotification(title, {
|
|
77
|
+
body,
|
|
78
|
+
// One live banner per chat per kind — a later one takes the earlier
|
|
79
|
+
// one's place instead of stacking under it.
|
|
80
|
+
tag: (notice && notice.tag) || "octiq",
|
|
81
|
+
icon: "./icon-192.png",
|
|
82
|
+
badge: "./icon-192.png",
|
|
83
|
+
data: {
|
|
84
|
+
conversationId: id ?? null,
|
|
85
|
+
projectId: (notice && notice.projectId) || null,
|
|
86
|
+
},
|
|
87
|
+
// Work that is BLOCKED on you should sit there until it is seen; a
|
|
88
|
+
// turn that merely ended can go away by itself. Honoured on desktop
|
|
89
|
+
// Chrome, ignored elsewhere, harmless either way.
|
|
90
|
+
requireInteraction: !!notice && notice.kind !== "done",
|
|
91
|
+
});
|
|
92
|
+
})(),
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/** Where a tap is written down for the app to find on its way in.
|
|
97
|
+
*
|
|
98
|
+
* `postMessage` only reaches a page that is RUNNING, and on a phone it usually
|
|
99
|
+
* is not: a home-screen app is suspended within seconds of going behind
|
|
100
|
+
* something, and killed outright soon after. So the chat that was asked for
|
|
101
|
+
* goes somewhere that outlives both the worker and the page, and whatever
|
|
102
|
+
* finally brings the app up reads it there — `openWindow`, the OS, or the
|
|
103
|
+
* person giving up on the banner and tapping the icon themselves.
|
|
104
|
+
*
|
|
105
|
+
* Cache Storage because it is the one store both halves can reach from here.
|
|
106
|
+
* This is a mailbox, not the cache the top of the file swears off: one entry,
|
|
107
|
+
* written by the worker, taken by the page, and nothing ever fetches it. */
|
|
108
|
+
const TAPS = "octiq-tap";
|
|
109
|
+
const TAPPED = "./__octiq_tapped__";
|
|
110
|
+
|
|
111
|
+
async function rememberTap(conversationId, projectId) {
|
|
112
|
+
if (!conversationId || !self.caches) return;
|
|
113
|
+
try {
|
|
114
|
+
const cache = await self.caches.open(TAPS);
|
|
115
|
+
await cache.put(
|
|
116
|
+
new Request(TAPPED),
|
|
117
|
+
new Response(
|
|
118
|
+
JSON.stringify({
|
|
119
|
+
conversationId,
|
|
120
|
+
projectId: projectId ?? null,
|
|
121
|
+
at: Date.now(),
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
} catch {
|
|
126
|
+
/* no store to write to; the message and the URL are the other two ways in */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Tapping the banner. Bring back the window that is already open rather than
|
|
131
|
+
// adding a second one — on a phone a duplicate tab is how you end up with two
|
|
132
|
+
// copies of a chat and no idea which is live.
|
|
133
|
+
self.addEventListener("notificationclick", (event) => {
|
|
134
|
+
event.notification.close();
|
|
135
|
+
const data = event.notification.data || {};
|
|
136
|
+
const id = data.conversationId;
|
|
137
|
+
const project = data.projectId;
|
|
138
|
+
// The address, for the case where the app has to be started. It reads
|
|
139
|
+
// `#/p/<project>/c/<chat>`, which is why the notice carries the project as
|
|
140
|
+
// well as the chat; without both halves it launches and lands wherever it
|
|
141
|
+
// left off, which is the one thing a tapped banner must not do. The token is
|
|
142
|
+
// already in the page's own storage, so our own scope is enough to get back
|
|
143
|
+
// in.
|
|
144
|
+
const to =
|
|
145
|
+
id && project
|
|
146
|
+
? `./#/p/${encodeURIComponent(project)}/c/${encodeURIComponent(id)}`
|
|
147
|
+
: "./";
|
|
148
|
+
|
|
149
|
+
event.waitUntil(
|
|
150
|
+
(async () => {
|
|
151
|
+
// Written down BEFORE anything is raised, because raising it is the part
|
|
152
|
+
// that fails.
|
|
153
|
+
await rememberTap(id, project);
|
|
154
|
+
|
|
155
|
+
const windows = await self.clients.matchAll({
|
|
156
|
+
type: "window",
|
|
157
|
+
includeUncontrolled: true,
|
|
158
|
+
});
|
|
159
|
+
for (const client of windows) {
|
|
160
|
+
// A page that is already running owns the conversation list and the
|
|
161
|
+
// routing, so it only needs the chat id. Sent whether or not the window
|
|
162
|
+
// can be brought forward: one that lands on a window the OS raised by
|
|
163
|
+
// itself is worth more than a tap that said nothing at all.
|
|
164
|
+
if (id) client.postMessage({ type: "open-chat", conversationId: id });
|
|
165
|
+
let raised = null;
|
|
166
|
+
try {
|
|
167
|
+
raised = await client.focus();
|
|
168
|
+
} catch {
|
|
169
|
+
/* refused — `openWindow` below is the other way to raise it */
|
|
170
|
+
}
|
|
171
|
+
if (raised && raised.focused) return;
|
|
172
|
+
}
|
|
173
|
+
// Either nothing was open, or nothing could be RAISED — and on iOS the
|
|
174
|
+
// second is the usual answer, which is what two earlier attempts at this
|
|
175
|
+
// missed. `matchAll` lists a home-screen app that is merely suspended,
|
|
176
|
+
// `focus()` cannot bring one of those back, and stopping at the refusal
|
|
177
|
+
// left the tap doing nothing whatsoever. `openWindow` is the call that
|
|
178
|
+
// launches it, so a window we could not raise has to fall through to
|
|
179
|
+
// here rather than count as handled.
|
|
180
|
+
if (self.clients.openWindow) {
|
|
181
|
+
try {
|
|
182
|
+
await self.clients.openWindow(to);
|
|
183
|
+
} catch {
|
|
184
|
+
/* not allowed to open one either; the note above is what is left */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})(),
|
|
188
|
+
);
|
|
189
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "octiqflow-linux-x64",
|
|
3
|
+
"version": "0.1.52",
|
|
4
|
+
"description": "Native OctiqFlow runtime for linux x64",
|
|
5
|
+
"os": [
|
|
6
|
+
"linux"
|
|
7
|
+
],
|
|
8
|
+
"cpu": [
|
|
9
|
+
"x64"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
"bin"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/kyson8101/octiq-flow.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/kyson8101/octiq-flow#readme",
|
|
19
|
+
"license": "UNLICENSED",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
}
|
|
23
|
+
}
|