├── .gitignore ├── Dockerfile ├── audio ├── definite.mp3 └── knob.mp3 ├── css ├── critical.css ├── critical.scss ├── style.css └── style.scss ├── dist ├── aez.wasm ├── blake2.wasm ├── brick-wall.svg ├── cross-stripes.svg ├── definite.mp3 ├── ed25519-to-x25519.wasm ├── fa-solid-900.woff2 ├── favicon.ico ├── index.min.html ├── knob.mp3 ├── logo-128.png ├── logo-256.png ├── logo-600.png ├── logo.svg ├── manifest.json ├── noise-c.wasm ├── script.min.js ├── style.min.css ├── supercop.wasm ├── webcomponents.min.js └── workbox-v3.3.0 │ ├── workbox-background-sync.dev.js │ ├── workbox-background-sync.dev.js.map │ ├── workbox-background-sync.prod.js │ ├── workbox-background-sync.prod.js.map │ ├── workbox-broadcast-cache-update.dev.js │ ├── workbox-broadcast-cache-update.dev.js.map │ ├── workbox-broadcast-cache-update.prod.js │ ├── workbox-broadcast-cache-update.prod.js.map │ ├── workbox-cache-expiration.dev.js │ ├── workbox-cache-expiration.dev.js.map │ ├── workbox-cache-expiration.prod.js │ ├── workbox-cache-expiration.prod.js.map │ ├── workbox-cacheable-response.dev.js │ ├── workbox-cacheable-response.dev.js.map │ ├── workbox-cacheable-response.prod.js │ ├── workbox-cacheable-response.prod.js.map │ ├── workbox-core.dev.js │ ├── workbox-core.dev.js.map │ ├── workbox-core.prod.js │ ├── workbox-core.prod.js.map │ ├── workbox-google-analytics.dev.js │ ├── workbox-google-analytics.dev.js.map │ ├── workbox-google-analytics.prod.js │ ├── workbox-google-analytics.prod.js.map │ ├── workbox-precaching.dev.js │ ├── workbox-precaching.dev.js.map │ ├── workbox-precaching.prod.js │ ├── workbox-precaching.prod.js.map │ ├── workbox-range-requests.dev.js │ ├── workbox-range-requests.dev.js.map │ ├── workbox-range-requests.prod.js │ ├── workbox-range-requests.prod.js.map │ ├── workbox-routing.dev.js │ ├── workbox-routing.dev.js.map │ ├── workbox-routing.prod.js │ ├── workbox-routing.prod.js.map │ ├── workbox-strategies.dev.js │ ├── workbox-strategies.dev.js.map │ ├── workbox-strategies.prod.js │ ├── workbox-strategies.prod.js.map │ ├── workbox-streams.dev.js │ ├── workbox-streams.dev.js.map │ ├── workbox-streams.prod.js │ ├── workbox-streams.prod.js.map │ ├── workbox-sw.js │ └── workbox-sw.js.map ├── favicon.ico ├── gulpfile.js ├── gulpfile.ls ├── html ├── cleverstyle-widgets-styling.html ├── detox-chat-app-dialogs │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-app-sidebar-contacts │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-app-sidebar-profile │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-app-sidebar-settings │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-app-sidebar-status │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-app │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── detox-chat-icon-button │ ├── index.html │ ├── index.pug │ ├── script.js │ ├── script.ls │ ├── style.css │ └── style.scss ├── index.html └── index.pug ├── img ├── brick-wall.svg ├── cross-stripes.svg ├── logo-128.png ├── logo-256.png ├── logo-600.png └── logo.svg ├── index-debug.html ├── index-debug.pug ├── index.html ├── js ├── a.async-styles.js ├── a.require.js ├── a.require.ls ├── behaviors.js ├── behaviors.ls ├── global.js ├── global.ls ├── markdown.js ├── markdown.ls ├── state.js └── state.ls ├── manifest.json ├── package.json ├── readme.md └── sw.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node as builder 2 | LABEL maintainer="Nazar Mokrynskyi " 3 | 4 | COPY audio /code/audio 5 | COPY css /code/css 6 | COPY html /code/html 7 | COPY img /code/img 8 | COPY js /code/js 9 | COPY gulpfile.js /code/ 10 | COPY favicon.ico /code/ 11 | COPY index.html /code/ 12 | COPY manifest.json /code/ 13 | COPY package.json /code/ 14 | 15 | RUN cd /code && npm install && mkdir dist && node_modules/.bin/gulp 16 | 17 | # This is basically a hack, since we use docker container in gulp build and can't call it inside of another docker container during build process 18 | FROM nazarpc/subset-font as font-builder 19 | LABEL maintainer="Nazar Mokrynskyi " 20 | COPY --from=builder /code/dist/fa-solid-900.woff2 /font.woff2 21 | COPY --from=builder /code/dist/index.min.html /style.css 22 | 23 | RUN php subset.php 24 | 25 | FROM nginx:alpine 26 | LABEL maintainer="Nazar Mokrynskyi " 27 | 28 | COPY --from=builder /code/dist /usr/share/nginx/html/dist 29 | COPY --from=font-builder /font.woff2 /usr/share/nginx/html/dist/fa-solid-900.woff2 30 | COPY --from=builder /code/favicon.ico /usr/share/nginx/html/ 31 | COPY --from=builder /code/index.html /usr/share/nginx/html/ 32 | COPY --from=builder /code/sw.min.js /usr/share/nginx/html/ 33 | 34 | RUN \ 35 | apk update && \ 36 | apk upgrade && \ 37 | rm -rf /var/cache/apk/* 38 | 39 | RUN sed -i 's/}/ application\/wasm wasm;\n}/g' /etc/nginx/mime.types 40 | RUN sed -i 's/access_log.\+;/access_log off;/g' /etc/nginx/nginx.conf 41 | -------------------------------------------------------------------------------- /audio/definite.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/audio/definite.mp3 -------------------------------------------------------------------------------- /audio/knob.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/audio/knob.mp3 -------------------------------------------------------------------------------- /css/critical.css: -------------------------------------------------------------------------------- 1 | html{background-color:#222;overflow:hidden}html::before{animation:pulsate 1s ease-out;animation-iteration-count:infinite;border:1vmax solid #c8c8c8;border-radius:100%;box-sizing:border-box;content:'';display:block;height:10vmax;left:50%;margin:-5vmax 0 0 -5vmax;opacity:0;position:absolute;top:50%;width:10vmax;z-index:-1}@keyframes pulsate{0%{transform:scale(0.1);opacity:0}50%{opacity:1}100%{transform:scale(1);opacity:0}}body{margin:0} 2 | -------------------------------------------------------------------------------- /css/critical.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | html { 7 | background-color : #222; 8 | overflow : hidden; 9 | 10 | &::before { 11 | animation : pulsate 1s ease-out; 12 | animation-iteration-count : infinite; 13 | border : 1vmax solid #c8c8c8; 14 | border-radius : 100%; 15 | box-sizing : border-box; 16 | content : ''; 17 | display : block; 18 | height : 10vmax; 19 | left : 50%; 20 | margin : -5vmax 0 0 -5vmax; 21 | opacity : 0; 22 | position : absolute; 23 | top : 50%; 24 | width : 10vmax; 25 | z-index : -1; 26 | } 27 | 28 | @keyframes pulsate { 29 | 0% { 30 | transform : scale(.1); 31 | opacity : 0; 32 | } 33 | 50% { 34 | opacity : 1; 35 | } 36 | 100% { 37 | transform : scale(1); 38 | opacity : 0; 39 | } 40 | } 41 | } 42 | 43 | body { 44 | margin : 0; 45 | } 46 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html{color:var(--text-color-base);font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:1.2;--app-height: 100vh;--app-width: 100vw;--text-color-base: #c8c8c8;--text-color-base-disabled: #949494;--text-color-active: #fff;--text-color-success: #8cc14c;--text-color-warning: #faa732;--text-color-error: #da314b;--background-color-dialogs: #222;--background-image-dialogs: url(../img/brick-wall.svg);--background-dialogs: var(--background-color-dialogs) var(--background-image-dialogs);--background-color-sidebar: #181818;--background-image-sidebar: url(../img/cross-stripes.svg);--background-sidebar: var(--background-color-sidebar) var(--background-image-sidebar);--background-color-interactive: #202020;--background-color-highlight: #333;--background-color-element: var(--background-color-sidebar);--background-color-element-disabled: #1f1f1f;--background-color-primary: var(--background-color-element);--background-color-success: var(--background-color-element);--background-color-warning: var(--background-color-element);--background-color-error: var(--background-color-element);--border-color-base: #484848;--border-color-active: var(--text-color-base-disabled);--border-width-base: 1px;--border-base: var(--border-width-base) solid var(--border-color-base);--border-active: var(--border-width-base) solid var(--border-color-active);--border-color-element: var(--border-color-base);--font-family-base: sans-serif;--font-family-heading: sans-serif;--font-family-mono: monospace;--font-size-base: 16px;--form-element-background: var(--background-color-sidebar);--form-element-border: var(--border-base);--form-element-color: var(--text-color-base);--form-element-focus-color: var(--text-color-active);--form-element-focus-border: var(--border-active);--form-element-height: 2.5em;--form-textarea-height: 4em;--form-element-padding-vertical: .6em;--form-element-padding-horizontal: 1em;--form-element-padding: var(--form-element-padding-vertical) var(--form-element-padding-horizontal);--form-element-width: 100%;--button-color: var(--text-color-base);--button-background: var(--background-color-interactive);--button-text-color-primary: var(--text-color-base);--button-background-color-primary: #1c468b;--button-background-color-primary-hover: #1f5ca8;--button-background-color-primary-disabled: #17366e;--button-hover-color: var(--text-color-active);--button-hover-background: var(--background-color-highlight);--button-active-color: var(--text-color-base);--button-active-background: var(--border-color-base);--button-compact-padding: .1em .2em;--link-button-padding: .7em 1em;--block-padding: 1em;--transition-duration: .3s;--transition-duration-short: .15s;--tabs-border: var(--border-base);--modal-content-background: var(--background-color-sidebar);--modal-content-padding: 2em;--modal-content-translate: 10vh;--modal-overlay-opacity: .95;--tooltip-background: var(--background-color-element);--tooltip-color: var(--text-color-base);--tooltip-padding: calc(var(--block-padding) / 2);--tooltip-max-height: 40vh;--tooltip-max-width: 40vw;--tooltip-arrow-background: var(--border-color-base);--tooltip-arrow-height: .4em;--tooltip-arrow-width: .7em;--tooltip-offset: 1.5em;--notify-content-max-width: 45vw;--notify-transition-duration: .5s;--notify-translate: 50vh;--notify-content-padding: var(--block-padding) 2.5em var(--block-padding) var(--block-padding);--sidebar-width-min: 300px;--sidebar-width-max: 420px;--sidebar-width-base: 20%;--sidebar-shadow: linear-gradient(to right, var(--background-color-sidebar), transparent);--sidebar-shadow-opacity: .75;--sidebar-contact-corner-bottom-left: 45deg;--sidebar-contact-corner-bottom-right: 315deg;--sidebar-contact-corner-end: var(--block-padding);--sidebar-contact-corner-start: 0;--sidebar-contact-corner-top-left: 135deg;--sidebar-contact-corner-top-right: 225deg;--sidebar-contact-online-color: var(--text-color-success);--sidebar-contact-online-corner: linear-gradient(var(--sidebar-contact-corner-top-left), var(--sidebar-contact-online-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-unread-color: var(--text-color-warning);--sidebar-contact-unread-corner: linear-gradient(var(--sidebar-contact-corner-top-right), var(--sidebar-contact-unread-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-pending-color: var(--sidebar-contact-unread-color);--sidebar-contact-pending-corner: linear-gradient(var(--sidebar-contact-corner-bottom-right), var(--sidebar-contact-pending-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-unconfirmed-color: var(--text-color-base-disabled);--sidebar-contact-unconfirmed-corner: linear-gradient(var(--sidebar-contact-corner-bottom-left), var(--sidebar-contact-unconfirmed-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--menu-button-unread-corner: var(--sidebar-contact-unread-corner);--dialog-edge-shadow: 0 0 var(--block-padding) var(--block-padding) var(--background-color-sidebar);--dialog-message-corner-end: var(--block-padding);--dialog-message-corner-start: 0%;--dialog-message-width-max: 80%;--dialog-message-pending-corner: linear-gradient(var(--sidebar-contact-corner-bottom-right), var(--sidebar-contact-pending-color) var(--dialog-message-corner-start), transparent var(--dialog-message-corner-end));--dialog-send-textarea-max: 40vh;--dialog-send-textarea-min: 5em;--content-link-color: #6dadfd;--content-link-color-visited: #b477ff;--content-blockquote-border-left: .4em solid var(--border-color-base);--content-blockquote-border-right: .1em solid var(--border-color-base);--content-blockquote-margin: .5em 0;--content-blockquote-padding: 0 1em;--content-code-border-radius: .3em;--content-code-block-padding: .5em;--content-code-inline-padding: .15em;--content-hr-height: .1em;--content-hr-background-color: currentColor}html a{color:var(--content-link-color)}html a:visited{color:var(--content-link-color-visited)}@media(max-width: 768px){html{--dialog-message-width-max: 90%;--notify-content-max-width: 70vw}} 2 | -------------------------------------------------------------------------------- /dist/aez.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/aez.wasm -------------------------------------------------------------------------------- /dist/blake2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/blake2.wasm -------------------------------------------------------------------------------- /dist/brick-wall.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/cross-stripes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/definite.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/definite.mp3 -------------------------------------------------------------------------------- /dist/ed25519-to-x25519.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/ed25519-to-x25519.wasm -------------------------------------------------------------------------------- /dist/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/fa-solid-900.woff2 -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/favicon.ico -------------------------------------------------------------------------------- /dist/knob.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/knob.mp3 -------------------------------------------------------------------------------- /dist/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/logo-128.png -------------------------------------------------------------------------------- /dist/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/logo-256.png -------------------------------------------------------------------------------- /dist/logo-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/logo-600.png -------------------------------------------------------------------------------- /dist/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | {"background_color":"#181818","display":"standalone","icons":[{"src":"logo.svg?7c632","type":"image/svg+xml","sizes":"any"},{"src":"logo-128.png?e1b94","type":"image/png","sizes":"128x128"},{"src":"logo-256.png?e5c43","type":"image/png","sizes":"256x256"},{"src":"logo-600.png?73696","type":"image/png","sizes":"600x600"}],"name":"Detox Chat","short_name":"Detox Chat","start_url":"../index.html?home","theme_color":"#181818","scope":"/"} -------------------------------------------------------------------------------- /dist/noise-c.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/noise-c.wasm -------------------------------------------------------------------------------- /dist/style.min.css: -------------------------------------------------------------------------------- 1 | html{color:var(--text-color-base);font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:1.2;--app-height:100vh;--app-width:100vw;--text-color-base:#c8c8c8;--text-color-base-disabled:#949494;--text-color-active:#fff;--text-color-success:#8cc14c;--text-color-warning:#faa732;--text-color-error:#da314b;--background-color-dialogs:#222;--background-image-dialogs:url(dist/brick-wall.svg?c63a0);--background-dialogs:var(--background-color-dialogs) var(--background-image-dialogs);--background-color-sidebar:#181818;--background-image-sidebar:url(dist/cross-stripes.svg?7e8c9);--background-sidebar:var(--background-color-sidebar) var(--background-image-sidebar);--background-color-interactive:#202020;--background-color-highlight:#333;--background-color-element:var(--background-color-sidebar);--background-color-element-disabled:#1f1f1f;--background-color-primary:var(--background-color-element);--background-color-success:var(--background-color-element);--background-color-warning:var(--background-color-element);--background-color-error:var(--background-color-element);--border-color-base:#484848;--border-color-active:var(--text-color-base-disabled);--border-width-base:1px;--border-base:var(--border-width-base) solid var(--border-color-base);--border-active:var(--border-width-base) solid var(--border-color-active);--border-color-element:var(--border-color-base);--font-family-base:sans-serif;--font-family-heading:sans-serif;--font-family-mono:monospace;--font-size-base:16px;--form-element-background:var(--background-color-sidebar);--form-element-border:var(--border-base);--form-element-color:var(--text-color-base);--form-element-focus-color:var(--text-color-active);--form-element-focus-border:var(--border-active);--form-element-height:2.5em;--form-textarea-height:4em;--form-element-padding-vertical:.6em;--form-element-padding-horizontal:1em;--form-element-padding:var(--form-element-padding-vertical) var(--form-element-padding-horizontal);--form-element-width:100%;--button-color:var(--text-color-base);--button-background:var(--background-color-interactive);--button-text-color-primary:var(--text-color-base);--button-background-color-primary:#1c468b;--button-background-color-primary-hover:#1f5ca8;--button-background-color-primary-disabled:#17366e;--button-hover-color:var(--text-color-active);--button-hover-background:var(--background-color-highlight);--button-active-color:var(--text-color-base);--button-active-background:var(--border-color-base);--button-compact-padding:.1em .2em;--link-button-padding:.7em 1em;--block-padding:1em;--transition-duration:.3s;--transition-duration-short:.15s;--tabs-border:var(--border-base);--modal-content-background:var(--background-color-sidebar);--modal-content-padding:2em;--modal-content-translate:10vh;--modal-overlay-opacity:.95;--tooltip-background:var(--background-color-element);--tooltip-color:var(--text-color-base);--tooltip-padding:calc(var(--block-padding) / 2);--tooltip-max-height:40vh;--tooltip-max-width:40vw;--tooltip-arrow-background:var(--border-color-base);--tooltip-arrow-height:.4em;--tooltip-arrow-width:.7em;--tooltip-offset:1.5em;--notify-content-max-width:45vw;--notify-transition-duration:.5s;--notify-translate:50vh;--notify-content-padding:var(--block-padding) 2.5em var(--block-padding) var(--block-padding);--sidebar-width-min:300px;--sidebar-width-max:420px;--sidebar-width-base:20%;--sidebar-shadow:linear-gradient(to right, var(--background-color-sidebar), transparent);--sidebar-shadow-opacity:.75;--sidebar-contact-corner-bottom-left:45deg;--sidebar-contact-corner-bottom-right:315deg;--sidebar-contact-corner-end:var(--block-padding);--sidebar-contact-corner-start:0;--sidebar-contact-corner-top-left:135deg;--sidebar-contact-corner-top-right:225deg;--sidebar-contact-online-color:var(--text-color-success);--sidebar-contact-online-corner:linear-gradient(var(--sidebar-contact-corner-top-left), var(--sidebar-contact-online-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-unread-color:var(--text-color-warning);--sidebar-contact-unread-corner:linear-gradient(var(--sidebar-contact-corner-top-right), var(--sidebar-contact-unread-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-pending-color:var(--sidebar-contact-unread-color);--sidebar-contact-pending-corner:linear-gradient(var(--sidebar-contact-corner-bottom-right), var(--sidebar-contact-pending-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--sidebar-contact-unconfirmed-color:var(--text-color-base-disabled);--sidebar-contact-unconfirmed-corner:linear-gradient(var(--sidebar-contact-corner-bottom-left), var(--sidebar-contact-unconfirmed-color) var(--sidebar-contact-corner-start), transparent var(--sidebar-contact-corner-end));--menu-button-unread-corner:var(--sidebar-contact-unread-corner);--dialog-edge-shadow:0 0 var(--block-padding) var(--block-padding) var(--background-color-sidebar);--dialog-message-corner-end:var(--block-padding);--dialog-message-corner-start:0%;--dialog-message-width-max:80%;--dialog-message-pending-corner:linear-gradient(var(--sidebar-contact-corner-bottom-right), var(--sidebar-contact-pending-color) var(--dialog-message-corner-start), transparent var(--dialog-message-corner-end));--dialog-send-textarea-max:40vh;--dialog-send-textarea-min:5em;--content-link-color:#6dadfd;--content-link-color-visited:#b477ff;--content-blockquote-border-left:.4em solid var(--border-color-base);--content-blockquote-border-right:.1em solid var(--border-color-base);--content-blockquote-margin:.5em 0;--content-blockquote-padding:0 1em;--content-code-border-radius:.3em;--content-code-block-padding:.5em;--content-code-inline-padding:.15em;--content-hr-height:.1em;--content-hr-background-color:currentColor}html a{color:var(--content-link-color)}html a:visited{color:var(--content-link-color-visited)}@media(max-width:768px){html{--dialog-message-width-max:90%;--notify-content-max-width:70vw}} -------------------------------------------------------------------------------- /dist/supercop.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/dist/supercop.wasm -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-background-sync.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.backgroundSync=function(e,t){"use strict";try{self.workbox.v["workbox:background-sync:3.3.0"]=1}catch(e){}const r=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];class s{static fromRequest(e){return babelHelpers.asyncToGenerator(function*(){const t={headers:{}};"GET"!==e.method&&(t.body=yield e.clone().blob());for(const[r,s]of e.headers.entries())t.headers[r]=s;for(const s of r)void 0!==e[s]&&(t[s]=e[s]);return new s({url:e.url,requestInit:t})})()}constructor({url:e,requestInit:t,timestamp:r=Date.now()}){this.url=e,this.requestInit=t,this.e=r}get timestamp(){return this.e}toObject(){return{url:this.url,timestamp:this.timestamp,requestInit:this.requestInit}}toRequest(){return new Request(this.url,this.requestInit)}clone(){const e=Object.assign({},this.requestInit);return e.headers=Object.assign({},this.requestInit.headers),this.requestInit.body&&(e.body=this.requestInit.body.slice()),new s({url:this.url,timestamp:this.timestamp,requestInit:e})}}const i="workbox-background-sync",n="requests",u="queueName",c="workbox-background-sync",o=10080;class l{constructor(t){this.t=t,this.r=new e.DBWrapper(i,1,{onupgradeneeded:e=>e.target.result.createObjectStore(n,{autoIncrement:!0}).createIndex(u,u,{unique:!1})})}addEntry(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.r.add(n,{queueName:t.t.name,storableRequest:e.toObject()})})()}getAndRemoveOldestEntry(){var e=this;return babelHelpers.asyncToGenerator(function*(){const[t]=yield e.r.getAllMatching(n,{index:u,query:IDBKeyRange.only(e.t.name),count:1,includeKeys:!0});if(t)return yield e.r.delete(n,t.primaryKey),new s(t.value.storableRequest)})()}}const a=new Set;class h{constructor(e,{callbacks:r={},maxRetentionTime:s=o}={}){if(a.has(e))throw new t.WorkboxError("duplicate-queue-name",{name:e});a.add(e),this.s=e,this.i=r,this.n=s,this.u=new l(this),this.c()}get name(){return this.s}addRequest(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=yield s.fromRequest(e.clone());yield t.o("requestWillEnqueue",r),yield t.u.addEntry(r),yield t.l()})()}replayRequests(){var e=this;return babelHelpers.asyncToGenerator(function*(){const r=Date.now(),s=[],i=[];let n;for(;n=yield e.u.getAndRemoveOldestEntry();){const t=n.clone(),u=60*e.n*1e3;if(r-n.timestamp>u)continue;yield e.o("requestWillReplay",n);const c={request:n.toRequest()};try{c.response=yield fetch(c.request.clone())}catch(e){c.error=e,i.push(t)}s.push(c)}if(yield e.o("queueDidReplay",s),i.length)throw yield Promise.all(i.map(function(t){return e.u.addEntry(t)})),new t.WorkboxError("queue-replay-failed",{name:e.s,count:i.length})})()}o(e,...t){var r=this;return babelHelpers.asyncToGenerator(function*(){"function"==typeof r.i[e]&&(yield r.i[e].apply(null,t))})()}c(){"sync"in registration?self.addEventListener("sync",e=>{e.tag===`${c}:${this.s}`&&e.waitUntil(this.replayRequests())}):this.replayRequests()}l(){var e=this;return babelHelpers.asyncToGenerator(function*(){if("sync"in registration)try{yield registration.sync.register(`${c}:${e.s}`)}catch(e){}})()}static get a(){return a}}return Object.freeze({Queue:h,Plugin:class{constructor(...e){this.t=new h(...e),this.fetchDidFail=this.fetchDidFail.bind(this)}fetchDidFail({request:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.t.addRequest(e)})()}}})}(workbox.core._private,workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-background-sync.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-background-sync.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-background-sync/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.backgroundSync=function(e,t){\"use strict\";try{self.workbox.v[\"workbox:background-sync:3.3.0\"]=1}catch(e){}const r=[\"method\",\"referrer\",\"referrerPolicy\",\"mode\",\"credentials\",\"cache\",\"redirect\",\"integrity\",\"keepalive\"];class s{static fromRequest(e){return babelHelpers.asyncToGenerator(function*(){const t={headers:{}};\"GET\"!==e.method&&(t.body=yield e.clone().blob());for(const[r,s]of e.headers.entries())t.headers[r]=s;for(const s of r)void 0!==e[s]&&(t[s]=e[s]);return new s({url:e.url,requestInit:t})})()}constructor({url:e,requestInit:t,timestamp:r=Date.now()}){this.url=e,this.requestInit=t,this.e=r}get timestamp(){return this.e}toObject(){return{url:this.url,timestamp:this.timestamp,requestInit:this.requestInit}}toRequest(){return new Request(this.url,this.requestInit)}clone(){const e=Object.assign({},this.requestInit);return e.headers=Object.assign({},this.requestInit.headers),this.requestInit.body&&(e.body=this.requestInit.body.slice()),new s({url:this.url,timestamp:this.timestamp,requestInit:e})}}const i=\"workbox-background-sync\",n=\"requests\",u=\"queueName\",c=\"workbox-background-sync\",o=10080;class l{constructor(t){this.t=t,this.r=new e.DBWrapper(i,1,{onupgradeneeded:e=>e.target.result.createObjectStore(n,{autoIncrement:!0}).createIndex(u,u,{unique:!1})})}addEntry(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.r.add(n,{queueName:t.t.name,storableRequest:e.toObject()})})()}getAndRemoveOldestEntry(){var e=this;return babelHelpers.asyncToGenerator(function*(){const[t]=yield e.r.getAllMatching(n,{index:u,query:IDBKeyRange.only(e.t.name),count:1,includeKeys:!0});if(t)return yield e.r.delete(n,t.primaryKey),new s(t.value.storableRequest)})()}}const a=new Set;class h{constructor(e,{callbacks:r={},maxRetentionTime:s=o}={}){if(a.has(e))throw new t.WorkboxError(\"duplicate-queue-name\",{name:e});a.add(e),this.s=e,this.i=r,this.n=s,this.u=new l(this),this.c()}get name(){return this.s}addRequest(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=yield s.fromRequest(e.clone());yield t.o(\"requestWillEnqueue\",r),yield t.u.addEntry(r),yield t.l()})()}replayRequests(){var e=this;return babelHelpers.asyncToGenerator(function*(){const r=Date.now(),s=[],i=[];let n;for(;n=yield e.u.getAndRemoveOldestEntry();){const t=n.clone(),u=60*e.n*1e3;if(r-n.timestamp>u)continue;yield e.o(\"requestWillReplay\",n);const c={request:n.toRequest()};try{c.response=yield fetch(c.request.clone())}catch(e){c.error=e,i.push(t)}s.push(c)}if(yield e.o(\"queueDidReplay\",s),i.length)throw yield Promise.all(i.map(function(t){return e.u.addEntry(t)})),new t.WorkboxError(\"queue-replay-failed\",{name:e.s,count:i.length})})()}o(e,...t){var r=this;return babelHelpers.asyncToGenerator(function*(){\"function\"==typeof r.i[e]&&(yield r.i[e].apply(null,t))})()}c(){\"sync\"in registration?self.addEventListener(\"sync\",e=>{e.tag===`${c}:${this.s}`&&e.waitUntil(this.replayRequests())}):this.replayRequests()}l(){var e=this;return babelHelpers.asyncToGenerator(function*(){if(\"sync\"in registration)try{yield registration.sync.register(`${c}:${e.s}`)}catch(e){}})()}static get a(){return a}}return Object.freeze({Queue:h,Plugin:class{constructor(...e){this.t=new h(...e),this.fetchDidFail=this.fetchDidFail.bind(this)}fetchDidFail({request:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.t.addRequest(e)})()}}})}(workbox.core._private,workbox.core._private);\n"],"file":"workbox-background-sync.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-broadcast-cache-update.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t){"use strict";try{self.workbox.v["workbox:broadcast-cache-update:3.3.0"]=1}catch(t){}const e=(t,e,s)=>{return!s.some(s=>t.headers.has(s)&&e.headers.has(s))||s.every(s=>{const a=t.headers.has(s)===e.headers.has(s),n=t.headers.get(s)===e.headers.get(s);return a&&n})};var s={CACHE_UPDATED:"CACHE_UPDATED"};const a=(t,e,a,n)=>{"BroadcastChannel"in self&&t&&t.postMessage({type:s.CACHE_UPDATED,meta:n,payload:{cacheName:e,updatedUrl:a}})};class n{constructor(t,{headersToCheck:e,source:s}={}){this.t=t,this.e=e||["content-length","etag","last-modified"],this.s=s||"workbox-broadcast-cache-update"}a(){return"BroadcastChannel"in self&&!this.n&&(this.n=new BroadcastChannel(this.t)),this.n}notifyIfUpdated(t,s,n,c){e(t,s,this.e)||a(this.a(),c,n,this.s)}}return t.BroadcastCacheUpdate=n,t.Plugin=class{constructor(t,e){this.c=new n(t,e)}cacheDidUpdate({cacheName:t,oldResponse:e,newResponse:s,request:a}){e&&this.c.notifyIfUpdated(e,s,a.url,t)}},t.broadcastUpdate=a,t.messageTypes=s,t}({}); 2 | 3 | //# sourceMappingURL=workbox-broadcast-cache-update.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-broadcast-cache-update.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-broadcast-cache-update/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t){\"use strict\";try{self.workbox.v[\"workbox:broadcast-cache-update:3.3.0\"]=1}catch(t){}const e=(t,e,s)=>{return!s.some(s=>t.headers.has(s)&&e.headers.has(s))||s.every(s=>{const a=t.headers.has(s)===e.headers.has(s),n=t.headers.get(s)===e.headers.get(s);return a&&n})};var s={CACHE_UPDATED:\"CACHE_UPDATED\"};const a=(t,e,a,n)=>{\"BroadcastChannel\"in self&&t&&t.postMessage({type:s.CACHE_UPDATED,meta:n,payload:{cacheName:e,updatedUrl:a}})};class n{constructor(t,{headersToCheck:e,source:s}={}){this.t=t,this.e=e||[\"content-length\",\"etag\",\"last-modified\"],this.s=s||\"workbox-broadcast-cache-update\"}a(){return\"BroadcastChannel\"in self&&!this.n&&(this.n=new BroadcastChannel(this.t)),this.n}notifyIfUpdated(t,s,n,c){e(t,s,this.e)||a(this.a(),c,n,this.s)}}return t.BroadcastCacheUpdate=n,t.Plugin=class{constructor(t,e){this.c=new n(t,e)}cacheDidUpdate({cacheName:t,oldResponse:e,newResponse:s,request:a}){e&&this.c.notifyIfUpdated(e,s,a.url,t)}},t.broadcastUpdate=a,t.messageTypes=s,t}({});\n"],"file":"workbox-broadcast-cache-update.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-cache-expiration.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.expiration=function(e,t,r,n,i){"use strict";try{self.workbox.v["workbox:cache-expiration:3.3.0"]=1}catch(e){}const s="url",a="timestamp";class l{constructor(e){this.e=e,this.t=e,this.r=new t.DBWrapper(this.e,2,{onupgradeneeded:e=>this.n(e)})}n(e){const t=e.target.result;e.oldVersion<2&&t.objectStoreNames.contains("workbox-cache-expiration")&&t.deleteObjectStore("workbox-cache-expiration"),t.createObjectStore(this.t,{keyPath:s}).createIndex(a,a,{unique:!1})}setTimestamp(e,t){var r=this;return babelHelpers.asyncToGenerator(function*(){yield r.r.put(r.t,{[s]:new URL(e,location).href,[a]:t})})()}getAllTimestamps(){var e=this;return babelHelpers.asyncToGenerator(function*(){return yield e.r.getAllMatching(e.t,{index:a})})()}getTimestamp(e){var t=this;return babelHelpers.asyncToGenerator(function*(){return(yield t.r.get(t.t,e)).timestamp})()}deleteUrl(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.r.delete(t.t,new URL(e,location).href)})()}delete(){var e=this;return babelHelpers.asyncToGenerator(function*(){yield e.r.deleteDatabase(),e.r=null})()}}class o{constructor(e,t={}){this.i=!1,this.s=!1,this.a=t.maxEntries,this.l=t.maxAgeSeconds,this.e=e,this.o=new l(e)}expireEntries(){var e=this;return babelHelpers.asyncToGenerator(function*(){if(e.i)return void(e.s=!0);e.i=!0;const t=Date.now(),r=yield e.c(t),n=yield e.u(),i=[...new Set(r.concat(n))];yield Promise.all([e.h(i),e.d(i)]),e.i=!1,e.s&&(e.s=!1,e.expireEntries())})()}c(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!t.l)return[];const r=e-1e3*t.l,n=[];return(yield t.o.getAllTimestamps()).forEach(function(e){e.timestampe.a;){const e=r.shift();t.push(e.url)}return t})()}h(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=yield caches.open(t.e);for(const t of e)yield r.delete(t)})()}d(e){var t=this;return babelHelpers.asyncToGenerator(function*(){for(const r of e)yield t.o.deleteUrl(r)})()}updateTimestamp(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=new URL(e,location);r.hash="",yield t.o.setTimestamp(r.href,Date.now())})()}isURLExpired(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!t.l)throw new r.WorkboxError("expired-test-without-max-age",{methodName:"isURLExpired",paramName:"maxAgeSeconds"});const n=new URL(e,location);return n.hash="",(yield t.o.getTimestamp(n.href))this.deleteCacheAndMetadata())}f(e){if(e===n.cacheNames.getRuntimeName())throw new r.WorkboxError("expire-custom-caches-only");let t=this.b.get(e);return t||(t=new o(e,this.p),this.b.set(e,t)),t}cachedResponseWillBeUsed({cacheName:e,cachedResponse:t}){if(!t)return null;let r=this.m(t);return this.f(e).expireEntries(),r?t:null}m(e){if(!this.l)return!0;const t=this.y(e);return null===t||t>=Date.now()-1e3*this.l}y(e){if(!e.headers.has("date"))return null;const t=e.headers.get("date"),r=new Date(t).getTime();return isNaN(r)?null:r}cacheDidUpdate({cacheName:e,request:t}){var r=this;return babelHelpers.asyncToGenerator(function*(){const n=r.f(e);yield n.updateTimestamp(t.url),yield n.expireEntries()})()}deleteCacheAndMetadata(){var e=this;return babelHelpers.asyncToGenerator(function*(){for(const[t,r]of e.b)yield caches.delete(t),yield r.delete();e.b=new Map})()}},e}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-cache-expiration.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-cache-expiration.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-cache-expiration/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.expiration=function(e,t,r,n,i){\"use strict\";try{self.workbox.v[\"workbox:cache-expiration:3.3.0\"]=1}catch(e){}const s=\"url\",a=\"timestamp\";class l{constructor(e){this.e=e,this.t=e,this.r=new t.DBWrapper(this.e,2,{onupgradeneeded:e=>this.n(e)})}n(e){const t=e.target.result;e.oldVersion<2&&t.objectStoreNames.contains(\"workbox-cache-expiration\")&&t.deleteObjectStore(\"workbox-cache-expiration\"),t.createObjectStore(this.t,{keyPath:s}).createIndex(a,a,{unique:!1})}setTimestamp(e,t){var r=this;return babelHelpers.asyncToGenerator(function*(){yield r.r.put(r.t,{[s]:new URL(e,location).href,[a]:t})})()}getAllTimestamps(){var e=this;return babelHelpers.asyncToGenerator(function*(){return yield e.r.getAllMatching(e.t,{index:a})})()}getTimestamp(e){var t=this;return babelHelpers.asyncToGenerator(function*(){return(yield t.r.get(t.t,e)).timestamp})()}deleteUrl(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.r.delete(t.t,new URL(e,location).href)})()}delete(){var e=this;return babelHelpers.asyncToGenerator(function*(){yield e.r.deleteDatabase(),e.r=null})()}}class o{constructor(e,t={}){this.i=!1,this.s=!1,this.a=t.maxEntries,this.l=t.maxAgeSeconds,this.e=e,this.o=new l(e)}expireEntries(){var e=this;return babelHelpers.asyncToGenerator(function*(){if(e.i)return void(e.s=!0);e.i=!0;const t=Date.now(),r=yield e.c(t),n=yield e.u(),i=[...new Set(r.concat(n))];yield Promise.all([e.h(i),e.d(i)]),e.i=!1,e.s&&(e.s=!1,e.expireEntries())})()}c(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!t.l)return[];const r=e-1e3*t.l,n=[];return(yield t.o.getAllTimestamps()).forEach(function(e){e.timestampe.a;){const e=r.shift();t.push(e.url)}return t})()}h(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=yield caches.open(t.e);for(const t of e)yield r.delete(t)})()}d(e){var t=this;return babelHelpers.asyncToGenerator(function*(){for(const r of e)yield t.o.deleteUrl(r)})()}updateTimestamp(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const r=new URL(e,location);r.hash=\"\",yield t.o.setTimestamp(r.href,Date.now())})()}isURLExpired(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!t.l)throw new r.WorkboxError(\"expired-test-without-max-age\",{methodName:\"isURLExpired\",paramName:\"maxAgeSeconds\"});const n=new URL(e,location);return n.hash=\"\",(yield t.o.getTimestamp(n.href))this.deleteCacheAndMetadata())}f(e){if(e===n.cacheNames.getRuntimeName())throw new r.WorkboxError(\"expire-custom-caches-only\");let t=this.b.get(e);return t||(t=new o(e,this.p),this.b.set(e,t)),t}cachedResponseWillBeUsed({cacheName:e,cachedResponse:t}){if(!t)return null;let r=this.m(t);return this.f(e).expireEntries(),r?t:null}m(e){if(!this.l)return!0;const t=this.y(e);return null===t||t>=Date.now()-1e3*this.l}y(e){if(!e.headers.has(\"date\"))return null;const t=e.headers.get(\"date\"),r=new Date(t).getTime();return isNaN(r)?null:r}cacheDidUpdate({cacheName:e,request:t}){var r=this;return babelHelpers.asyncToGenerator(function*(){const n=r.f(e);yield n.updateTimestamp(t.url),yield n.expireEntries()})()}deleteCacheAndMetadata(){var e=this;return babelHelpers.asyncToGenerator(function*(){for(const[t,r]of e.b)yield caches.delete(t),yield r.delete();e.b=new Map})()}},e}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);\n"],"file":"workbox-cache-expiration.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-cacheable-response.dev.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-cacheable-response/browser.mjs"],"sourcesContent":["this.workbox = this.workbox || {};\nthis.workbox.cacheableResponse = (function (exports,WorkboxError_mjs,assert_mjs,getFriendlyURL_mjs,logger_mjs) {\n 'use strict';\n\n try {\n self.workbox.v['workbox:cacheable-response:3.3.0'] = 1;\n } catch (e) {} // eslint-disable-line\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /**\n * This class allows you to set up rules determining what\n * status codes and/or headers need to be present in order for a\n * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n * to be considered cacheable.\n *\n * @memberof workbox.cacheableResponse\n */\n class CacheableResponse {\n /**\n * To construct a new CacheableResponse instance you must provide at least\n * one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config = {}) {\n {\n if (!(config.statuses || config.headers)) {\n throw new WorkboxError_mjs.WorkboxError('statuses-or-headers-required', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor'\n });\n }\n\n if (config.statuses) {\n assert_mjs.assert.isArray(config.statuses, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.statuses'\n });\n }\n\n if (config.headers) {\n assert_mjs.assert.isType(config.headers, 'object', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.headers'\n });\n }\n }\n\n this._statuses = config.statuses;\n this._headers = config.headers;\n }\n\n /**\n * Checks a response to see whether it's cacheable or not, based on this\n * object's configuration.\n *\n * @param {Response} response The response whose cacheability is being\n * checked.\n * @return {boolean} `true` if the `Response` is cacheable, and `false`\n * otherwise.\n */\n isResponseCacheable(response) {\n {\n assert_mjs.assert.isInstance(response, Response, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'isResponseCacheable',\n paramName: 'response'\n });\n }\n\n let cacheable = true;\n\n if (this._statuses) {\n cacheable = this._statuses.includes(response.status);\n }\n\n if (this._headers && cacheable) {\n cacheable = Object.keys(this._headers).some(headerName => {\n return response.headers.get(headerName) === this._headers[headerName];\n });\n }\n\n {\n if (!cacheable) {\n logger_mjs.logger.groupCollapsed(`The request for ` + `'${getFriendlyURL_mjs.getFriendlyURL(response.url)}' returned a response that does ` + `not meet the criteria for being cached.`);\n\n logger_mjs.logger.groupCollapsed(`View cacheability criteria here.`);\n logger_mjs.logger.unprefixed.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));\n logger_mjs.logger.unprefixed.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));\n logger_mjs.logger.groupEnd();\n\n const logFriendlyHeaders = {};\n response.headers.forEach((value, key) => {\n logFriendlyHeaders[key] = value;\n });\n\n logger_mjs.logger.groupCollapsed(`View response status and headers here.`);\n logger_mjs.logger.unprefixed.log(`Response status: ` + response.status);\n logger_mjs.logger.unprefixed.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));\n logger_mjs.logger.groupEnd();\n\n logger_mjs.logger.groupCollapsed(`View full response details here.`);\n logger_mjs.logger.unprefixed.log(response.headers);\n logger_mjs.logger.unprefixed.log(response);\n logger_mjs.logger.groupEnd();\n\n logger_mjs.logger.groupEnd();\n }\n }\n\n return cacheable;\n }\n }\n\n /*\n Copyright 2016 Google Inc. All Rights Reserved.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /**\n * A class implementing the `cacheWillUpdate` lifecycle callback. This makes it\n * easier to add in cacheability checks to requests made via Workbox's built-in\n * strategies.\n *\n * @memberof workbox.cacheableResponse\n */\n class Plugin {\n /**\n * To construct a new cacheable response Plugin instance you must provide at\n * least one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config) {\n this._cacheableResponse = new CacheableResponse(config);\n }\n\n /**\n * @param {Object} options\n * @param {Response} options.response\n * @return {boolean}\n * @private\n */\n cacheWillUpdate({ response }) {\n if (this._cacheableResponse.isResponseCacheable(response)) {\n return response;\n }\n return null;\n }\n }\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n exports.CacheableResponse = CacheableResponse;\n exports.Plugin = Plugin;\n\n return exports;\n\n}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private));\n"],"file":"workbox-cacheable-response.dev.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-cacheable-response.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(t){"use strict";try{self.workbox.v["workbox:cacheable-response:3.3.0"]=1}catch(t){}class s{constructor(t={}){this.t=t.statuses,this.s=t.headers}isResponseCacheable(t){let s=!0;return this.t&&(s=this.t.includes(t.status)),this.s&&s&&(s=Object.keys(this.s).some(s=>t.headers.get(s)===this.s[s])),s}}return t.CacheableResponse=s,t.Plugin=class{constructor(t){this.e=new s(t)}cacheWillUpdate({response:t}){return this.e.isResponseCacheable(t)?t:null}},t}({}); 2 | 3 | //# sourceMappingURL=workbox-cacheable-response.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-cacheable-response.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-cacheable-response/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(t){\"use strict\";try{self.workbox.v[\"workbox:cacheable-response:3.3.0\"]=1}catch(t){}class s{constructor(t={}){this.t=t.statuses,this.s=t.headers}isResponseCacheable(t){let s=!0;return this.t&&(s=this.t.includes(t.status)),this.s&&s&&(s=Object.keys(this.s).some(s=>t.headers.get(s)===this.s[s])),s}}return t.CacheableResponse=s,t.Plugin=class{constructor(t){this.e=new s(t)}cacheWillUpdate({response:t}){return this.e.isResponseCacheable(t)?t:null}},t}({});\n"],"file":"workbox-cacheable-response.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-core.prod.js: -------------------------------------------------------------------------------- 1 | self.babelHelpers={asyncToGenerator:function(e){return function(){var r=e.apply(this,arguments);return new Promise(function(e,t){return function n(o,i){try{var c=r[o](i),l=c.value}catch(e){return void t(e)}if(!c.done)return Promise.resolve(l).then(function(e){n("next",e)},function(e){n("throw",e)});e(l)}("next")})}}},this.workbox=this.workbox||{},this.workbox.core=function(){"use strict";try{self.workbox.v["workbox:core:3.3.0"]=1}catch(e){}var e={debug:0,log:1,warn:2,error:3,silent:4};const r=(e,...r)=>{let t=e;return r.length>0&&(t+=` :: ${JSON.stringify(r)}`),t};class t extends Error{constructor(e,t){super(r(e,t)),this.name=e,this.details=t}}const n={prefix:"workbox",suffix:self.registration.scope,googleAnalytics:"googleAnalytics",precache:"precache",runtime:"runtime"},o=e=>[n.prefix,e,n.suffix].filter(e=>e.length>0).join("-"),i={updateDetails:e=>{Object.keys(n).forEach(r=>{void 0!==e[r]&&(n[r]=e[r])})},getGoogleAnalyticsName:e=>e||o(n.googleAnalytics),getPrecacheName:e=>e||o(n.precache),getRuntimeName:e=>e||o(n.runtime)},c=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let l=(()=>e.warn)();const u=e=>l<=e,s=e=>l=e,a=()=>l,f=e.error,d=function(r,t,n){const o=0===r.indexOf("group")?f:e[r];if(!u(o))return;if(!n||"groupCollapsed"===r&&c)return void console[r](...t);const i=["%cworkbox",`background: ${n}; color: white; padding: 2px 0.5em; `+"border-radius: 0.5em;"];console[r](...i,...t)},h=()=>{u(f)&&console.groupEnd()},p={groupEnd:h,unprefixed:{groupEnd:h}},b={debug:"#7f8c8d",log:"#2ecc71",warn:"#f39c12",error:"#c0392b",groupCollapsed:"#3498db"};var y,g;Object.keys(b).forEach(e=>(e=e,g=b[e],p[e]=((...r)=>d(e,r,g)),void(p.unprefixed[e]=((...r)=>d(e,r)))));var v=new class{constructor(){try{self.workbox.v=self.workbox.v||{}}catch(e){}}get cacheNames(){return{googleAnalytics:i.getGoogleAnalyticsName(),precache:i.getPrecacheName(),runtime:i.getRuntimeName()}}setCacheNameDetails(e){i.updateDetails(e)}get logLevel(){return a()}setLogLevel(r){if(r>e.silent||r=r.count?n(i):t.continue()}else n(i)}})})()}transaction(e,r,t){var n=this;return babelHelpers.asyncToGenerator(function*(){return yield n.open(),yield new Promise(function(o,i){const c=n.o.transaction(e,r);c.onerror=function(e){return i(e.target.error)},c.onabort=function(e){return i(e.target.error)},c.oncomplete=function(){return o()};const l={};for(const r of e)l[r]=c.objectStore(r);t(l,function(e){return o(e)},function(){i(new Error("The transaction was manually aborted")),c.abort()})})})()}i(e,r,t,...n){var o=this;return babelHelpers.asyncToGenerator(function*(){yield o.open();return yield o.transaction([r],t,function(t,o){t[r][e](...n).onsuccess=function(e){o(e.target.result)}})})()}e(e){this.close()}close(){this.o&&this.o.close()}}w.prototype.OPEN_TIMEOUT=2e3;var m="cacheDidUpdate",E="cacheWillUpdate",L="cachedResponseWillBeUsed",H="fetchDidFail",x="requestWillFetch",k=(e,r)=>e.filter(e=>r in e);let q=(D=babelHelpers.asyncToGenerator(function*(){for(const e of N)yield e()}),function(){return D.apply(this,arguments)});var D;const N=new Set;const O=e=>{const r=new URL(e,location);return r.origin===location.origin?r.pathname:r.href},R=(()=>{var e=babelHelpers.asyncToGenerator(function*(e,r,n,o=[]){if(!n)throw new t("cache-put-with-no-response",{url:O(r.url)});let i=yield W(r,n,o);if(!i)return;const c=yield caches.open(e),l=k(o,m);let u=l.length>0?yield A(e,r):null;try{yield c.put(r,i)}catch(e){throw"QuotaExceededError"===e.name&&(yield q()),e}for(let t of l)yield t[m].call(t,{cacheName:e,request:r,oldResponse:u,newResponse:i})});return function(r,t,n){return e.apply(this,arguments)}})(),A=(P=babelHelpers.asyncToGenerator(function*(e,r,t,n=[]){let o=yield(yield caches.open(e)).match(r,t);for(let i of n)L in i&&(o=yield i[L].call(i,{cacheName:e,request:r,matchOptions:t,cachedResponse:o}));return o}),function(e,r,t){return P.apply(this,arguments)});var P;const W=(S=babelHelpers.asyncToGenerator(function*(e,r,t){let n=r,o=!1;for(let r of t)if(E in r&&(o=!0,!(n=yield r[E].call(r,{request:e,response:n}))))break;return o||(n=n.ok?n:null),n||null}),function(e,r,t){return S.apply(this,arguments)});var S;const _={put:R,match:A},j={fetch:(()=>{var e=babelHelpers.asyncToGenerator(function*(e,r,n=[]){"string"==typeof e&&(e=new Request(e));const o=k(n,H),i=o.length>0?e.clone():null;try{for(let r of n)x in r&&(e=yield r[x].call(r,{request:e.clone()}))}catch(e){throw new t("plugin-error-request-will-fetch",{thrownError:e})}const c=e.clone();try{return yield fetch(e,r)}catch(e){for(let r of o)yield r[H].call(r,{error:e,originalRequest:i.clone(),request:c.clone()});throw e}});return function(r,t){return e.apply(this,arguments)}})()};var B=Object.freeze({DBWrapper:w,WorkboxError:t,assert:null,cacheNames:i,cacheWrapper:_,fetchWrapper:j,getFriendlyURL:O,logger:p,registerQuotaErrorCallback:function(e){N.add(e)}});return Object.assign(v,{LOG_LEVELS:e,_private:B})}(); 2 | 3 | //# sourceMappingURL=workbox-core.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-core.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-core/browser.mjs"],"sourcesContent":["self.babelHelpers={asyncToGenerator:function(e){return function(){var r=e.apply(this,arguments);return new Promise(function(e,t){return function n(o,i){try{var c=r[o](i),l=c.value}catch(e){return void t(e)}if(!c.done)return Promise.resolve(l).then(function(e){n(\"next\",e)},function(e){n(\"throw\",e)});e(l)}(\"next\")})}}},this.workbox=this.workbox||{},this.workbox.core=function(){\"use strict\";try{self.workbox.v[\"workbox:core:3.3.0\"]=1}catch(e){}var e={debug:0,log:1,warn:2,error:3,silent:4};const r=(e,...r)=>{let t=e;return r.length>0&&(t+=` :: ${JSON.stringify(r)}`),t};class t extends Error{constructor(e,t){super(r(e,t)),this.name=e,this.details=t}}const n={prefix:\"workbox\",suffix:self.registration.scope,googleAnalytics:\"googleAnalytics\",precache:\"precache\",runtime:\"runtime\"},o=e=>[n.prefix,e,n.suffix].filter(e=>e.length>0).join(\"-\"),i={updateDetails:e=>{Object.keys(n).forEach(r=>{void 0!==e[r]&&(n[r]=e[r])})},getGoogleAnalyticsName:e=>e||o(n.googleAnalytics),getPrecacheName:e=>e||o(n.precache),getRuntimeName:e=>e||o(n.runtime)},c=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let l=(()=>e.warn)();const u=e=>l<=e,s=e=>l=e,a=()=>l,f=e.error,d=function(r,t,n){const o=0===r.indexOf(\"group\")?f:e[r];if(!u(o))return;if(!n||\"groupCollapsed\"===r&&c)return void console[r](...t);const i=[\"%cworkbox\",`background: ${n}; color: white; padding: 2px 0.5em; `+\"border-radius: 0.5em;\"];console[r](...i,...t)},h=()=>{u(f)&&console.groupEnd()},p={groupEnd:h,unprefixed:{groupEnd:h}},b={debug:\"#7f8c8d\",log:\"#2ecc71\",warn:\"#f39c12\",error:\"#c0392b\",groupCollapsed:\"#3498db\"};var y,g;Object.keys(b).forEach(e=>(e=e,g=b[e],p[e]=((...r)=>d(e,r,g)),void(p.unprefixed[e]=((...r)=>d(e,r)))));var v=new class{constructor(){try{self.workbox.v=self.workbox.v||{}}catch(e){}}get cacheNames(){return{googleAnalytics:i.getGoogleAnalyticsName(),precache:i.getPrecacheName(),runtime:i.getRuntimeName()}}setCacheNameDetails(e){i.updateDetails(e)}get logLevel(){return a()}setLogLevel(r){if(r>e.silent||r=r.count?n(i):t.continue()}else n(i)}})})()}transaction(e,r,t){var n=this;return babelHelpers.asyncToGenerator(function*(){return yield n.open(),yield new Promise(function(o,i){const c=n.o.transaction(e,r);c.onerror=function(e){return i(e.target.error)},c.onabort=function(e){return i(e.target.error)},c.oncomplete=function(){return o()};const l={};for(const r of e)l[r]=c.objectStore(r);t(l,function(e){return o(e)},function(){i(new Error(\"The transaction was manually aborted\")),c.abort()})})})()}i(e,r,t,...n){var o=this;return babelHelpers.asyncToGenerator(function*(){yield o.open();return yield o.transaction([r],t,function(t,o){t[r][e](...n).onsuccess=function(e){o(e.target.result)}})})()}e(e){this.close()}close(){this.o&&this.o.close()}}w.prototype.OPEN_TIMEOUT=2e3;var m=\"cacheDidUpdate\",E=\"cacheWillUpdate\",L=\"cachedResponseWillBeUsed\",H=\"fetchDidFail\",x=\"requestWillFetch\",k=(e,r)=>e.filter(e=>r in e);let q=(D=babelHelpers.asyncToGenerator(function*(){for(const e of N)yield e()}),function(){return D.apply(this,arguments)});var D;const N=new Set;const O=e=>{const r=new URL(e,location);return r.origin===location.origin?r.pathname:r.href},R=(()=>{var e=babelHelpers.asyncToGenerator(function*(e,r,n,o=[]){if(!n)throw new t(\"cache-put-with-no-response\",{url:O(r.url)});let i=yield W(r,n,o);if(!i)return;const c=yield caches.open(e),l=k(o,m);let u=l.length>0?yield A(e,r):null;try{yield c.put(r,i)}catch(e){throw\"QuotaExceededError\"===e.name&&(yield q()),e}for(let t of l)yield t[m].call(t,{cacheName:e,request:r,oldResponse:u,newResponse:i})});return function(r,t,n){return e.apply(this,arguments)}})(),A=(P=babelHelpers.asyncToGenerator(function*(e,r,t,n=[]){let o=yield(yield caches.open(e)).match(r,t);for(let i of n)L in i&&(o=yield i[L].call(i,{cacheName:e,request:r,matchOptions:t,cachedResponse:o}));return o}),function(e,r,t){return P.apply(this,arguments)});var P;const W=(S=babelHelpers.asyncToGenerator(function*(e,r,t){let n=r,o=!1;for(let r of t)if(E in r&&(o=!0,!(n=yield r[E].call(r,{request:e,response:n}))))break;return o||(n=n.ok?n:null),n||null}),function(e,r,t){return S.apply(this,arguments)});var S;const _={put:R,match:A},j={fetch:(()=>{var e=babelHelpers.asyncToGenerator(function*(e,r,n=[]){\"string\"==typeof e&&(e=new Request(e));const o=k(n,H),i=o.length>0?e.clone():null;try{for(let r of n)x in r&&(e=yield r[x].call(r,{request:e.clone()}))}catch(e){throw new t(\"plugin-error-request-will-fetch\",{thrownError:e})}const c=e.clone();try{return yield fetch(e,r)}catch(e){for(let r of o)yield r[H].call(r,{error:e,originalRequest:i.clone(),request:c.clone()});throw e}});return function(r,t){return e.apply(this,arguments)}})()};var B=Object.freeze({DBWrapper:w,WorkboxError:t,assert:null,cacheNames:i,cacheWrapper:_,fetchWrapper:j,getFriendlyURL:O,logger:p,registerQuotaErrorCallback:function(e){N.add(e)}});return Object.assign(v,{LOG_LEVELS:e,_private:B})}();\n"],"file":"workbox-core.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-google-analytics.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(e,n,t,o,r,c,s){"use strict";try{self.workbox.v["workbox:google-analytics:3.3.0"]=1}catch(e){}const l=/^\/(\w+\/)?collect/,i=(a=babelHelpers.asyncToGenerator(function*(e){return yield new Promise(function(n,t){const o=new FileReader;o.onloadend=function(){return n(o.result)},o.onerror=function(){return t(o.error)},o.readAsText(e)})}),function(e){return a.apply(this,arguments)});var a;const w=e=>(u=babelHelpers.asyncToGenerator(function*(n){let t,{url:o,requestInit:r,timestamp:c}=n;if(o=new URL(o),r.body){const e=r.body instanceof Blob?yield i(r.body):r.body;t=new URLSearchParams(e)}else t=o.searchParams;const s=c-(Number(t.get("qt"))||0),l=Date.now()-s;if(t.set("qt",l),e.parameterOverrides)for(const n of Object.keys(e.parameterOverrides)){const o=e.parameterOverrides[n];t.set(n,o)}"function"==typeof e.hitFilter&&e.hitFilter.call(null,t),r.body=t.toString(),r.method="POST",r.mode="cors",r.credentials="omit",r.headers={"Content-Type":"text/plain"},n.url=`${o.origin}${o.pathname}`}),function(e){return u.apply(this,arguments)});var u;return e.initialize=((e={})=>{const i=t.cacheNames.getGoogleAnalyticsName(e.cacheName),a=new n.Plugin("workbox-google-analytics",{maxRetentionTime:2880,callbacks:{requestWillReplay:w(e)}}),u=[(e=>{const n=new c.NetworkFirst({cacheName:e});return new o.Route(({url:e})=>"www.google-analytics.com"===e.hostname&&"/analytics.js"===e.pathname,n,"GET")})(i),(e=>{const n=new c.NetworkFirst({cacheName:e});return new o.Route(({url:e})=>"www.googletagmanager.com"===e.hostname&&"/gtag/js"===e.pathname,n,"GET")})(i),...(e=>{const n=({url:e})=>"www.google-analytics.com"===e.hostname&&l.test(e.pathname),t=new s.NetworkOnly({plugins:[e]});return[new o.Route(n,t,"GET"),new o.Route(n,t,"POST")]})(a)],f=new r.Router;for(const e of u)f.registerRoute(e);self.addEventListener("fetch",e=>{const n=f.handleRequest(e);n&&e.respondWith(n)})}),e}({},workbox.backgroundSync,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies); 2 | 3 | //# sourceMappingURL=workbox-google-analytics.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-google-analytics.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-google-analytics/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(e,n,t,o,r,c,s){\"use strict\";try{self.workbox.v[\"workbox:google-analytics:3.3.0\"]=1}catch(e){}const l=/^\\/(\\w+\\/)?collect/,i=(a=babelHelpers.asyncToGenerator(function*(e){return yield new Promise(function(n,t){const o=new FileReader;o.onloadend=function(){return n(o.result)},o.onerror=function(){return t(o.error)},o.readAsText(e)})}),function(e){return a.apply(this,arguments)});var a;const w=e=>(u=babelHelpers.asyncToGenerator(function*(n){let t,{url:o,requestInit:r,timestamp:c}=n;if(o=new URL(o),r.body){const e=r.body instanceof Blob?yield i(r.body):r.body;t=new URLSearchParams(e)}else t=o.searchParams;const s=c-(Number(t.get(\"qt\"))||0),l=Date.now()-s;if(t.set(\"qt\",l),e.parameterOverrides)for(const n of Object.keys(e.parameterOverrides)){const o=e.parameterOverrides[n];t.set(n,o)}\"function\"==typeof e.hitFilter&&e.hitFilter.call(null,t),r.body=t.toString(),r.method=\"POST\",r.mode=\"cors\",r.credentials=\"omit\",r.headers={\"Content-Type\":\"text/plain\"},n.url=`${o.origin}${o.pathname}`}),function(e){return u.apply(this,arguments)});var u;return e.initialize=((e={})=>{const i=t.cacheNames.getGoogleAnalyticsName(e.cacheName),a=new n.Plugin(\"workbox-google-analytics\",{maxRetentionTime:2880,callbacks:{requestWillReplay:w(e)}}),u=[(e=>{const n=new c.NetworkFirst({cacheName:e});return new o.Route(({url:e})=>\"www.google-analytics.com\"===e.hostname&&\"/analytics.js\"===e.pathname,n,\"GET\")})(i),(e=>{const n=new c.NetworkFirst({cacheName:e});return new o.Route(({url:e})=>\"www.googletagmanager.com\"===e.hostname&&\"/gtag/js\"===e.pathname,n,\"GET\")})(i),...(e=>{const n=({url:e})=>\"www.google-analytics.com\"===e.hostname&&l.test(e.pathname),t=new s.NetworkOnly({plugins:[e]});return[new o.Route(n,t,\"GET\"),new o.Route(n,t,\"POST\")]})(a)],f=new r.Router;for(const e of u)f.registerRoute(e);self.addEventListener(\"fetch\",e=>{const n=f.handleRequest(e);n&&e.respondWith(n)})}),e}({},workbox.backgroundSync,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies);\n"],"file":"workbox-google-analytics.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-precaching.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.precaching=function(e,t,n,r,s){"use strict";try{self.workbox.v["workbox:precaching:3.3.0"]=1}catch(e){}class i{constructor(e,t,n,r){this.e=e,this.t=t,this.n=n;const s=new Request(t,{credentials:"same-origin"});this.r=s,this.s=r?this.i(s):s}i(e){let t=e.url;const n={credentials:"same-origin"};if("cache"in Request.prototype)n.cache="reload";else{const e=new URL(t,location),n=encodeURIComponent;e.search+=(e.search?"&":"")+n("_workbox-cache-bust")+"="+n(this.n),t=e.toString()}return new Request(t,n)}}const c="revision",o="url",l="precached-details-models";class a{constructor(t){const n=t.replace(/[^\w-]/g,"_");this.c=new e.DBWrapper(n,2,{onupgradeneeded:this.o})}o(e){const t=e.target.result;e.oldVersion<2&&(t.objectStoreNames.contains("workbox-precaching")&&t.deleteObjectStore("workbox-precaching"),t.objectStoreNames.contains(l)&&t.deleteObjectStore(l)),t.createObjectStore(l)}l(e,t){var n=this;return babelHelpers.asyncToGenerator(function*(){return(yield n.a(t.t))===t.n&&!!(yield(yield caches.open(e)).match(t.r))})()}u(){var e=this;return babelHelpers.asyncToGenerator(function*(){return yield e.c.getAllMatching(l,{includeKeys:!0})})()}a(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield t.c.get(l,e);return n?n[c]:null})()}d(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.c.put(l,{[c]:e.n,[o]:e.r.url},e.t)})()}h(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.c.delete(l,e)})()}}const u=(d=babelHelpers.asyncToGenerator(function*(e){const t=e.clone(),n=yield"body"in t?Promise.resolve(t.body):t.blob();return new Response(n,["headers","status","statusText"].map(function(e){return t[e]}))}),function(e){return d.apply(this,arguments)});var d;class h{constructor(e){this.f=t.cacheNames.getPrecacheName(e),this.y=new Map,this.p=new a(this.f)}addToCacheList(e){e.map(e=>{this.b(this.w(e))})}w(e){switch(typeof e){case"string":return new i(e,e,e);case"object":return new i(e,e.url,e.revision||e.url,!!e.revision);default:throw new n.WorkboxError("add-to-cache-list-unexpected-type",{entry:e})}}b(e){const t=this.y.get(e.t);if(t){if(t.n!==e.n)throw new n.WorkboxError("add-to-cache-list-conflicting-entries",{firstEntry:t.e,secondEntry:e.e})}else this.y.set(e.t,e)}install(e={}){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield caches.open(t.g()),r=yield n.keys();yield Promise.all(r.map(function(e){return n.delete(e)}));const s=[],i=[];for(const e of t.y.values())(yield t.p.l(t.f,e))?i.push(e):s.push(e);return yield Promise.all(s.map(function(n){return t.R(n,e.plugins)})),{updatedEntries:s,notUpdatedEntries:i}})()}activate(e={}){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield caches.open(t.g()),r=yield n.keys();for(const i of r){const r=yield n.match(i);yield s.cacheWrapper.put(t.f,i,r,e.plugins),yield n.delete(i)}return t.m()})()}g(){return`${this.f}-temp`}R(e,t){var n=this;return babelHelpers.asyncToGenerator(function*(){let i=yield r.fetchWrapper.fetch(e.s,null,t);return i.redirected&&(i=yield u(i)),yield s.cacheWrapper.put(n.g(),e.r,i,t),yield n.p.d(e),!0})()}m(){var e=this;return babelHelpers.asyncToGenerator(function*(){const t=[];e.y.forEach(function(e){const n=new URL(e.r.url,location).toString();t.push(n)});const[n,r]=yield Promise.all([e._(t),e.U(t)]);return{deletedCacheRequests:n,deletedRevisionDetails:r}})()}_(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!(yield caches.has(t.f)))return[];const n=yield caches.open(t.f),r=(yield n.keys()).filter(function(t){return!e.includes(new URL(t.url,location).toString())});return yield Promise.all(r.map(function(e){return n.delete(e)})),r.map(function(e){return e.url})})()}U(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=(yield t.p.u()).filter(function(t){const n=new URL(t.value.url,location).toString();return!e.includes(n)});return yield Promise.all(n.map(function(e){return t.p.h(e.primaryKey)})),n.map(function(e){return e.value.url})})()}getCachedUrls(){return Array.from(this.y.keys()).map(e=>new URL(e,location).href)}}var f=Object.freeze({PrecacheController:h});let y=!1,p=!1,b=!1,w=[];const g=t.cacheNames.getPrecacheName(),R=new h(g),m=(e,{ignoreUrlParametersMatching:t=[/^utm_/],directoryIndex:n="index.html",cleanUrls:r=!0,urlManipulation:s=null}={})=>{const i=new URL(e,location);i.hash="";const c=((e,t)=>{const n=e.search.slice(1).split("&").map(e=>e.split("=")).filter(e=>t.every(t=>!t.test(e[0]))).map(e=>e.join("=")),r=new URL(e);return r.search=n.join("&"),r})(i,t);let o=[i,c];if(n&&c.pathname.endsWith("/")){const e=new URL(c);e.pathname+=n,o.push(e)}if(r){const e=new URL(c);e.pathname+=".html",o.push(e)}if(s){const e=s({url:i});o=o.concat(e)}const l=R.getCachedUrls();for(const e of o)if(-1!==l.indexOf(e.href))return e.href;return null},v={precache:e=>{R.addToCacheList(e),y||e.length<=0||(y=!0,self.addEventListener("install",e=>{e.waitUntil(R.install({suppressWarnings:b,plugins:w}))}),self.addEventListener("activate",e=>{e.waitUntil(R.activate({plugins:w}))}))},addRoute:e=>{p||(p=!0,self.addEventListener("fetch",t=>{const n=m(t.request.url,e);if(!n)return;let r=caches.open(g).then(e=>e.match(n)).then(e=>e||fetch(n));t.respondWith(r)}))},precacheAndRoute:(e,t)=>{v.precache(e),v.addRoute(t)},suppressWarnings:e=>{b=e},addPlugins:e=>{w=w.concat(e)}};return Object.assign(v,f)}(workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-precaching.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-precaching.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-precaching/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.precaching=function(e,t,n,r,s){\"use strict\";try{self.workbox.v[\"workbox:precaching:3.3.0\"]=1}catch(e){}class i{constructor(e,t,n,r){this.e=e,this.t=t,this.n=n;const s=new Request(t,{credentials:\"same-origin\"});this.r=s,this.s=r?this.i(s):s}i(e){let t=e.url;const n={credentials:\"same-origin\"};if(\"cache\"in Request.prototype)n.cache=\"reload\";else{const e=new URL(t,location),n=encodeURIComponent;e.search+=(e.search?\"&\":\"\")+n(\"_workbox-cache-bust\")+\"=\"+n(this.n),t=e.toString()}return new Request(t,n)}}const c=\"revision\",o=\"url\",l=\"precached-details-models\";class a{constructor(t){const n=t.replace(/[^\\w-]/g,\"_\");this.c=new e.DBWrapper(n,2,{onupgradeneeded:this.o})}o(e){const t=e.target.result;e.oldVersion<2&&(t.objectStoreNames.contains(\"workbox-precaching\")&&t.deleteObjectStore(\"workbox-precaching\"),t.objectStoreNames.contains(l)&&t.deleteObjectStore(l)),t.createObjectStore(l)}l(e,t){var n=this;return babelHelpers.asyncToGenerator(function*(){return(yield n.a(t.t))===t.n&&!!(yield(yield caches.open(e)).match(t.r))})()}u(){var e=this;return babelHelpers.asyncToGenerator(function*(){return yield e.c.getAllMatching(l,{includeKeys:!0})})()}a(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield t.c.get(l,e);return n?n[c]:null})()}d(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.c.put(l,{[c]:e.n,[o]:e.r.url},e.t)})()}h(e){var t=this;return babelHelpers.asyncToGenerator(function*(){yield t.c.delete(l,e)})()}}const u=(d=babelHelpers.asyncToGenerator(function*(e){const t=e.clone(),n=yield\"body\"in t?Promise.resolve(t.body):t.blob();return new Response(n,[\"headers\",\"status\",\"statusText\"].map(function(e){return t[e]}))}),function(e){return d.apply(this,arguments)});var d;class h{constructor(e){this.f=t.cacheNames.getPrecacheName(e),this.y=new Map,this.p=new a(this.f)}addToCacheList(e){e.map(e=>{this.b(this.w(e))})}w(e){switch(typeof e){case\"string\":return new i(e,e,e);case\"object\":return new i(e,e.url,e.revision||e.url,!!e.revision);default:throw new n.WorkboxError(\"add-to-cache-list-unexpected-type\",{entry:e})}}b(e){const t=this.y.get(e.t);if(t){if(t.n!==e.n)throw new n.WorkboxError(\"add-to-cache-list-conflicting-entries\",{firstEntry:t.e,secondEntry:e.e})}else this.y.set(e.t,e)}install(e={}){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield caches.open(t.g()),r=yield n.keys();yield Promise.all(r.map(function(e){return n.delete(e)}));const s=[],i=[];for(const e of t.y.values())(yield t.p.l(t.f,e))?i.push(e):s.push(e);return yield Promise.all(s.map(function(n){return t.R(n,e.plugins)})),{updatedEntries:s,notUpdatedEntries:i}})()}activate(e={}){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=yield caches.open(t.g()),r=yield n.keys();for(const i of r){const r=yield n.match(i);yield s.cacheWrapper.put(t.f,i,r,e.plugins),yield n.delete(i)}return t.m()})()}g(){return`${this.f}-temp`}R(e,t){var n=this;return babelHelpers.asyncToGenerator(function*(){let i=yield r.fetchWrapper.fetch(e.s,null,t);return i.redirected&&(i=yield u(i)),yield s.cacheWrapper.put(n.g(),e.r,i,t),yield n.p.d(e),!0})()}m(){var e=this;return babelHelpers.asyncToGenerator(function*(){const t=[];e.y.forEach(function(e){const n=new URL(e.r.url,location).toString();t.push(n)});const[n,r]=yield Promise.all([e._(t),e.U(t)]);return{deletedCacheRequests:n,deletedRevisionDetails:r}})()}_(e){var t=this;return babelHelpers.asyncToGenerator(function*(){if(!(yield caches.has(t.f)))return[];const n=yield caches.open(t.f),r=(yield n.keys()).filter(function(t){return!e.includes(new URL(t.url,location).toString())});return yield Promise.all(r.map(function(e){return n.delete(e)})),r.map(function(e){return e.url})})()}U(e){var t=this;return babelHelpers.asyncToGenerator(function*(){const n=(yield t.p.u()).filter(function(t){const n=new URL(t.value.url,location).toString();return!e.includes(n)});return yield Promise.all(n.map(function(e){return t.p.h(e.primaryKey)})),n.map(function(e){return e.value.url})})()}getCachedUrls(){return Array.from(this.y.keys()).map(e=>new URL(e,location).href)}}var f=Object.freeze({PrecacheController:h});let y=!1,p=!1,b=!1,w=[];const g=t.cacheNames.getPrecacheName(),R=new h(g),m=(e,{ignoreUrlParametersMatching:t=[/^utm_/],directoryIndex:n=\"index.html\",cleanUrls:r=!0,urlManipulation:s=null}={})=>{const i=new URL(e,location);i.hash=\"\";const c=((e,t)=>{const n=e.search.slice(1).split(\"&\").map(e=>e.split(\"=\")).filter(e=>t.every(t=>!t.test(e[0]))).map(e=>e.join(\"=\")),r=new URL(e);return r.search=n.join(\"&\"),r})(i,t);let o=[i,c];if(n&&c.pathname.endsWith(\"/\")){const e=new URL(c);e.pathname+=n,o.push(e)}if(r){const e=new URL(c);e.pathname+=\".html\",o.push(e)}if(s){const e=s({url:i});o=o.concat(e)}const l=R.getCachedUrls();for(const e of o)if(-1!==l.indexOf(e.href))return e.href;return null},v={precache:e=>{R.addToCacheList(e),y||e.length<=0||(y=!0,self.addEventListener(\"install\",e=>{e.waitUntil(R.install({suppressWarnings:b,plugins:w}))}),self.addEventListener(\"activate\",e=>{e.waitUntil(R.activate({plugins:w}))}))},addRoute:e=>{p||(p=!0,self.addEventListener(\"fetch\",t=>{const n=m(t.request.url,e);if(!n)return;let r=caches.open(g).then(e=>e.match(n)).then(e=>e||fetch(n));t.respondWith(r)}))},precacheAndRoute:(e,t)=>{v.precache(e),v.addRoute(t)},suppressWarnings:e=>{b=e},addPlugins:e=>{w=w.concat(e)}};return Object.assign(v,f)}(workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);\n"],"file":"workbox-precaching.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-range-requests.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.rangeRequests=function(e,n){"use strict";try{self.workbox.v["workbox:range-requests:3.3.0"]=1}catch(e){}let t=(r=babelHelpers.asyncToGenerator(function*(e,t){try{const r=e.headers.get("range");if(!r)throw new n.WorkboxError("no-range-header");const s=function(e){const t=e.trim().toLowerCase();if(!t.startsWith("bytes="))throw new n.WorkboxError("unit-must-be-bytes",{normalizedRangeHeader:t});if(t.includes(","))throw new n.WorkboxError("single-range-only",{normalizedRangeHeader:t});const r=/(\d*)-(\d*)/.exec(t);if(null===r||!r[1]&&!r[2])throw new n.WorkboxError("invalid-range-values",{normalizedRangeHeader:t});return{start:""===r[1]?null:Number(r[1]),end:""===r[2]?null:Number(r[2])}}(r),a=yield t.blob(),i=function(e,t,r){const s=e.size;if(r>s||t<0)throw new n.WorkboxError("range-not-satisfiable",{size:s,end:r,start:t});let a,i;return null===t?(a=s-r,i=s):null===r?(a=t,i=s):(a=t,i=r+1),{start:a,end:i}}(a,s.start,s.end),l=a.slice(i.start,i.end),o=l.size,u=new Response(l,{status:206,statusText:"Partial Content",headers:t.headers});return u.headers.set("Content-Length",o),u.headers.set("Content-Range",`bytes ${i.start}-${i.end-1}/`+a.size),u}catch(e){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}),function(e,n){return r.apply(this,arguments)});var r;return e.createPartialResponse=t,e.Plugin=class{cachedResponseWillBeUsed({request:e,cachedResponse:n}){return babelHelpers.asyncToGenerator(function*(){return n&&e.headers.has("range")?yield t(e,n):n})()}},e}({},workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-range-requests.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-range-requests.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-range-requests/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.rangeRequests=function(e,n){\"use strict\";try{self.workbox.v[\"workbox:range-requests:3.3.0\"]=1}catch(e){}let t=(r=babelHelpers.asyncToGenerator(function*(e,t){try{const r=e.headers.get(\"range\");if(!r)throw new n.WorkboxError(\"no-range-header\");const s=function(e){const t=e.trim().toLowerCase();if(!t.startsWith(\"bytes=\"))throw new n.WorkboxError(\"unit-must-be-bytes\",{normalizedRangeHeader:t});if(t.includes(\",\"))throw new n.WorkboxError(\"single-range-only\",{normalizedRangeHeader:t});const r=/(\\d*)-(\\d*)/.exec(t);if(null===r||!r[1]&&!r[2])throw new n.WorkboxError(\"invalid-range-values\",{normalizedRangeHeader:t});return{start:\"\"===r[1]?null:Number(r[1]),end:\"\"===r[2]?null:Number(r[2])}}(r),a=yield t.blob(),i=function(e,t,r){const s=e.size;if(r>s||t<0)throw new n.WorkboxError(\"range-not-satisfiable\",{size:s,end:r,start:t});let a,i;return null===t?(a=s-r,i=s):null===r?(a=t,i=s):(a=t,i=r+1),{start:a,end:i}}(a,s.start,s.end),l=a.slice(i.start,i.end),o=l.size,u=new Response(l,{status:206,statusText:\"Partial Content\",headers:t.headers});return u.headers.set(\"Content-Length\",o),u.headers.set(\"Content-Range\",`bytes ${i.start}-${i.end-1}/`+a.size),u}catch(e){return new Response(\"\",{status:416,statusText:\"Range Not Satisfiable\"})}}),function(e,n){return r.apply(this,arguments)});var r;return e.createPartialResponse=t,e.Plugin=class{cachedResponseWillBeUsed({request:e,cachedResponse:n}){return babelHelpers.asyncToGenerator(function*(){return n&&e.headers.has(\"range\")?yield t(e,n):n})()}},e}({},workbox.core._private);\n"],"file":"workbox-range-requests.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-routing.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.routing=function(t,e){"use strict";try{self.workbox.v["workbox:routing:3.3.0"]=1}catch(t){}const r="GET";var s=t=>t&&"object"==typeof t?t:{handle:t};class n{constructor(t,e,n){this.handler=s(e),this.match=t,this.method=n||r}}class o extends n{constructor(t,e,r){super(({url:e})=>{const r=t.exec(e.href);return r?e.origin!==location.origin&&0!==r.index?null:r.slice(1):null},e,r)}}class i{constructor(){this.t=new Map}handleRequest(t){const e=new URL(t.request.url);if(!e.protocol.startsWith("http"))return;let r=null,s=null,n=null;const o=this.e(t,e);if(s=o.handler,n=o.params,r=o.route,!s&&this.r&&(s=this.r),!s)return;let i;try{i=s.handle({url:e,event:t,params:n})}catch(t){i=Promise.reject(t)}return i&&this.s&&(i=i.catch(r=>this.s.handle({url:e,event:t,err:r}))),i}e(t,e){const r=this.t.get(t.request.method)||[];for(const s of r){let r=s.match({url:e,event:t});if(r)return Array.isArray(r)&&0===r.length?r=void 0:(r.constructor===Object&&0===Object.keys(r).length||!0===r)&&(r=void 0),{route:s,params:r,handler:s.handler}}return{handler:void 0,params:void 0}}setDefaultHandler(t){this.r=s(t)}setCatchHandler(t){this.s=s(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(e){if(!this.t.has(e.method))throw new t.WorkboxError("unregister-route-but-not-found-with-method",{method:e.method});const r=this.t.get(e.method).indexOf(e);if(!(r>-1))throw new t.WorkboxError("unregister-route-route-not-registered");this.t.get(e.method).splice(r,1)}}class u extends n{constructor(t,{whitelist:e=[/./],blacklist:r=[]}={}){super((...t)=>this.n(...t),t),this.o=e,this.i=r}n({event:t,url:e}){if("navigate"!==t.request.mode)return!1;const r=e.pathname+e.search;return!this.i.some(t=>t.test(r))&&!!this.o.some(t=>t.test(r))}}var a=Object.freeze({RegExpRoute:o,Route:n,Router:i,NavigationRoute:u});const c=new class extends i{registerRoute(e,r,s="GET"){let i;if("string"==typeof e){const t=new URL(e,location);i=new n(({url:e})=>e.href===t.href,r,s)}else if(e instanceof RegExp)i=new o(e,r,s);else if("function"==typeof e)i=new n(e,r,s);else{if(!(e instanceof n))throw new t.WorkboxError("unsupported-route-type",{moduleName:"workbox-routing",className:"DefaultRouter",funcName:"registerRoute",paramName:"capture"});i=e}return super.registerRoute(i),i}registerNavigationRoute(t,r={}){const s=e.cacheNames.getPrecacheName(r.cacheName),n=new u(()=>caches.match(t,{cacheName:s}).then(e=>{if(e)return e;throw new Error(`The cache ${s} did not have an entry for `+`${t}.`)}).catch(e=>fetch(t)),{whitelist:r.whitelist,blacklist:r.blacklist});return super.registerRoute(n),n}};return self.addEventListener("fetch",t=>{const e=c.handleRequest(t);e&&t.respondWith(e)}),Object.assign(c,a)}(workbox.core._private,workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-routing.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-routing.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-routing/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.routing=function(t,e){\"use strict\";try{self.workbox.v[\"workbox:routing:3.3.0\"]=1}catch(t){}const r=\"GET\";var s=t=>t&&\"object\"==typeof t?t:{handle:t};class n{constructor(t,e,n){this.handler=s(e),this.match=t,this.method=n||r}}class o extends n{constructor(t,e,r){super(({url:e})=>{const r=t.exec(e.href);return r?e.origin!==location.origin&&0!==r.index?null:r.slice(1):null},e,r)}}class i{constructor(){this.t=new Map}handleRequest(t){const e=new URL(t.request.url);if(!e.protocol.startsWith(\"http\"))return;let r=null,s=null,n=null;const o=this.e(t,e);if(s=o.handler,n=o.params,r=o.route,!s&&this.r&&(s=this.r),!s)return;let i;try{i=s.handle({url:e,event:t,params:n})}catch(t){i=Promise.reject(t)}return i&&this.s&&(i=i.catch(r=>this.s.handle({url:e,event:t,err:r}))),i}e(t,e){const r=this.t.get(t.request.method)||[];for(const s of r){let r=s.match({url:e,event:t});if(r)return Array.isArray(r)&&0===r.length?r=void 0:(r.constructor===Object&&0===Object.keys(r).length||!0===r)&&(r=void 0),{route:s,params:r,handler:s.handler}}return{handler:void 0,params:void 0}}setDefaultHandler(t){this.r=s(t)}setCatchHandler(t){this.s=s(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(e){if(!this.t.has(e.method))throw new t.WorkboxError(\"unregister-route-but-not-found-with-method\",{method:e.method});const r=this.t.get(e.method).indexOf(e);if(!(r>-1))throw new t.WorkboxError(\"unregister-route-route-not-registered\");this.t.get(e.method).splice(r,1)}}class u extends n{constructor(t,{whitelist:e=[/./],blacklist:r=[]}={}){super((...t)=>this.n(...t),t),this.o=e,this.i=r}n({event:t,url:e}){if(\"navigate\"!==t.request.mode)return!1;const r=e.pathname+e.search;return!this.i.some(t=>t.test(r))&&!!this.o.some(t=>t.test(r))}}var a=Object.freeze({RegExpRoute:o,Route:n,Router:i,NavigationRoute:u});const c=new class extends i{registerRoute(e,r,s=\"GET\"){let i;if(\"string\"==typeof e){const t=new URL(e,location);i=new n(({url:e})=>e.href===t.href,r,s)}else if(e instanceof RegExp)i=new o(e,r,s);else if(\"function\"==typeof e)i=new n(e,r,s);else{if(!(e instanceof n))throw new t.WorkboxError(\"unsupported-route-type\",{moduleName:\"workbox-routing\",className:\"DefaultRouter\",funcName:\"registerRoute\",paramName:\"capture\"});i=e}return super.registerRoute(i),i}registerNavigationRoute(t,r={}){const s=e.cacheNames.getPrecacheName(r.cacheName),n=new u(()=>caches.match(t,{cacheName:s}).then(e=>{if(e)return e;throw new Error(`The cache ${s} did not have an entry for `+`${t}.`)}).catch(e=>fetch(t)),{whitelist:r.whitelist,blacklist:r.blacklist});return super.registerRoute(n),n}};return self.addEventListener(\"fetch\",t=>{const e=c.handleRequest(t);e&&t.respondWith(e)}),Object.assign(c,a)}(workbox.core._private,workbox.core._private);\n"],"file":"workbox-routing.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-strategies.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.strategies=function(e,t,r){"use strict";try{self.workbox.v["workbox:strategies:3.3.0"]=1}catch(e){}class n{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){"string"==typeof r&&(r=new Request(r));let s,i=yield t.cacheWrapper.match(n.e,r,null,n.t);if(!i)try{i=yield n.n(r,e)}catch(e){s=e}if(s)throw s;return i})()}n(e,n){var s=this;return babelHelpers.asyncToGenerator(function*(){const i=yield r.fetchWrapper.fetch(e,s.r,s.t),l=i.clone(),u=t.cacheWrapper.put(s.e,e,l,s.t);if(n)try{n.waitUntil(u)}catch(e){}return i})()}}class s{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[]}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){return"string"==typeof r&&(r=new Request(r)),yield t.cacheWrapper.match(n.e,r,null,n.t)})()}}var i={cacheWillUpdate:({response:e})=>e.ok||0===e.status?e:null};class l{constructor(t={}){if(this.e=e.cacheNames.getRuntimeName(t.cacheName),t.plugins){let e=t.plugins.some(e=>!!e.cacheWillUpdate);this.t=e?t.plugins:[i,...t.plugins]}else this.t=[i];this.s=t.networkTimeoutSeconds,this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:t}){var r=this;return babelHelpers.asyncToGenerator(function*(){const n=[];"string"==typeof t&&(t=new Request(t));const s=[];let i;if(r.s){const{id:e,promise:l}=r.i(t,n);i=e,s.push(l)}const l=r.l(i,e,t,n);s.push(l);let u=yield Promise.race(s);return u||(u=yield l),u})()}i(e,t){var r=this;let n;var s;return{promise:new Promise(t=>{const i=(s=babelHelpers.asyncToGenerator(function*(){t(yield r.u(e))}),function(){return s.apply(this,arguments)});n=setTimeout(i,1e3*this.s)}),id:n}}l(e,n,s,i){var l=this;return babelHelpers.asyncToGenerator(function*(){let i,u;try{u=yield r.fetchWrapper.fetch(s,l.r,l.t)}catch(e){i=e}if(e&&clearTimeout(e),i||!u)u=yield l.u(s);else{const e=u.clone(),r=t.cacheWrapper.put(l.e,s,e,l.t);if(n)try{n.waitUntil(r)}catch(e){}}return u})()}u(e){return t.cacheWrapper.match(this.e,e,null,this.t)}}class u{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:t}){var n=this;return babelHelpers.asyncToGenerator(function*(){let e,s;"string"==typeof t&&(t=new Request(t));try{s=yield r.fetchWrapper.fetch(t,n.r,n.t)}catch(t){e=t}if(e)throw e;return s})()}}class c{constructor(t={}){if(this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],t.plugins){let e=t.plugins.some(e=>!!e.cacheWillUpdate);this.t=e?t.plugins:[i,...t.plugins]}else this.t=[i];this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){"string"==typeof r&&(r=new Request(r));const s=n.n(r,e);let i=yield t.cacheWrapper.match(n.e,r,null,n.t);if(i){if(e)try{e.waitUntil(s)}catch(e){}}else i=yield s;return i})()}n(e,n){var s=this;return babelHelpers.asyncToGenerator(function*(){const i=yield r.fetchWrapper.fetch(e,s.r,s.t),l=t.cacheWrapper.put(s.e,e,i.clone(),s.t);if(n)try{n.waitUntil(l)}catch(e){}return i})()}}var o=Object.freeze({CacheFirst:n,CacheOnly:s,NetworkFirst:l,NetworkOnly:u,StaleWhileRevalidate:c});const a={cacheFirst:n,cacheOnly:s,networkFirst:l,networkOnly:u,staleWhileRevalidate:c},h={};return Object.keys(a).forEach(e=>{h[e]=((t={})=>{return new(0,a[e])(Object.assign(t))})}),Object.assign(h,o)}(workbox.core._private,workbox.core._private,workbox.core._private); 2 | 3 | //# sourceMappingURL=workbox-strategies.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-strategies.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-strategies/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.strategies=function(e,t,r){\"use strict\";try{self.workbox.v[\"workbox:strategies:3.3.0\"]=1}catch(e){}class n{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){\"string\"==typeof r&&(r=new Request(r));let s,i=yield t.cacheWrapper.match(n.e,r,null,n.t);if(!i)try{i=yield n.n(r,e)}catch(e){s=e}if(s)throw s;return i})()}n(e,n){var s=this;return babelHelpers.asyncToGenerator(function*(){const i=yield r.fetchWrapper.fetch(e,s.r,s.t),l=i.clone(),u=t.cacheWrapper.put(s.e,e,l,s.t);if(n)try{n.waitUntil(u)}catch(e){}return i})()}}class s{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[]}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){return\"string\"==typeof r&&(r=new Request(r)),yield t.cacheWrapper.match(n.e,r,null,n.t)})()}}var i={cacheWillUpdate:({response:e})=>e.ok||0===e.status?e:null};class l{constructor(t={}){if(this.e=e.cacheNames.getRuntimeName(t.cacheName),t.plugins){let e=t.plugins.some(e=>!!e.cacheWillUpdate);this.t=e?t.plugins:[i,...t.plugins]}else this.t=[i];this.s=t.networkTimeoutSeconds,this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:t}){var r=this;return babelHelpers.asyncToGenerator(function*(){const n=[];\"string\"==typeof t&&(t=new Request(t));const s=[];let i;if(r.s){const{id:e,promise:l}=r.i(t,n);i=e,s.push(l)}const l=r.l(i,e,t,n);s.push(l);let u=yield Promise.race(s);return u||(u=yield l),u})()}i(e,t){var r=this;let n;var s;return{promise:new Promise(t=>{const i=(s=babelHelpers.asyncToGenerator(function*(){t(yield r.u(e))}),function(){return s.apply(this,arguments)});n=setTimeout(i,1e3*this.s)}),id:n}}l(e,n,s,i){var l=this;return babelHelpers.asyncToGenerator(function*(){let i,u;try{u=yield r.fetchWrapper.fetch(s,l.r,l.t)}catch(e){i=e}if(e&&clearTimeout(e),i||!u)u=yield l.u(s);else{const e=u.clone(),r=t.cacheWrapper.put(l.e,s,e,l.t);if(n)try{n.waitUntil(r)}catch(e){}}return u})()}u(e){return t.cacheWrapper.match(this.e,e,null,this.t)}}class u{constructor(t={}){this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:t}){var n=this;return babelHelpers.asyncToGenerator(function*(){let e,s;\"string\"==typeof t&&(t=new Request(t));try{s=yield r.fetchWrapper.fetch(t,n.r,n.t)}catch(t){e=t}if(e)throw e;return s})()}}class c{constructor(t={}){if(this.e=e.cacheNames.getRuntimeName(t.cacheName),this.t=t.plugins||[],t.plugins){let e=t.plugins.some(e=>!!e.cacheWillUpdate);this.t=e?t.plugins:[i,...t.plugins]}else this.t=[i];this.r=t.fetchOptions||null}handle({event:e}){var t=this;return babelHelpers.asyncToGenerator(function*(){return t.makeRequest({event:e,request:e.request})})()}makeRequest({event:e,request:r}){var n=this;return babelHelpers.asyncToGenerator(function*(){\"string\"==typeof r&&(r=new Request(r));const s=n.n(r,e);let i=yield t.cacheWrapper.match(n.e,r,null,n.t);if(i){if(e)try{e.waitUntil(s)}catch(e){}}else i=yield s;return i})()}n(e,n){var s=this;return babelHelpers.asyncToGenerator(function*(){const i=yield r.fetchWrapper.fetch(e,s.r,s.t),l=t.cacheWrapper.put(s.e,e,i.clone(),s.t);if(n)try{n.waitUntil(l)}catch(e){}return i})()}}var o=Object.freeze({CacheFirst:n,CacheOnly:s,NetworkFirst:l,NetworkOnly:u,StaleWhileRevalidate:c});const a={cacheFirst:n,cacheOnly:s,networkFirst:l,networkOnly:u,staleWhileRevalidate:c},h={};return Object.keys(a).forEach(e=>{h[e]=((t={})=>{return new(0,a[e])(Object.assign(t))})}),Object.assign(h,o)}(workbox.core._private,workbox.core._private,workbox.core._private);\n"],"file":"workbox-strategies.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-streams.prod.js: -------------------------------------------------------------------------------- 1 | this.workbox=this.workbox||{},this.workbox.streams=function(){"use strict";try{self.workbox.v["workbox:streams:3.3.0"]=1}catch(e){}function e(e){const n=e.map(e=>Promise.resolve(e).then(e=>(e=e).body&&e.body.getReader?e.body.getReader():e.getReader?e.getReader():new Response(e).body.getReader()));var t;let r,o;let s=0;return{done:new Promise((e,n)=>{r=e,o=n}),stream:new ReadableStream({pull(e){return n[s].then(e=>e.read()).then(t=>{if(t.done)return++s>=n.length?(e.close(),void r()):this.pull(e);e.enqueue(t.value)}).catch(e=>{throw o(e),e})},cancel(){r()}})}}function n(e={}){const n=new Headers(e);return n.has("content-type")||n.set("content-type","text/html"),n}function t(t,r){const{done:o,stream:s}=e(t),c=n(r);return{done:o,response:new Response(s,{headers:c})}}let r=void 0;function o(){if(void 0===r)try{new ReadableStream({start(){}}),r=!0}catch(e){r=!1}return r}var s=Object.freeze({concatenate:e,concatenateToResponse:t,isSupported:o});var c={concatenate:e,concatenateToResponse:t,isSupported:o,strategy:function(e,r){return s=babelHelpers.asyncToGenerator(function*({event:s,url:c,params:a}){if(o()){const{done:n,response:o}=t(e.map(function(e){return e({event:s,url:c,params:a})}),r);return s.waitUntil(n),o}const u=yield Promise.all(e.map(function(e){return e({event:s,url:c,params:a})}).map((i=babelHelpers.asyncToGenerator(function*(e){const n=yield e;return n instanceof Response?n.blob():n}),function(e){return i.apply(this,arguments)})));var i;const l=n(r);return new Response(new Blob(u),{headers:l})}),function(e){return s.apply(this,arguments)};var s}};return Object.assign(c,s)}(); 2 | 3 | //# sourceMappingURL=workbox-streams.prod.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-streams.prod.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-streams/browser.mjs"],"sourcesContent":["this.workbox=this.workbox||{},this.workbox.streams=function(){\"use strict\";try{self.workbox.v[\"workbox:streams:3.3.0\"]=1}catch(e){}function e(e){const n=e.map(e=>Promise.resolve(e).then(e=>(e=e).body&&e.body.getReader?e.body.getReader():e.getReader?e.getReader():new Response(e).body.getReader()));var t;let r,o;let s=0;return{done:new Promise((e,n)=>{r=e,o=n}),stream:new ReadableStream({pull(e){return n[s].then(e=>e.read()).then(t=>{if(t.done)return++s>=n.length?(e.close(),void r()):this.pull(e);e.enqueue(t.value)}).catch(e=>{throw o(e),e})},cancel(){r()}})}}function n(e={}){const n=new Headers(e);return n.has(\"content-type\")||n.set(\"content-type\",\"text/html\"),n}function t(t,r){const{done:o,stream:s}=e(t),c=n(r);return{done:o,response:new Response(s,{headers:c})}}let r=void 0;function o(){if(void 0===r)try{new ReadableStream({start(){}}),r=!0}catch(e){r=!1}return r}var s=Object.freeze({concatenate:e,concatenateToResponse:t,isSupported:o});var c={concatenate:e,concatenateToResponse:t,isSupported:o,strategy:function(e,r){return s=babelHelpers.asyncToGenerator(function*({event:s,url:c,params:a}){if(o()){const{done:n,response:o}=t(e.map(function(e){return e({event:s,url:c,params:a})}),r);return s.waitUntil(n),o}const u=yield Promise.all(e.map(function(e){return e({event:s,url:c,params:a})}).map((i=babelHelpers.asyncToGenerator(function*(e){const n=yield e;return n instanceof Response?n.blob():n}),function(e){return i.apply(this,arguments)})));var i;const l=n(r);return new Response(new Blob(u),{headers:l})}),function(e){return s.apply(this,arguments)};var s}};return Object.assign(c,s)}();\n"],"file":"workbox-streams.prod.js"} -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-sw.js: -------------------------------------------------------------------------------- 1 | var workbox=function(){"use strict";try{self.workbox.v["workbox:sw:3.3.0"]=1}catch(t){}const t="https://storage.googleapis.com/workbox-cdn/releases/3.3.0",e={backgroundSync:"background-sync",broadcastUpdate:"broadcast-cache-update",cacheableResponse:"cacheable-response",core:"core",expiration:"cache-expiration",googleAnalytics:"google-analytics",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};return new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.e=this.t.debug?"dev":"prod",this.s=!1,new Proxy(this,{get(t,s){if(t[s])return t[s];const o=e[s];return o&&t.loadModule(`workbox-${o}`),t[s]}})}setConfig(t={}){if(this.s)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.e=this.t.debug?"dev":"prod"}skipWaiting(){self.addEventListener("install",()=>self.skipWaiting())}clientsClaim(){self.addEventListener("activate",()=>self.clients.claim())}loadModule(t){const e=this.o(t);try{importScripts(e),this.s=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}o(e){if(this.t.modulePathCb)return this.t.modulePathCb(e,this.t.debug);let s=[t];const o=`${e}.${this.e}.js`,r=this.t.modulePathPrefix;return r&&""===(s=r.split("/"))[s.length-1]&&s.splice(s.length-1,1),s.push(o),s.join("/")}}}(); 2 | 3 | //# sourceMappingURL=workbox-sw.js.map 4 | -------------------------------------------------------------------------------- /dist/workbox-v3.3.0/workbox-sw.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["packages/workbox-sw/browser.mjs"],"sourcesContent":["var workbox=function(){\"use strict\";try{self.workbox.v[\"workbox:sw:3.3.0\"]=1}catch(t){}const t=\"https://storage.googleapis.com/workbox-cdn/releases/3.3.0\",e={backgroundSync:\"background-sync\",broadcastUpdate:\"broadcast-cache-update\",cacheableResponse:\"cacheable-response\",core:\"core\",expiration:\"cache-expiration\",googleAnalytics:\"google-analytics\",precaching:\"precaching\",rangeRequests:\"range-requests\",routing:\"routing\",strategies:\"strategies\",streams:\"streams\"};return new class{constructor(){return this.v={},this.t={debug:\"localhost\"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.e=this.t.debug?\"dev\":\"prod\",this.s=!1,new Proxy(this,{get(t,s){if(t[s])return t[s];const o=e[s];return o&&t.loadModule(`workbox-${o}`),t[s]}})}setConfig(t={}){if(this.s)throw new Error(\"Config must be set before accessing workbox.* modules\");Object.assign(this.t,t),this.e=this.t.debug?\"dev\":\"prod\"}skipWaiting(){self.addEventListener(\"install\",()=>self.skipWaiting())}clientsClaim(){self.addEventListener(\"activate\",()=>self.clients.claim())}loadModule(t){const e=this.o(t);try{importScripts(e),this.s=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}o(e){if(this.t.modulePathCb)return this.t.modulePathCb(e,this.t.debug);let s=[t];const o=`${e}.${this.e}.js`,r=this.t.modulePathPrefix;return r&&\"\"===(s=r.split(\"/\"))[s.length-1]&&s.splice(s.length-1,1),s.push(o),s.join(\"/\")}}}();\n"],"file":"workbox-sw.js"} -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/favicon.ico -------------------------------------------------------------------------------- /html/detox-chat-app-dialogs/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-app-dialogs/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app-dialogs") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | #contact-details(hidden="[[!active_contact]]") 5 | csw-button.open-sidebar(icon="bars" unread$="[[unread_messages]]"): button(type="button" on-click="_show_sidebar") 6 | div.actions(hidden="[[!direct_connections]]") 7 | csw-button(icon="phone" tooltip="Start an audio/video call"): button(type="button" on-click="_not_implemented") 8 | csw-button(icon="paperclip" tooltip="Send files"): button(type="button" on-click="_not_implemented") 9 | div.nickname(hidden="[[!active_contact]]") [[contact.nickname]] 10 | #messages-list(hidden="[[!active_contact]]") 11 | //- Hack: This is needed because otherwise padding on parent element will have no effect 12 | .inner 13 | template#messages-list-template(is="dom-repeat" items="{{messages}}" mutable-data) 14 | .message(origin$="[[_message_origin(item)]]" pending$="[[!item.date_sent]]" inner-h-t-m-l="[[_markdown_renderer(item.text)]] " tooltip="Written: [[_format_date(item.date_written)]],
delivered: [[_format_date(item.date_sent)]]") 15 | csw-form#send-form(hidden="[[!active_contact]]"): form 16 | csw-textarea(autosize): textarea#textarea(value="{{text_message}}" placeholder="[[_send_placeholder(send_ctrl_enter)]]") 17 | csw-button: button(type="button" on-click="_send") Send 18 | #welcome(hidden="[[active_contact]]") 19 | h4 Welcome to Detox Chat! 20 | p WARNING: INSECURE UNTIL PROVEN THE OPPOSITE!!! 21 | p You can add contacts on the left panel using their ID. Once you've added someone, click on the name to start chatting. 22 | p Share your ID from Profile tab so that other people can find you. 23 | h4 Detox Chat application is a part of Detox project. 24 | p It is published under 0BSD license. Source code can be found at https://github.com/Detox/chat-app 25 | p Built with ❤ by Nazar Mokrynskyi <nazar@mokrynskyi.com>, consider supporting me at https://www.patreon.com/nazarpc 26 | script(src="script.js") 27 | -------------------------------------------------------------------------------- /html/detox-chat-app-dialogs/script.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | ([detox-utils, hotkeys-js, behaviors, markdown]) <-! require(['@detox/utils', 'hotkeys-js', 'js/behaviors', 'js/markdown']).then 7 | 8 | function node_to_string (node) 9 | nodes = node.childNodes 10 | if nodes.length 11 | ( 12 | for node in nodes 13 | node_to_string(node) 14 | ) 15 | .map (node) -> 16 | node.trim() 17 | .filter(Boolean) 18 | .join(' ') 19 | else 20 | node.textContent.trim() 21 | 22 | Polymer( 23 | is : 'detox-chat-app-dialogs' 24 | behaviors : [ 25 | behaviors.state_instance 26 | Polymer.MutableDataBehavior 27 | ] 28 | properties : 29 | active_contact : 30 | type : Boolean 31 | value : false 32 | contact : Object 33 | direct_connections : Boolean 34 | messages : Array 35 | send_ctrl_enter : Boolean 36 | text_message : 37 | type : String 38 | value : '' 39 | unread_messages : Boolean 40 | created : !-> 41 | @_ctrl_enter_handler = @_ctrl_enter_handler.bind(@) 42 | @_enter_handler = @_enter_handler.bind(@) 43 | ready : !-> 44 | are_arrays_equal = detox-utils.are_arrays_equal 45 | ArrayMap = detox-utils.ArrayMap 46 | 47 | text_messages = ArrayMap() 48 | state = @state 49 | @active_contact = !!state.get_ui_active_contact() 50 | @direct_connections = state.get_settings_direct_connections() != @state['DIRECT_CONNECTIONS_REJECT'] 51 | @send_ctrl_enter = state.get_settings_send_ctrl_enter() 52 | @_update_unread_messages() 53 | state 54 | .on('contact_message_added', (contact_id, message) !~> 55 | active_contact = state.get_ui_active_contact() 56 | if message.origin != state.MESSAGE_ORIGIN_RECEIVED 57 | return 58 | if state.get_settings_audio_notifications() 59 | detox_chat_app.play_sound('audio/definite.mp3') 60 | if !( 61 | document.hasFocus() && 62 | active_contact && 63 | are_arrays_equal(contact_id, active_contact) 64 | ) 65 | contact = state.get_contact(contact_id) 66 | tmp_node = document.createElement('div') 67 | ..innerHTML = message.text 68 | text = node_to_string(tmp_node) 69 | detox_chat_app.notify( 70 | contact.nickname 71 | if text.length > 60 then text.substr(0, 60) + '...' else text 72 | 7 73 | ).then !-> 74 | state.set_ui_active_contact(contact_id) 75 | ) 76 | .on('contact_messages_changed', (contact_id) !~> 77 | active_contact = state.get_ui_active_contact() 78 | if active_contact && are_arrays_equal(contact_id, active_contact) 79 | messages_list = @$['messages-list'] 80 | need_to_update_scroll = messages_list.scrollHeight - messages_list.offsetHeight == messages_list.scrollTop 81 | (@messages) <~! state.get_contact_messages(contact_id).then 82 | @notifyPath('messages') 83 | if need_to_update_scroll 84 | # Force synchronous messages render in order to be sure scrolling works properly 85 | @$['messages-list-template'].render() 86 | messages_list.scrollTop = messages_list.scrollHeight - messages_list.offsetHeight 87 | ) 88 | .on('contact_changed', (new_contact) !~> 89 | if @contact?id && are_arrays_equal(@contact.id, new_contact.id) 90 | @contact = new_contact 91 | ) 92 | .on('contact_deleted', (old_contact) !~> 93 | text_messages.delete(old_contact.id) 94 | ) 95 | .on('contacts_with_unread_messages_changed', !~> 96 | @_update_unread_messages() 97 | ) 98 | .on('ui_active_contact_changed', (new_active_contact, old_active_contact) !~> 99 | text_message = @text_message.trim() 100 | if text_message && old_active_contact 101 | text_messages.set(old_active_contact, text_message) 102 | @text_message = '' 103 | if new_active_contact && text_messages.has(new_active_contact) 104 | @text_message = text_messages.get(new_active_contact) 105 | text_messages.delete(new_active_contact) 106 | if !new_active_contact 107 | @active_contact = false 108 | @contact = {} 109 | @messages = [] 110 | return 111 | @active_contact = true 112 | @contact = state.get_contact(new_active_contact) 113 | (@messages) <~! state.get_contact_messages(new_active_contact).then 114 | @notifyPath('messages') 115 | # Force synchronous messages render in order to be sure scrolling works properly 116 | @$['messages-list-template'].render() 117 | messages_list = @$['messages-list'] 118 | messages_list.scrollTop = messages_list.scrollHeight - messages_list.offsetHeight 119 | @_update_unread_messages() 120 | ) 121 | .on('settings_direct_connections_changed', (value) !~> 122 | @direct_connections = value != @state['DIRECT_CONNECTIONS_REJECT'] 123 | ) 124 | .on('settings_send_ctrl_enter_changed', (@send_ctrl_enter) !~>) 125 | attached : !-> 126 | hotkeys-js('Ctrl+Enter', @_ctrl_enter_handler) 127 | hotkeys-js('Enter', @_enter_handler) 128 | detached : !-> 129 | hotkeys-js.unbind('Ctrl+Enter', @_ctrl_enter_handler) 130 | hotkeys-js.unbind('Enter', @_enter_handler) 131 | _ctrl_enter_handler : (e) !-> 132 | if e.composedPath()[0] == @$.textarea 133 | @_send() 134 | e.preventDefault() 135 | _enter_handler : (e) !-> 136 | if e.composedPath()[0] == @$.textarea && !@send_ctrl_enter 137 | @_send() 138 | e.preventDefault() 139 | _update_unread_messages : !-> 140 | state = @state 141 | @unread_messages = !!( 142 | state.get_contacts_with_unread_messages() 143 | .filter (contact) -> 144 | contact != state.get_ui_active_contact() 145 | .length 146 | ) 147 | _show_sidebar : !-> 148 | @state.set_ui_sidebar_shown(true) 149 | _send_placeholder : (send_ctrl_enter) -> 150 | 'Type you message here, Markdown (GFM) supported!\n' + ( 151 | if send_ctrl_enter 152 | 'Enter for new line, Ctrl+Enter for sending' 153 | else 154 | 'Shift+Enter for new line, Enter for sending' 155 | ) 156 | _send : !-> 157 | text_message = @text_message.trim() 158 | if !text_message 159 | return 160 | @text_message = '' 161 | state = @state 162 | contact_id = state.get_ui_active_contact() 163 | state.add_contact_message(contact_id, state.MESSAGE_ORIGIN_SENT, +(new Date), 0, text_message) 164 | if state.get_settings_audio_notifications() 165 | detox_chat_app.play_sound('audio/knob.mp3') 166 | _markdown_renderer : (markdown_text) -> 167 | markdown(markdown_text) 168 | _format_date : (date) -> 169 | if !date 170 | 'Not yet' 171 | # If message older than 24 hours, we'll use full date and time, otherwise time only 172 | else if date - (new Date) < 24 * 60 * 60 * 1000 173 | (new Date(date)).toLocaleTimeString() 174 | else 175 | (new Date(date)).toLocaleString() 176 | _message_origin : (message) -> 177 | switch message.origin 178 | case @state.MESSAGE_ORIGIN_SENT 179 | 'sent' 180 | case @state.MESSAGE_ORIGIN_RECEIVED 181 | 'received' 182 | case @state.MESSAGE_ORIGIN_SERVICE 183 | 'service' 184 | _help_insecure : !-> 185 | content = """ 186 |

Don't get this message wrong, Detox Chat in particular and Detox network in general are built with security and anonymity in mind from the beginning.

187 |

However, until independent security audit is conducted and proved that the application is indeed secure, you shouldn't trust it critical data.

188 | """ 189 | detox_chat_app.simple_modal(content) 190 | _not_implemented : !-> 191 | csw.functions.alert("Yeah, I'd like to use this too, but it is not yet implemented, get back in future updates") 192 | ) 193 | -------------------------------------------------------------------------------- /html/detox-chat-app-dialogs/style.css: -------------------------------------------------------------------------------- 1 | :host{background:var(--background-dialogs);color:var(--text-color-base);display:flex;flex-direction:column}#contact-details{height:var(--form-element-height);background:var(--background-sidebar);border-bottom:var(--border-base);flex-shrink:0;line-height:var(--form-element-height);z-index:1}#contact-details:not([hidden]){box-shadow:var(--dialog-edge-shadow);display:flex}#contact-details .open-sidebar{display:none}#contact-details .open-sidebar[unread] button{background-image:var(--menu-button-unread-corner)}#contact-details .actions:not([hidden]){display:flex}#contact-details .nickname{flex-grow:1;overflow:hidden;padding:0 var(--block-padding);text-overflow:ellipsis}#welcome{padding:var(--block-padding)}#welcome h4{margin-top:0}#welcome a{color:currentColor}#messages-list{flex-grow:1;overflow-x:hidden;overflow-y:auto;padding:0 var(--block-padding)}#messages-list .inner{display:flex;flex-direction:column;padding:var(--block-padding) 0}#messages-list .inner .message{background-color:var(--background-color-highlight);border:var(--border-base);border-radius:var(--block-padding) var(--block-padding) var(--block-padding) 0;box-sizing:border-box;flex-shrink:0;margin-bottom:var(--block-padding);max-width:var(--dialog-message-width-max);overflow-x:auto;padding:var(--block-padding);word-break:break-word}#messages-list .inner .message:last-of-type{margin-bottom:0}#messages-list .inner .message[origin=received]{margin-right:auto}#messages-list .inner .message[origin=sent]{border-radius:var(--block-padding) var(--block-padding) 0 var(--block-padding);margin-left:auto}#messages-list .inner .message[pending]{background-image:var(--dialog-message-pending-corner)}#messages-list .inner .message :first-child{margin-top:0}#messages-list .inner .message :last-child{margin-bottom:0}#messages-list .inner .message a{color:var(--content-link-color)}#messages-list .inner .message a:visited{color:var(--content-link-color-visited)}#messages-list .inner .message blockquote{border-left:var(--content-blockquote-border-left);border-right:var(--content-blockquote-border-right);margin:var(--content-blockquote-margin);padding:var(--content-blockquote-padding)}#messages-list .inner .message pre > code{display:block;overflow-x:auto;padding:var(--content-code-block-padding)}#messages-list .inner .message code{background-color:var(--border-color-base);border-radius:var(--content-code-border-radius);padding:var(--content-code-inline-padding)}#messages-list .inner .message hr{height:var(--content-hr-height);border:none;background-color:var(--content-hr-background-color)}#send-form{box-shadow:var(--dialog-edge-shadow);z-index:1}#send-form form{align-items:stretch;border-top:var(--border-base);display:flex}#send-form form csw-textarea{flex-grow:1}#send-form form csw-textarea textarea{border:none;height:auto;margin:0;resize:none;max-height:var(--dialog-send-textarea-max);min-height:var(--dialog-send-textarea-min)}#send-form form csw-button{display:none}#send-form form csw-button button{height:100%}@media(max-width: 768px){#contact-details{display:flex}#contact-details .open-sidebar{display:inline-block}#contact-details .actions{border-left:var(--border-base)}#messages-list{box-sizing:border-box;width:var(--app-width)}#send-form form csw-button{display:inline-block}}@media(max-width: 600px){#contact-details{flex-wrap:wrap;height:auto;max-height:calc(var(--form-element-height) * 2 + var(--border-width-base))}#contact-details .nickname{border-top:var(--border-base);width:100%}} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app-dialogs/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | background : var(--background-dialogs); 8 | color : var(--text-color-base); 9 | display : flex; 10 | flex-direction : column; 11 | } 12 | 13 | #contact-details { 14 | height : var(--form-element-height); 15 | background : var(--background-sidebar); 16 | border-bottom : var(--border-base); 17 | flex-shrink : 0; 18 | line-height : var(--form-element-height); 19 | z-index : 1; 20 | 21 | &:not([hidden]) { 22 | box-shadow : var(--dialog-edge-shadow); 23 | display : flex; 24 | } 25 | 26 | .open-sidebar { 27 | display : none; 28 | 29 | &[unread] { 30 | button { 31 | background-image : var(--menu-button-unread-corner); 32 | } 33 | } 34 | } 35 | 36 | .actions:not([hidden]) { 37 | display : flex; 38 | } 39 | 40 | .nickname { 41 | flex-grow : 1; 42 | overflow : hidden; 43 | padding : 0 var(--block-padding); 44 | text-overflow : ellipsis; 45 | } 46 | } 47 | 48 | #welcome { 49 | padding : var(--block-padding); 50 | 51 | h4 { 52 | margin-top : 0; 53 | } 54 | 55 | a { 56 | color : currentColor; 57 | } 58 | } 59 | 60 | #messages-list { 61 | flex-grow : 1; 62 | overflow-x : hidden; 63 | overflow-y : auto; 64 | padding : 0 var(--block-padding); 65 | 66 | .inner { 67 | display : flex; 68 | flex-direction : column; 69 | padding : var(--block-padding) 0; 70 | 71 | .message { 72 | background-color : var(--background-color-highlight); 73 | border : var(--border-base); 74 | border-radius : var(--block-padding) var(--block-padding) var(--block-padding) 0; 75 | box-sizing : border-box; 76 | flex-shrink : 0; 77 | margin-bottom : var(--block-padding); 78 | max-width : var(--dialog-message-width-max); 79 | overflow-x : auto; 80 | padding : var(--block-padding); 81 | word-break : break-word; 82 | 83 | &:last-of-type { 84 | // Hack: This is needed for Chromium, otherwise it throws additional margin (because it doesn't follow the latest version of the spec) 85 | margin-bottom : 0; 86 | } 87 | 88 | &[origin=received] { 89 | margin-right : auto; 90 | } 91 | 92 | &[origin=sent] { 93 | border-radius : var(--block-padding) var(--block-padding) 0 var(--block-padding); 94 | margin-left : auto; 95 | } 96 | 97 | &[pending] { 98 | background-image : var(--dialog-message-pending-corner); 99 | } 100 | 101 | :first-child { 102 | margin-top : 0; 103 | } 104 | 105 | :last-child { 106 | margin-bottom : 0; 107 | } 108 | 109 | // We mostly rely on browser's styles here, but also tweak some stuff a bit 110 | a { 111 | color : var(--content-link-color); 112 | 113 | &:visited { 114 | color : var(--content-link-color-visited); 115 | } 116 | } 117 | 118 | blockquote { 119 | border-left : var(--content-blockquote-border-left); 120 | border-right : var(--content-blockquote-border-right); 121 | margin : var(--content-blockquote-margin); 122 | padding : var(--content-blockquote-padding); 123 | } 124 | 125 | pre > code { 126 | display : block; 127 | overflow-x : auto; 128 | padding : var(--content-code-block-padding); 129 | } 130 | 131 | code { 132 | background-color : var(--border-color-base); 133 | border-radius : var(--content-code-border-radius); 134 | padding : var(--content-code-inline-padding); 135 | } 136 | 137 | hr { 138 | height : var(--content-hr-height); 139 | border : none; 140 | background-color : var(--content-hr-background-color); 141 | } 142 | } 143 | } 144 | } 145 | 146 | #send-form { 147 | box-shadow : var(--dialog-edge-shadow); 148 | z-index : 1; 149 | 150 | form { 151 | align-items : stretch; 152 | border-top : var(--border-base); 153 | display : flex; 154 | 155 | csw-textarea { 156 | flex-grow : 1; 157 | 158 | textarea { 159 | border : none; 160 | height : auto; 161 | margin : 0; 162 | resize : none; 163 | max-height : var(--dialog-send-textarea-max); 164 | min-height : var(--dialog-send-textarea-min); 165 | } 166 | } 167 | 168 | csw-button { 169 | display : none; 170 | 171 | button { 172 | height : 100%; 173 | } 174 | } 175 | } 176 | } 177 | 178 | // Would be nice to have this value in CSS property, but it is not working:( 179 | @media (max-width : 768px) { 180 | #contact-details { 181 | // On mobile show this bar even if no dialog is selected, otherwise it might be not obvious how to open side panel 182 | display : flex; 183 | 184 | .open-sidebar { 185 | display : inline-block; 186 | } 187 | 188 | .actions { 189 | border-left : var(--border-base); 190 | } 191 | } 192 | 193 | #messages-list { 194 | box-sizing : border-box; 195 | width : var(--app-width); 196 | } 197 | 198 | #send-form { 199 | form { 200 | csw-button { 201 | display : inline-block; 202 | } 203 | } 204 | } 205 | } 206 | 207 | // Would be nice to have this value in CSS property, but it is not working:( 208 | @media (max-width : 600px) { 209 | #contact-details { 210 | flex-wrap : wrap; 211 | height : auto; 212 | max-height : calc(var(--form-element-height) * 2 + var(--border-width-base)); 213 | 214 | .nickname { 215 | border-top : var(--border-base); 216 | width : 100%; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-contacts/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-contacts/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app-sidebar-contacts") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | div(hidden="[[_hide_header(contacts_requests, add_contact)]]") 5 | h4 Contacts requests 6 | csw-group#contacts-requests(vertical) 7 | template(is="dom-repeat" items="{{contacts_requests}}") 8 | .contact-item(on-click="_accept_contact_request") 9 | .nickname [[item.secret_name]]: [[item.name]] 10 | div(hidden="[[add_contact]]") 11 | h3 12 | div Contacts 13 | detox-chat-icon-button(icon="plus" tooltip="Add new contact"): button(type="button" on-click="_add_contact") 14 | detox-chat-icon-button(icon="question" tooltip="About contacts" hidden="[[!help]]"): button(type="button" on-click="_help") 15 | csw-form.add-contact(hidden="[[!add_contact]]"): form 16 | label Contact ID: 17 | csw-textarea: textarea(rows="4" value="{{new_contact_id}}") 18 | label Contact nickname (optional): 19 | csw-textarea: textarea(rows="3" value="{{new_contact_name}}") 20 | csw-button(primary): button(type="button" on-click="_add_contact_confirm") Save contact 21 | csw-button: button(type="button" on-click="_add_contact_cancel") Cancel 22 | div(hidden="[[!contacts.length]]") 23 | csw-group(vertical) 24 | template(is="dom-repeat" items="{{contacts}}") 25 | .contact-item( 26 | on-click="_set_active_contact" 27 | selected$="[[_selected(item, ui_active_contact)]]" 28 | unconfirmed$="[[_unconfirmed(item)]]" 29 | online$="[[_online(item, online_contacts)]]" 30 | unread$="[[_unread(item, contacts_with_unread_messages)]]" 31 | pending$="[[_pending(item, contacts_with_pending_messages)]]" 32 | ) 33 | .nickname [[item.nickname]] 34 | detox-chat-icon-button(icon="edit" tooltip="Rename contact"): button(type="button" on-click="_rename_contact") 35 | detox-chat-icon-button(icon="trash" tooltip="Delete contact"): button(type="button" on-click="_del_contact") 36 | script(src="script.js") 37 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-contacts/script.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | ([detox-chat, detox-crypto, detox-utils, behaviors]) <-! require(['@detox/chat', '@detox/crypto', '@detox/utils', 'js/behaviors']).then 7 | 8 | Polymer( 9 | is : 'detox-chat-app-sidebar-contacts' 10 | behaviors : [ 11 | behaviors.experience_level 12 | behaviors.help 13 | behaviors.state_instance 14 | ] 15 | properties : 16 | add_contact : 17 | type : Boolean 18 | value : false 19 | contacts : 20 | type : Array 21 | value : [] 22 | contacts_requests : 23 | type : Array 24 | value : [] 25 | contacts_with_pending_messages : Object 26 | contacts_with_unread_messages : Object 27 | new_contact_id : 28 | type : String 29 | value : '' 30 | new_contact_name : 31 | type : String 32 | value : '' 33 | online_contacts : 34 | type : Object 35 | ui_active_contact : 36 | type : Object 37 | ready : !-> 38 | ArraySet = detox-utils.ArraySet 39 | 40 | state = @state 41 | @contacts = state.get_contacts() 42 | @online_contacts = ArraySet(state.get_online_contacts()) 43 | @contacts_requests = state.get_contacts_requests() 44 | @contacts_with_pending_messages = ArraySet(state.get_contacts_with_pending_messages()) 45 | @contacts_with_unread_messages = ArraySet(state.get_contacts_with_unread_messages()) 46 | @ui_active_contact = ArraySet([state.get_ui_active_contact() || new Uint8Array(0)]) 47 | state 48 | .on('contacts_changed', !~> 49 | # TODO: Sort contacts 50 | @contacts = state.get_contacts() 51 | ) 52 | .on('online_contacts_changed', !~> 53 | @online_contacts = ArraySet(state.get_online_contacts()) 54 | ) 55 | .on('contact_request_added', !~> 56 | detox_chat_app.notify_warning('Incoming contact request received', 7) 57 | ) 58 | .on('contacts_requests_changed', !~> 59 | # TODO: Sort contacts 60 | contacts_requests = state.get_contacts_requests() 61 | @contacts_requests = contacts_requests 62 | ) 63 | .on('contacts_with_pending_messages_changed', !~> 64 | @contacts_with_pending_messages = ArraySet(state.get_contacts_with_pending_messages()) 65 | ) 66 | .on('contacts_with_unread_messages_changed', !~> 67 | @contacts_with_unread_messages = ArraySet(state.get_contacts_with_unread_messages()) 68 | ) 69 | .on('ui_active_contact_changed', !~> 70 | @ui_active_contact = ArraySet([state.get_ui_active_contact() || new Uint8Array(0)]) 71 | ) 72 | 73 | protocol = 'web+detoxchat' 74 | # Not present on non-secure origins 75 | if navigator.registerProtocolHandler 76 | # Register protocol handler so that we can add contacts easier 77 | current_location = location.href.split('?')[0] 78 | navigator.registerProtocolHandler(protocol, "#current_location?contact=%s", 'Detox Chat') 79 | if location.search 80 | url = new URL(location.href) 81 | contact = url.searchParams.get('contact') 82 | if contact && contact.startsWith("#protocol:") 83 | contact_splitted = contact.substr(protocol.length + 1).split('+') 84 | @add_contact = true 85 | @new_contact_id = contact_splitted[0] 86 | @new_contact_name = decodeURIComponent(contact_splitted.slice(1).join(' ')) 87 | detox_chat_app.notify_success('Contact addition form is filled, confirm if you want to proceed', 5) 88 | 89 | _hide_header : (list, add_contact) -> 90 | !list.length || add_contact 91 | _add_contact : !-> 92 | @add_contact = true 93 | _add_contact_confirm : !-> 94 | <~! detox-chat.ready 95 | 96 | try 97 | [public_key, remote_secret] = detox-chat.id_decode(@new_contact_id) 98 | own_public_key = detox-crypto.create_keypair(@state.get_seed()).ed25519.public 99 | if detox-utils.are_arrays_equal(public_key, own_public_key) 100 | detox_chat_app.notify_error('Adding yourself to contacts is not supported', 3) 101 | return 102 | existing_contact = @state.get_contact(public_key) 103 | if existing_contact 104 | detox_chat_app.notify_warning("Not added: this contact is already in contacts list under nickname \"#{existing_contact.nickname}\"", 3) 105 | return 106 | @state.add_contact(public_key, @new_contact_name || @new_contact_id, remote_secret) 107 | detox_chat_app.notify_success('Contact added', 'You can already send messages and they will be delivered when/if contact request is accepted', 5) 108 | @add_contact = false 109 | @new_contact_id = '' 110 | @new_contact_name = '' 111 | catch 112 | detox_chat_app.notify_error('Incorrect ID, check for typos and try again', 3) 113 | _add_contact_cancel : !-> 114 | @add_contact = false 115 | _help : !-> 116 | if @advanced_user 117 | content = """ 118 |

You need to add contact using their ID in order to communicate.

119 |

There are 2 kinds of IDs: without secrets and with secrets. Look at Profile tab for more details on those.

120 |

Each contact in the list might have some of the corners highlighted, which indicates some information about its state.

121 |

Top left corner is highlighted when there is an active connection to contact right now.
122 | Bottom left corner is highlighted means that there was never an active connection, for instance you've added someone to contacts, but they didn't accept request (yet).
123 | Top right corner is highlighted when there are unread messages from that contact.
124 | Bottom right corner is highlighted when your last message to contact was not yet received (just received, there is no indication if it was read by contact).

125 | """ 126 | else 127 | content = """ 128 |

You need to add contact using their ID in order to communicate.

129 |

Your ID can be found in Profile tab.

130 |

Each contact in the list might have some of the corners highlighted, which indicates some information about its state.

131 |

Top left corner is highlighted when there is an active connection to contact right now.
132 | Bottom left corner is highlighted means that there was never an active connection, for instance you've added someone to contacts, but they didn't accept request (yet).
133 | Top right corner is highlighted when there are unread messages from that contact.
134 | Bottom right corner is highlighted when your last message to contact was not yet received (just received, there is no indication if it was read by contact).

135 | """ 136 | detox_chat_app.simple_modal(content) 137 | _set_active_contact : (e) !-> 138 | @state 139 | ..set_ui_active_contact(e.model.item.id) 140 | ..set_ui_sidebar_shown(false) 141 | _rename_contact : (e) !-> 142 | modal = csw.functions.prompt("New nickname:", (new_nickname) !~> 143 | @state.set_contact_nickname(e.model.item.id, new_nickname) 144 | detox_chat_app.notify_success('Nickname updated', 3) 145 | ) 146 | modal.input.value = e.model.item.nickname 147 | e.stopPropagation() 148 | _del_contact : (e) !-> 149 | csw.functions.confirm("

Are you sure you want to delete contact #{e.model.item.nickname}?

", !~> 150 | @state.del_contact(e.model.item.id) 151 | ) 152 | e.stopPropagation() 153 | _accept_contact_request : (e) !-> 154 | state = @state 155 | 156 | item = e.model.item 157 | if @advanced_user 158 | content = """ 159 |

What do you want to do with contact request?

160 |

ID: #{item.name}

161 |

Secret used: #{item.secret_name}

162 | 163 | 164 | 165 | 166 | 167 | """ 168 | else 169 | content = """ 170 |

What do you want to do with contact request?

171 |

ID: #{item.name}

172 | 173 | 174 | 175 | 176 | 177 | """ 178 | modal = csw.functions.simple_modal(content) 179 | modal.querySelector('#accept').addEventListener('click', !-> 180 | state.add_contact(item.id, item.name, new Uint8Array(0)) 181 | state.del_contact_request(item.id) 182 | modal.close() 183 | detox_chat_app.notify_success('Contact added', 3) 184 | ) 185 | modal.querySelector('#reject').addEventListener('click', !-> 186 | state.del_contact_request(item.id) 187 | modal.close() 188 | detox_chat_app.notify_warning('Contact request rejected', 3) 189 | ) 190 | modal.querySelector('#cancel').addEventListener('click', !-> 191 | modal.close() 192 | ) 193 | _online : (contact, online_contacts) -> 194 | online_contacts.has(contact.id) 195 | _selected : (contact, ui_active_contact) -> 196 | ui_active_contact.has(contact.id) 197 | _unconfirmed : (contact) -> 198 | !contact.local_secret 199 | _unread : (contact, contacts_with_unread_messages) -> 200 | contacts_with_unread_messages.has(contact.id) 201 | _pending : (contact, contacts_with_pending_messages) -> 202 | contacts_with_pending_messages.has(contact.id) 203 | ) 204 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-contacts/style.css: -------------------------------------------------------------------------------- 1 | :host{display:block}h3{display:flex;justify-content:space-between;align-items:center}h3:first-of-type{margin-top:0}h3 div{flex-grow:1}h3 detox-chat-icon-button{color:var(--text-color-base-disabled);margin-left:calc(var(--block-padding) / 2)}h3 detox-chat-icon-button:hover{color:var(--button-hover-color)}#contacts-requests{margin-bottom:var(--block-padding)}.add-contact{margin-bottom:var(--block-padding)}.add-contact csw-textarea{display:block}.add-contact csw-textarea textarea{height:auto;resize:none}csw-group{display:block}csw-group .contact-item{-webkit-tap-highlight-color:transparent;align-items:center;background-color:var(--background-color-interactive);cursor:pointer;display:flex;padding:var(--block-padding);width:calc(100% - var(--block-padding))}csw-group .contact-item:nth-of-type(n+2){border-top:var(--border-base)}csw-group .contact-item:hover{color:var(--button-hover-color);background-color:var(--background-color-highlight)}csw-group .contact-item:hover detox-chat-icon-button{display:inline-block}csw-group .contact-item[selected]{background-color:var(--border-color-base)}csw-group .contact-item[unconfirmed]{background-image:var(--sidebar-contact-unconfirmed-corner)}csw-group .contact-item[unconfirmed][pending]{background-image:var(--sidebar-contact-unconfirmed-corner),var(--sidebar-contact-pending-corner)}csw-group .contact-item[online]{background-image:var(--sidebar-contact-online-corner)}csw-group .contact-item[online][unread]{background-image:var(--sidebar-contact-online-corner),var(--sidebar-contact-unread-corner)}csw-group .contact-item[online][pending]{background-image:var(--sidebar-contact-online-corner),var(--sidebar-contact-pending-corner)}csw-group .contact-item[online][unread][pending]{background-image:var(--sidebar-contact-online-corner),var(--sidebar-contact-unread-corner),var(--sidebar-contact-pending-corner)}csw-group .contact-item[unread]{background-image:var(--sidebar-contact-unread-corner)}csw-group .contact-item[unread][pending]{background-image:var(--sidebar-contact-unread-corner),var(--sidebar-contact-pending-corner)}csw-group .contact-item[pending]{background-image:var(--sidebar-contact-pending-corner)}csw-group .contact-item .nickname{flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}csw-group .contact-item detox-chat-icon-button{color:var(--text-color-base-disabled);display:none;margin-left:calc(var(--block-padding) / 2)}csw-group .contact-item detox-chat-icon-button:hover{color:var(--button-hover-color)}@media(max-width: 768px){csw-group .contact-item detox-chat-icon-button{display:inline-block}} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-contacts/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | display : block; 8 | } 9 | 10 | h3 { 11 | display : flex; 12 | justify-content : space-between; 13 | align-items : center; 14 | 15 | &:first-of-type { 16 | margin-top : 0; 17 | } 18 | 19 | div { 20 | flex-grow : 1; 21 | } 22 | 23 | detox-chat-icon-button { 24 | color : var(--text-color-base-disabled); 25 | margin-left : calc(var(--block-padding) / 2); 26 | 27 | &:hover { 28 | color : var(--button-hover-color); 29 | } 30 | } 31 | } 32 | 33 | #contacts-requests { 34 | margin-bottom : var(--block-padding); 35 | } 36 | 37 | .add-contact { 38 | margin-bottom : var(--block-padding); 39 | 40 | csw-textarea { 41 | display : block; 42 | 43 | textarea { 44 | height : auto; 45 | resize : none; 46 | } 47 | } 48 | } 49 | 50 | csw-group { 51 | display : block; 52 | 53 | .contact-item { 54 | -webkit-tap-highlight-color : transparent; 55 | align-items : center; 56 | background-color : var(--background-color-interactive); 57 | cursor : pointer; 58 | display : flex; 59 | padding : var(--block-padding); 60 | width : calc(100% - var(--block-padding)); 61 | 62 | &:nth-of-type(n+2) { 63 | border-top : var(--border-base); 64 | } 65 | 66 | &:hover { 67 | color : var(--button-hover-color); 68 | background-color : var(--background-color-highlight); 69 | 70 | detox-chat-icon-button { 71 | display : inline-block; 72 | } 73 | } 74 | 75 | &[selected] { 76 | background-color : var(--border-color-base); 77 | } 78 | 79 | &[unconfirmed] { 80 | background-image : var(--sidebar-contact-unconfirmed-corner); 81 | } 82 | 83 | &[unconfirmed][pending] { 84 | background-image : var(--sidebar-contact-unconfirmed-corner), var(--sidebar-contact-pending-corner); 85 | } 86 | 87 | &[online] { 88 | background-image : var(--sidebar-contact-online-corner); 89 | } 90 | 91 | &[online][unread] { 92 | background-image : var(--sidebar-contact-online-corner), var(--sidebar-contact-unread-corner); 93 | } 94 | 95 | &[online][pending] { 96 | background-image : var(--sidebar-contact-online-corner), var(--sidebar-contact-pending-corner); 97 | } 98 | 99 | &[online][unread][pending] { 100 | background-image : var(--sidebar-contact-online-corner), var(--sidebar-contact-unread-corner), var(--sidebar-contact-pending-corner); 101 | } 102 | 103 | &[unread] { 104 | background-image : var(--sidebar-contact-unread-corner); 105 | } 106 | 107 | &[unread][pending] { 108 | background-image : var(--sidebar-contact-unread-corner), var(--sidebar-contact-pending-corner); 109 | } 110 | 111 | &[pending] { 112 | background-image : var(--sidebar-contact-pending-corner); 113 | } 114 | 115 | .nickname { 116 | flex-grow : 1; 117 | overflow : hidden; 118 | text-overflow : ellipsis; 119 | white-space : nowrap; 120 | } 121 | 122 | detox-chat-icon-button { 123 | color : var(--text-color-base-disabled); 124 | display : none; 125 | margin-left : calc(var(--block-padding) / 2); 126 | 127 | &:hover { 128 | color : var(--button-hover-color); 129 | } 130 | } 131 | } 132 | } 133 | 134 | // Would be nice to have this value in CSS property, but it is not working:( 135 | @media (max-width : 768px) { 136 | csw-group { 137 | .contact-item { 138 | detox-chat-icon-button { 139 | display : inline-block; 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app-sidebar-profile") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | csw-form: form 5 | label(hidden="[[!advanced_user]]") Plain ID (no secret): 6 | csw-textarea(hidden="[[!advanced_user]]"): textarea(readonly on-click="_id_click" rows="3" value="[[id_base58]]") 7 | label(hidden="[[advanced_user]]") 8 | div Your ID: 9 | detox-chat-icon-button(icon="question" tooltip="What is ID?" hidden="[[!help]]"): button(type="button" on-click="_help_id") 10 | csw-textarea(hidden="[[advanced_user]]"): textarea(readonly on-click="_id_click" rows="3" value="[[id_default_secret]]") 11 | label Nickname: 12 | csw-textarea: textarea(rows="3" value="{{nickname}}" on-blur="_nickname_blur") 13 | div(hidden="[[add_secret]]") 14 | h3(hidden="[[!advanced_user]]") 15 | div Secrets 16 | detox-chat-icon-button(icon="plus" tooltip="Add secret"): button(type="button" on-click="_add_secret") 17 | detox-chat-icon-button(icon="question" tooltip="What are secrets?" hidden="[[!help]]"): button(type="button" on-click="_help_secrets") 18 | csw-form.add-secret(hidden="[[!add_secret]]"): form 19 | label Secret name: 20 | csw-input-text: input(type="text" value="{{new_secret_name}}") 21 | label Secret length (1..32): 22 | csw-input-text: input(type="number" min="1" max="32" value="{{new_secret_length}}") 23 | csw-button(primary): button(type="button" on-click="_add_secret_confirm") Save secret 24 | csw-button: button(type="button" on-click="_add_secret_cancel") Cancel 25 | csw-form(hidden="[[!advanced_user]]"): form 26 | template(is="dom-repeat" items="{{secrets}}") 27 | label 28 | div ID for secret [[item.name]]: 29 | detox-chat-icon-button(icon="edit" tooltip="Rename secret"): button(type="button" on-click="_rename_secret") 30 | detox-chat-icon-button(icon="trash" tooltip="Delete secret"): button(type="button" on-click="_del_secret") 31 | csw-textarea: textarea(readonly on-click="_id_click" rows="4" value="[[item.id]]") 32 | script(src="script.js") 33 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/script.js: -------------------------------------------------------------------------------- 1 | // Generated by LiveScript 1.5.0 2 | /** 3 | * @package Detox chat app 4 | * @author Nazar Mokrynskyi 5 | * @license 0BSD 6 | */ 7 | (function(){ 8 | require(['@detox/chat', '@detox/crypto', 'js/behaviors']).then(function(arg$){ 9 | var detoxChat, detoxCrypto, behaviors; 10 | detoxChat = arg$[0], detoxCrypto = arg$[1], behaviors = arg$[2]; 11 | Polymer({ 12 | is: 'detox-chat-app-sidebar-profile', 13 | behaviors: [behaviors.experience_level, behaviors.help, behaviors.state_instance], 14 | properties: { 15 | add_secret: { 16 | type: Boolean, 17 | value: false 18 | }, 19 | id_base58: String, 20 | id_default_secret: String, 21 | new_secret_name: String, 22 | new_secret_length: { 23 | type: Number, 24 | value: 4 25 | }, 26 | nickname: String 27 | }, 28 | ready: function(){ 29 | var this$ = this; 30 | detoxChat.ready(function(){ 31 | var id_encode, state, public_key; 32 | id_encode = detoxChat.id_encode; 33 | state = this$.state; 34 | function update_secrets(){ 35 | var secrets, res$, i$, len$, secret; 36 | secrets = state.get_secrets(); 37 | this$.id_default_secret = id_encode(public_key, secrets[0].secret); 38 | res$ = []; 39 | for (i$ = 0, len$ = secrets.length; i$ < len$; ++i$) { 40 | secret = secrets[i$]; 41 | res$.push({ 42 | secret: secret.secret, 43 | id: id_encode(public_key, secret.secret), 44 | name: secret.name 45 | }); 46 | } 47 | this$.secrets = res$; 48 | } 49 | public_key = detoxCrypto.create_keypair(state.get_seed()).ed25519['public']; 50 | this$.id_base58 = id_encode(public_key, new Uint8Array(0)); 51 | this$.nickname = state.get_nickname(); 52 | update_secrets(); 53 | state.on('nickname_changed', function(new_nickname){ 54 | if (this$.nickname !== new_nickname) { 55 | this$.nickname = new_nickname; 56 | } 57 | }).on('secrets_changed', update_secrets); 58 | }); 59 | }, 60 | _nickname_blur: function(){ 61 | if (this.nickname !== this.state.get_nickname()) { 62 | this.state.set_nickname(this.nickname); 63 | detox_chat_app.notify_success('Nickname updated', 3); 64 | } 65 | }, 66 | _id_click: function(e){ 67 | e.target.select(); 68 | document.execCommand('copy'); 69 | detox_chat_app.notify_success('ID copied to clipboard', 3); 70 | }, 71 | _add_secret: function(){ 72 | this.add_secret = true; 73 | }, 74 | _add_secret_confirm: function(){ 75 | var new_secret_name, secret; 76 | new_secret_name = this.new_secret_name.trim(); 77 | if (!new_secret_name) { 78 | detox_chat_app.notify_error('Secret name is required', 3); 79 | return; 80 | } 81 | secret = detoxChat.generate_secret().slice(0, this.new_secret_length); 82 | this.state.add_secret(secret, new_secret_name); 83 | detox_chat_app.notify_success('Secret added', 3); 84 | this.add_secret = false; 85 | this.new_secret_name = ''; 86 | this.new_secret_length = 4; 87 | }, 88 | _add_secret_cancel: function(){ 89 | this.add_secret = false; 90 | }, 91 | _help_id: function(){ 92 | var content; 93 | content = "

ID is an identifier that represents you in the network.
\nIf you share this ID with someone, they'll be able to send contact request to you.

"; 94 | detox_chat_app.simple_modal(content); 95 | }, 96 | _help_secrets: function(){ 97 | var content; 98 | content = "

Secrets are used as anti-spam system. You can create different secrets for different purposes.
\nEach time you have incoming contact request, you'll see which secret was used.
\nFor instance, you can create a secret for conference and know who is connecting to you before you accept contact request.

\n\n

For contact requests you need to share ID with secret.
\nPlain ID without secret will not result in visible contact request, but if you and your interlocutor add each other to contacts list explicitly, you'll be connected and able to communicate.

"; 99 | detox_chat_app.simple_modal(content); 100 | }, 101 | _rename_secret: function(e){ 102 | var modal, this$ = this; 103 | modal = csw.functions.prompt("New secret name:", function(new_secret_name){ 104 | new_secret_name = new_secret_name.trim(); 105 | if (!new_secret_name) { 106 | detox_chat_app.notify_error('Secret name is required', 3); 107 | return; 108 | } 109 | this$.state.set_secret_name(e.model.item.secret, new_secret_name); 110 | detox_chat_app.notify_success('Secret name updated', 3); 111 | }); 112 | modal.input.value = e.model.item.name; 113 | e.preventDefault(); 114 | }, 115 | _del_secret: function(e){ 116 | var this$ = this; 117 | csw.functions.confirm("

Are you sure you want to delete secret " + e.model.item.name + "?

", function(){ 118 | this$.state.del_secret(e.model.item.secret); 119 | }); 120 | e.preventDefault(); 121 | } 122 | }); 123 | }); 124 | }).call(this); 125 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/script.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | ([detox-chat, detox-crypto, behaviors]) <-! require(['@detox/chat', '@detox/crypto', 'js/behaviors']).then 7 | 8 | Polymer( 9 | is : 'detox-chat-app-sidebar-profile' 10 | behaviors : [ 11 | behaviors.experience_level 12 | behaviors.help 13 | behaviors.state_instance 14 | ] 15 | properties : 16 | add_secret : 17 | type : Boolean 18 | value : false 19 | id_base58 : String 20 | id_default_secret : String 21 | new_secret_name : String 22 | new_secret_length : 23 | type : Number 24 | value : 4 25 | nickname : String 26 | ready : !-> 27 | <~! detox-chat.ready 28 | 29 | id_encode = detox-chat.id_encode 30 | 31 | state = @state 32 | 33 | !~function update_secrets 34 | secrets = state.get_secrets() 35 | @id_default_secret = id_encode(public_key, secrets[0].secret) 36 | @secrets = for secret in secrets 37 | { 38 | secret : secret.secret 39 | id : id_encode(public_key, secret.secret) 40 | name : secret.name 41 | } 42 | 43 | public_key = detox-crypto.create_keypair(state.get_seed()).ed25519.public 44 | @id_base58 = id_encode(public_key, new Uint8Array(0)) 45 | @nickname = state.get_nickname() 46 | update_secrets() 47 | 48 | state 49 | .on('nickname_changed', (new_nickname) !~> 50 | if @nickname != new_nickname 51 | @nickname = new_nickname 52 | ) 53 | .on('secrets_changed', update_secrets) 54 | _nickname_blur : !-> 55 | if @nickname != @state.get_nickname() 56 | @state.set_nickname(@nickname) 57 | detox_chat_app.notify_success('Nickname updated', 3) 58 | _id_click : (e) !-> 59 | e.target.select() 60 | document.execCommand('copy') 61 | detox_chat_app.notify_success('ID copied to clipboard', 3) 62 | _add_secret : !-> 63 | @add_secret = true 64 | _add_secret_confirm : !-> 65 | new_secret_name = @new_secret_name.trim() 66 | if !new_secret_name 67 | detox_chat_app.notify_error('Secret name is required', 3) 68 | return 69 | secret = detox-chat.generate_secret().slice(0, @new_secret_length) 70 | @state.add_secret(secret, new_secret_name) 71 | detox_chat_app.notify_success('Secret added', 3) 72 | @add_secret = false 73 | @new_secret_name = '' 74 | @new_secret_length = 4 75 | _add_secret_cancel : !-> 76 | @add_secret = false 77 | _help_id : !-> 78 | content = """ 79 |

ID is an identifier that represents you in the network.
80 | If you share this ID with someone, they'll be able to send contact request to you.

81 | """ 82 | detox_chat_app.simple_modal(content) 83 | _help_secrets : !-> 84 | content = """ 85 |

Secrets are used as anti-spam system. You can create different secrets for different purposes.
86 | Each time you have incoming contact request, you'll see which secret was used.
87 | For instance, you can create a secret for conference and know who is connecting to you before you accept contact request.

88 | 89 |

For contact requests you need to share ID with secret.
90 | Plain ID without secret will not result in visible contact request, but if you and your interlocutor add each other to contacts list explicitly, you'll be connected and able to communicate.

91 | """ 92 | detox_chat_app.simple_modal(content) 93 | _rename_secret : (e) !-> 94 | modal = csw.functions.prompt("New secret name:", (new_secret_name) !~> 95 | new_secret_name = new_secret_name.trim() 96 | if !new_secret_name 97 | detox_chat_app.notify_error('Secret name is required', 3) 98 | return 99 | @state.set_secret_name(e.model.item.secret, new_secret_name) 100 | detox_chat_app.notify_success('Secret name updated', 3) 101 | ) 102 | modal.input.value = e.model.item.name 103 | e.preventDefault() 104 | _del_secret : (e) !-> 105 | csw.functions.confirm("

Are you sure you want to delete secret #{e.model.item.name}?

", !~> 106 | @state.del_secret(e.model.item.secret) 107 | ) 108 | e.preventDefault() 109 | ) 110 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/style.css: -------------------------------------------------------------------------------- 1 | :host{display:block}csw-textarea:not([hidden]){display:block}csw-textarea textarea{height:auto;resize:none}csw-textarea textarea[readonly]{cursor:copy}csw-input-text{display:block}csw-button{margin-bottom:var(--block-padding)}label:not([hidden]),h3:not([hidden]){display:flex !important}label div,h3 div{flex-grow:1}label detox-chat-icon-button,h3 detox-chat-icon-button{color:var(--text-color-base-disabled);margin-left:calc(var(--block-padding) / 2)}label detox-chat-icon-button:hover,h3 detox-chat-icon-button:hover{color:var(--button-hover-color)} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-profile/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | display : block; 8 | } 9 | 10 | csw-textarea { 11 | &:not([hidden]) { 12 | display : block; 13 | } 14 | 15 | textarea { 16 | height : auto; 17 | resize : none; 18 | 19 | &[readonly] { 20 | cursor : copy; 21 | } 22 | } 23 | } 24 | 25 | csw-input-text { 26 | display : block; 27 | } 28 | 29 | csw-button { 30 | margin-bottom : var(--block-padding); 31 | } 32 | 33 | label, h3 { 34 | &:not([hidden]) { 35 | display : flex !important; 36 | } 37 | 38 | div { 39 | flex-grow : 1; 40 | } 41 | 42 | detox-chat-icon-button { 43 | color : var(--text-color-base-disabled); 44 | margin-left : calc(var(--block-padding) / 2); 45 | 46 | &:hover { 47 | color : var(--button-hover-color); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-settings/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app-sidebar-settings") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | csw-form: form 5 | label 6 | div Desired anonymity*: 7 | detox-chat-icon-button(icon="question" tooltip="What is desired anonymity?" hidden="[[!help]]"): button(type="button" on-click="_help_desired_anonymity") 8 | csw-group 9 | csw-label-button(value="{{desired_anonymity}}"): label 10 | input(type="radio" value="0") 11 | | Strict 12 | csw-label-button: label 13 | input(type="radio" value="1") 14 | | High 15 | csw-label-button: label 16 | input(type="radio" value="2") 17 | | Low 18 | csw-label-button(hidden="[[!desired_anonymity_custom]]"): label 19 | input(type="radio" value="3") 20 | | Custom 21 | label 22 | div User experience level: 23 | detox-chat-icon-button(icon="question" tooltip="What these levels mean?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_experience") 24 | csw-group 25 | csw-label-button(value="{{settings_experience}}"): label 26 | input(type="radio" value="[[state.EXPERIENCE_REGULAR]]") 27 | | Regular 28 | csw-label-button: label 29 | input(type="radio" value="[[state.EXPERIENCE_ADVANCED]]") 30 | | Advanced 31 | csw-label-button: label 32 | input(type="radio" value="[[state.EXPERIENCE_DEVELOPER]]") 33 | | Developer 34 | label 35 | div Send message with: 36 | detox-chat-icon-button(icon="question" tooltip="What is this?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_send_ctrl_enter") 37 | csw-group 38 | csw-label-button(value="{{settings_send_ctrl_enter}}"): label 39 | input(type="radio" value="0") 40 | | Enter 41 | csw-label-button: label 42 | input(type="radio" value="1") 43 | | Ctrl+Enter 44 | label 45 | div Audio notifications: 46 | detox-chat-icon-button(icon="question" tooltip="What notifications?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_audio_notifications") 47 | csw-group 48 | csw-label-button(value="{{settings_audio_notifications}}"): label 49 | input(type="radio" value="0") 50 | | No 51 | csw-label-button: label 52 | input(type="radio" value="1") 53 | | Yes 54 | label 55 | div Help buttons: 56 | detox-chat-icon-button(icon="question" tooltip="Help buttons?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_help") 57 | csw-group 58 | csw-label-button(value="{{settings_help}}"): label 59 | input(type="radio" value="0") 60 | | No 61 | csw-label-button: label 62 | input(type="radio" value="1") 63 | | Yes 64 | label(hidden="[[!advanced_user]]") 65 | div Announce to the network*: 66 | detox-chat-icon-button(icon="question" tooltip="What is announcement?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_announce") 67 | csw-group(hidden="[[!advanced_user]]") 68 | csw-label-button(value="{{settings_announce}}"): label 69 | input(type="radio" value="0") 70 | | No 71 | csw-label-button: label 72 | input(type="radio" value="1") 73 | | Yes 74 | label(hidden="[[!advanced_user]]") 75 | div Block contact requests X days: 76 | detox-chat-icon-button(icon="question" tooltip="What is blocking contacts requests?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_block_contact_requests_for") 77 | csw-input-text(hidden="[[!advanced_user]]"): input(type="number" min="0" value="{{settings_block_contact_requests_for}}") 78 | label(hidden="[[!advanced_user]]") 79 | div Bootstrap nodes*: 80 | detox-chat-icon-button(icon="question" tooltip="What are bootstrap nodes?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_bootstrap_nodes") 81 | csw-textarea(hidden="[[!advanced_user]]"): textarea(rows="10" wrap="off" value="{{settings_bootstrap_nodes_string}}" on-blur="_settings_bootstrap_nodes_blur") 82 | label(hidden="[[!developer]]") 83 | div DHT bucket size*: 84 | detox-chat-icon-button(icon="question" tooltip="What is DHT bucket size?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_bucket_size") 85 | csw-input-text(hidden="[[!developer]]"): input(type="number" min="2" value="{{settings_bucket_size}}") 86 | label(hidden="[[!developer]]") 87 | div Direct connections: 88 | detox-chat-icon-button(icon="question" tooltip="What are direct connections?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_direct_connections") 89 | csw-group(hidden="[[!developer]]") 90 | csw-label-button(value="{{settings_direct_connections}}"): label 91 | input(type="radio" value="[[state.DIRECT_CONNECTIONS_REJECT]]") 92 | | Reject 93 | csw-label-button: label 94 | input(type="radio" value="[[state.DIRECT_CONNECTIONS_PROMPT]]") 95 | | Prompt 96 | csw-label-button: label 97 | input(type="radio" value="[[state.DIRECT_CONNECTIONS_ACCEPT]]") 98 | | Accept 99 | label(hidden="[[!advanced_user]]") 100 | div ICE servers*: 101 | detox-chat-icon-button(icon="question" tooltip="What are ICE servers?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_ice_servers") 102 | csw-textarea(hidden="[[!advanced_user]]"): textarea(rows="10" wrap="off" value="{{settings_ice_servers_string}}" on-blur="_settings_ice_servers_blur") 103 | label(hidden="[[!developer]]") 104 | div Max pending segments*: 105 | detox-chat-icon-button(icon="question" tooltip="What are pending segments?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_max_pending_segments") 106 | csw-input-text(hidden="[[!developer]]"): input(type="number" min="1" value="{{settings_max_pending_segments}}") 107 | label(hidden="[[!developer]]") 108 | div Number of intermediate nodes*: 109 | detox-chat-icon-button(icon="question" tooltip="What are intermediate nodes?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_number_of_intermediate_nodes") 110 | csw-input-text(hidden="[[!developer]]"): input(type="number" min="0" value="{{settings_number_of_intermediate_nodes}}") 111 | label(hidden="[[!developer]]") 112 | div Number of introduction nodes*: 113 | detox-chat-icon-button(icon="question" tooltip="What are introduction nodes?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_number_of_introduction_nodes") 114 | csw-input-text(hidden="[[!developer]]"): input(type="number" min="1" value="{{settings_number_of_introduction_nodes}}") 115 | label(hidden="[[!developer]]") 116 | div Online*: 117 | detox-chat-icon-button(icon="question" tooltip="What online means?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_online") 118 | csw-group(hidden="[[!developer]]") 119 | csw-label-button(value="{{settings_online}}"): label 120 | input(type="radio" value="0") 121 | | No 122 | csw-label-button: label 123 | input(type="radio" value="1") 124 | | Yes 125 | label(hidden="[[!developer]]") 126 | div Packets per second*: 127 | detox-chat-icon-button(icon="question" tooltip="What are packets per second?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_packets_per_second") 128 | csw-input-text(hidden="[[!developer]]"): input(type="number" min="1" value="{{settings_packets_per_second}}") 129 | label(hidden="[[!developer]]") 130 | div Reconnects intervals: 131 | detox-chat-icon-button(icon="question" tooltip="What are reconnects intervals?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_reconnects_intervals") 132 | csw-textarea(hidden="[[!developer]]"): textarea(rows="10" wrap="off" value="{{settings_reconnects_intervals_string}}" on-blur="_settings_reconnects_intervals_blur") 133 | label(hidden="[[!developer]]") 134 | div Additional options: 135 | detox-chat-icon-button(icon="question" tooltip="What are additional options?" hidden="[[!help]]"): button(type="button" on-click="_help_settings_additional_options") 136 | csw-textarea(hidden="[[!developer]]"): textarea(rows="10" wrap="off" value="{{settings_additional_options_string}}" on-blur="_settings_additional_options_blur") 137 | label 138 | div.recommended Backup and restore data 139 | detox-chat-icon-button.recommended(icon="question" tooltip="What data can be backed up and restored?" hidden="[[!help]]"): button(type="button" on-click="_help_backup_restore_data") 140 | div 141 | csw-button: button.recommended(type="button" on-click="_backup") Backup 142 | csw-button: button.recommended(type="button" on-click="_restore") Restore 143 | label 144 | div.dangerous Remove all of the data 145 | detox-chat-icon-button.dangerous(icon="question" tooltip="What will be removed?" hidden="[[!help]]"): button(type="button" on-click="_help_remove_all_of_the_data") 146 | csw-button: button.dangerous(type="button" on-click="_remove_all_of_the_data") Remove 147 | p.restart-warning * Changing these settings may require restart to take effect 148 | script(src="script.js") 149 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-settings/style.css: -------------------------------------------------------------------------------- 1 | :host{display:block}label:not([hidden]){display:flex !important}label div{flex-grow:1}label detox-chat-icon-button{color:var(--text-color-base-disabled);margin-left:calc(var(--block-padding) / 2)}label detox-chat-icon-button:hover{color:var(--button-hover-color)}csw-textarea:not([hidden]){display:block}csw-textarea textarea{height:auto;resize:vertical}.restart-warning{color:var(--text-color-base-disabled);margin-bottom:0}.recommended{color:var(--text-color-success)}.dangerous{color:var(--text-color-error)} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-settings/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | display : block; 8 | } 9 | 10 | label { 11 | &:not([hidden]) { 12 | display : flex !important; 13 | } 14 | 15 | div { 16 | flex-grow : 1; 17 | } 18 | 19 | detox-chat-icon-button { 20 | color : var(--text-color-base-disabled); 21 | margin-left : calc(var(--block-padding) / 2); 22 | 23 | &:hover { 24 | color : var(--button-hover-color); 25 | } 26 | } 27 | } 28 | 29 | csw-textarea { 30 | &:not([hidden]) { 31 | display : block; 32 | } 33 | 34 | textarea { 35 | height : auto; 36 | resize : vertical; 37 | } 38 | } 39 | 40 | .restart-warning { 41 | color : var(--text-color-base-disabled); 42 | margin-bottom : 0; 43 | } 44 | 45 | .recommended { 46 | color : var(--text-color-success); 47 | } 48 | 49 | .dangerous { 50 | color : var(--text-color-error); 51 | } 52 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app-sidebar-status") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | span Network: [[_online(online, connected_nodes_count)]]/[[_announced(announced)]] 5 | span(tooltip="connected nodes/aware of nodes/routing paths/contacts connected") 6 | | [[connected_nodes_count]]/[[aware_of_nodes_count]]/[[routing_paths_count]]/[[application_connections_count]] 7 | csw-tooltip 8 | script(src="script.js") 9 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/script.js: -------------------------------------------------------------------------------- 1 | // Generated by LiveScript 1.5.0 2 | /** 3 | * @package Detox chat app 4 | * @author Nazar Mokrynskyi 5 | * @license 0BSD 6 | */ 7 | (function(){ 8 | require(['js/behaviors']).then(function(arg$){ 9 | var behaviors; 10 | behaviors = arg$[0]; 11 | Polymer({ 12 | is: 'detox-chat-app-sidebar-status', 13 | behaviors: [behaviors.state_instance], 14 | properties: { 15 | online: { 16 | type: Boolean, 17 | value: false 18 | }, 19 | announced: { 20 | type: Boolean, 21 | value: false 22 | }, 23 | connected_nodes_count: { 24 | type: Number, 25 | value: 0 26 | }, 27 | aware_of_nodes_count: { 28 | type: Number, 29 | value: 0 30 | }, 31 | routing_paths_count: { 32 | type: Number, 33 | value: 0 34 | }, 35 | application_connections_count: { 36 | type: Number, 37 | value: 0 38 | } 39 | }, 40 | ready: function(){ 41 | var state, this$ = this; 42 | state = this.state; 43 | state.on('online_changed', function(new_online){ 44 | this$.online = new_online; 45 | }).on('announced_changed', function(new_announced){ 46 | this$.announced = new_announced; 47 | }).on('connected_nodes_count_changed', function(connected_nodes_count){ 48 | this$.connected_nodes_count = connected_nodes_count; 49 | }).on('aware_of_nodes_count_changed', function(aware_of_nodes_count){ 50 | this$.aware_of_nodes_count = aware_of_nodes_count; 51 | }).on('routing_paths_count_changed', function(routing_paths_count){ 52 | this$.routing_paths_count = routing_paths_count; 53 | }).on('application_connections_count_changed', function(application_connections_count){ 54 | this$.application_connections_count = application_connections_count; 55 | }); 56 | }, 57 | _online: function(online, connected_nodes_count){ 58 | if (online && connected_nodes_count) { 59 | return 'online'; 60 | } else { 61 | return 'offline'; 62 | } 63 | }, 64 | _announced: function(announced){ 65 | if (announced) { 66 | return 'announced'; 67 | } else { 68 | return 'not announced'; 69 | } 70 | } 71 | }); 72 | }); 73 | }).call(this); 74 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/script.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | ([behaviors]) <-! require(['js/behaviors']).then 7 | Polymer( 8 | is : 'detox-chat-app-sidebar-status' 9 | behaviors : [ 10 | behaviors.state_instance 11 | ] 12 | properties : 13 | online : 14 | type : Boolean 15 | value : false 16 | announced : 17 | type : Boolean 18 | value : false 19 | connected_nodes_count : 20 | type : Number 21 | value : 0 22 | aware_of_nodes_count : 23 | type : Number 24 | value : 0 25 | routing_paths_count : 26 | type : Number 27 | value : 0 28 | application_connections_count : 29 | type : Number 30 | value : 0 31 | ready : !-> 32 | state = @state 33 | state 34 | .on('online_changed', (new_online) !~> 35 | @online = new_online 36 | ) 37 | .on('announced_changed', (new_announced) !~> 38 | @announced = new_announced 39 | ) 40 | .on('connected_nodes_count_changed', (@connected_nodes_count) !~>) 41 | .on('aware_of_nodes_count_changed', (@aware_of_nodes_count) !~>) 42 | .on('routing_paths_count_changed', (@routing_paths_count) !~>) 43 | .on('application_connections_count_changed', (@application_connections_count) !~>) 44 | _online : (online, connected_nodes_count) -> 45 | if online && connected_nodes_count 46 | 'online' 47 | else 48 | 'offline' 49 | _announced : (announced) -> 50 | if announced 51 | 'announced' 52 | else 53 | 'not announced' 54 | ) 55 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/style.css: -------------------------------------------------------------------------------- 1 | :host{border-top:var(--border-base);display:block;padding:var(--block-padding)}[tooltip]{cursor:help} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app-sidebar-status/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | border-top : var(--border-base); 8 | display : block; 9 | padding : var(--block-padding); 10 | } 11 | 12 | [tooltip] { 13 | cursor : help; 14 | } 15 | -------------------------------------------------------------------------------- /html/detox-chat-app/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-app/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-app") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | #sidebar(sidebar-shown$="[[sidebar_shown]]" on-click="_sidebar_click") 5 | #header 6 | csw-tabs(selected="{{selected_tab}}") 7 | csw-button: button(type="button") Contacts 8 | csw-button: button(type="button") Profile 9 | csw-button(icon="cogs" tooltip="Settings"): button(type="button") 10 | csw-button.hide-sidebar(icon="times"): button(type="button" on-click="_hide_sidebar") 11 | csw-switcher(selected="[[selected_tab]]") 12 | detox-chat-app-sidebar-contacts(chat-id="[[chat-id]]") 13 | detox-chat-app-sidebar-profile(chat-id="[[chat-id]]") 14 | detox-chat-app-sidebar-settings(chat-id="[[chat-id]]") 15 | detox-chat-app-sidebar-status(chat-id="[[chat-id]]") 16 | detox-chat-app-dialogs(chat-id="[[chat-id]]") 17 | script(src="script.js") 18 | -------------------------------------------------------------------------------- /html/detox-chat-app/style.css: -------------------------------------------------------------------------------- 1 | :host{display:flex;height:var(--app-height);overflow:hidden;width:var(--app-width)}#sidebar{background:var(--background-sidebar);border-right:var(--border-base);box-sizing:border-box;color:var(--text-color-base);display:flex;flex-basis:var(--sidebar-width-base);flex-direction:column;max-width:var(--sidebar-width-max);min-width:var(--sidebar-width-min);transition:transform var(--transition-duration);z-index:2}#sidebar::before,#sidebar::after{content:"";display:none;height:100%;left:calc(100% + var(--border-width-base));opacity:0;position:absolute;transition:opacity var(--transition-duration) var(--transition-duration-short);width:calc(var(--app-width) - 100% - var(--border-width-base))}#sidebar #header{display:flex;flex-shrink:0}#sidebar #header csw-tabs{flex-grow:1}#sidebar #header csw-tabs csw-button:last-of-type{margin-left:auto}#sidebar #header .hide-sidebar{border-bottom:var(--tabs-border);display:none}#sidebar csw-switcher{flex-grow:1;height:100%;overflow-y:auto}#sidebar detox-chat-sidebar-status{border-top:var(--border-base);flex-shrink:0;overflow:hidden}detox-chat-app-dialogs{flex-basis:calc(100% - var(--sidebar-width-base));flex-grow:1}@media(max-width: 768px){#sidebar{bottom:0;top:0;position:absolute;max-width:100vw;width:var(--sidebar-width-max);transform:translateX(calc(-1 * var(--app-width)))}#sidebar::before,#sidebar::after{display:block}#sidebar[sidebar-shown]{transform:translateX(0)}#sidebar[sidebar-shown]::before{background-color:var(--background-color-sidebar);opacity:var(--sidebar-shadow-opacity)}#sidebar[sidebar-shown]::after{background-image:var(--sidebar-shadow);opacity:1}#sidebar #header .hide-sidebar{border-left:var(--border-base);display:inline-block}detox-chat-app-dialogs{width:var(--app-width)}} 2 | -------------------------------------------------------------------------------- /html/detox-chat-app/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | display : flex; 8 | height : var(--app-height); 9 | overflow : hidden; 10 | width : var(--app-width); 11 | } 12 | 13 | #sidebar { 14 | background : var(--background-sidebar); 15 | border-right : var(--border-base); 16 | box-sizing : border-box; 17 | color : var(--text-color-base); 18 | display : flex; 19 | flex-basis : var(--sidebar-width-base); 20 | flex-direction : column; 21 | max-width : var(--sidebar-width-max); 22 | min-width : var(--sidebar-width-min); 23 | transition : transform var(--transition-duration); 24 | z-index : 2; 25 | 26 | &::before, &::after { 27 | content : ''; 28 | display : none; 29 | height : 100%; 30 | left : calc(100% + var(--border-width-base)); 31 | opacity : 0; 32 | position : absolute; 33 | transition : opacity var(--transition-duration) var(--transition-duration-short); 34 | width : calc(var(--app-width) - 100% - var(--border-width-base)); 35 | } 36 | 37 | #header { 38 | display : flex; 39 | flex-shrink : 0; 40 | 41 | csw-tabs { 42 | flex-grow : 1; 43 | 44 | csw-button:last-of-type { 45 | margin-left : auto; 46 | } 47 | } 48 | 49 | .hide-sidebar { 50 | border-bottom : var(--tabs-border); 51 | display : none; 52 | } 53 | } 54 | 55 | csw-switcher { 56 | flex-grow : 1; 57 | height : 100%; 58 | overflow-y : auto; 59 | } 60 | 61 | detox-chat-sidebar-status { 62 | border-top : var(--border-base); 63 | flex-shrink : 0; 64 | overflow : hidden; 65 | } 66 | } 67 | 68 | detox-chat-app-dialogs { 69 | flex-basis : calc(100% - var(--sidebar-width-base)); 70 | flex-grow : 1; 71 | } 72 | 73 | // Would be nice to have this value in CSS property, but it is not working:( 74 | @media (max-width : 768px) { 75 | #sidebar { 76 | bottom : 0; 77 | top : 0; 78 | position : absolute; 79 | max-width : 100vw; 80 | width : var(--sidebar-width-max); 81 | transform : translateX(calc(-1 * var(--app-width))); 82 | 83 | &::before, &::after { 84 | display : block; 85 | } 86 | 87 | &[sidebar-shown] { 88 | transform : translateX(0); 89 | 90 | &::before { 91 | background-color : var(--background-color-sidebar); 92 | opacity : var(--sidebar-shadow-opacity); 93 | } 94 | 95 | &::after { 96 | background-image : var(--sidebar-shadow); 97 | opacity : 1; 98 | } 99 | } 100 | 101 | #header { 102 | .hide-sidebar { 103 | border-left : var(--border-base); 104 | display : inline-block; 105 | } 106 | } 107 | } 108 | 109 | detox-chat-app-dialogs { 110 | width : var(--app-width); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/index.pug: -------------------------------------------------------------------------------- 1 | dom-module(id="detox-chat-icon-button") 2 | link(rel="import" href="style.css" type="css") 3 | template 4 | slot 5 | script(src="script.js") 6 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/script.js: -------------------------------------------------------------------------------- 1 | // Generated by LiveScript 1.5.0 2 | /** 3 | * @package Detox chat app 4 | * @author Nazar Mokrynskyi 5 | * @license 0BSD 6 | */ 7 | (function(){ 8 | Polymer({ 9 | is: 'detox-chat-icon-button', 10 | behaviors: csw.behaviors.cswButton 11 | }); 12 | }).call(this); 13 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/script.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | Polymer( 7 | is : 'detox-chat-icon-button' 8 | behaviors : csw.behaviors.csw-button 9 | ) 10 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/style.css: -------------------------------------------------------------------------------- 1 | :host{display:inline-block}:host([hidden]){display:none}::slotted(button){background-color:transparent;border:none;color:inherit;cursor:pointer;display:flex;font-size:inherit;padding:0} 2 | -------------------------------------------------------------------------------- /html/detox-chat-icon-button/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | :host { 7 | display : inline-block; 8 | } 9 | 10 | :host([hidden]) { 11 | display : none; 12 | } 13 | 14 | ::slotted(button) { 15 | background-color : transparent; 16 | border : none; 17 | color : inherit; 18 | cursor : pointer; 19 | display : flex; 20 | font-size : inherit; 21 | padding : 0; 22 | } 23 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/index.pug: -------------------------------------------------------------------------------- 1 | //- TODO: Following 2 line would be unnecessary if Polymer had proper NPM package. Now we have these imports + 404 errors in browser console, but at least it works 2 | link(rel="import" href="../node_modules/@webcomponents/shadycss/apply-shim.html") 3 | link(rel="import" href="../node_modules/@webcomponents/shadycss/custom-style-interface.html") 4 | link(rel="import" href="../node_modules/@polymer/polymer/polymer.html") 5 | script(src="../js/global.js") 6 | link(rel="import" href="cleverstyle-widgets-styling.html") 7 | link(rel="import" href="../node_modules/cleverstyle-widgets/src/index.html") 8 | link(rel="import" href="detox-chat-app/index.html") 9 | link(rel="import" href="detox-chat-app-dialogs/index.html") 10 | link(rel="import" href="detox-chat-app-sidebar-contacts/index.html") 11 | link(rel="import" href="detox-chat-app-sidebar-profile/index.html") 12 | link(rel="import" href="detox-chat-app-sidebar-settings/index.html") 13 | link(rel="import" href="detox-chat-app-sidebar-status/index.html") 14 | link(rel="import" href="detox-chat-icon-button/index.html") 15 | -------------------------------------------------------------------------------- /img/brick-wall.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/cross-stripes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/img/logo-128.png -------------------------------------------------------------------------------- /img/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/img/logo-256.png -------------------------------------------------------------------------------- /img/logo-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Detox/chat-app/f447bc5986a5151e5ba7fb74643783042234ecc5/img/logo-600.png -------------------------------------------------------------------------------- /img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index-debug.html: -------------------------------------------------------------------------------- 1 | Detox Chat (debug) -------------------------------------------------------------------------------- /index-debug.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | meta(charset="utf-8") 3 | title Detox Chat (debug) 4 | meta(name="theme-color" content="#181818") 5 | meta(name="viewport" content="width=device-width, initial-scale=1") 6 | link(rel="shortcut icon" href="favicon.ico") 7 | link(rel="manifest" href="manifest.json") 8 | link(rel="stylesheet" href="css/critical.css") 9 | link(rel="stylesheet" href="css/style.css") 10 | script(async data-main="js/a.require" src="node_modules/alameda/alameda.js") 11 | script(async src="node_modules/@webcomponents/webcomponentsjs/webcomponents-hi-sd-ce.js") 12 | link(async rel="import" href="html/index.html") 13 | detox-chat-app 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Detox Chat 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /js/a.async-styles.js: -------------------------------------------------------------------------------- 1 | document.head.querySelector('[media=async]').removeAttribute('media'); 2 | -------------------------------------------------------------------------------- /js/a.require.js: -------------------------------------------------------------------------------- 1 | // Generated by LiveScript 1.5.0 2 | /** 3 | * @package Detox chat app 4 | * @author Nazar Mokrynskyi 5 | * @license 0BSD 6 | */ 7 | (function(){ 8 | var defined_modules, current_module, wait_for, get_defined_module, load_dependencies, add_wait_for; 9 | if (typeof requirejs === 'function') { 10 | requirejs['config']({ 11 | 'baseUrl': '.', 12 | 'paths': { 13 | '@detox/base-x': 'node_modules/@detox/base-x/index', 14 | '@detox/chat': 'node_modules/@detox/chat/src/index', 15 | '@detox/core': 'node_modules/@detox/core/src/index', 16 | '@detox/crypto': 'node_modules/@detox/crypto/src/index', 17 | '@detox/dht': 'node_modules/@detox/dht/src/index', 18 | '@detox/nodes-manager': 'node_modules/@detox/nodes-manager/src/index', 19 | '@detox/routing': 'node_modules/@detox/routing/src/index', 20 | '@detox/simple-peer': 'node_modules/@detox/simple-peer/simplepeer.min', 21 | '@detox/transport': 'node_modules/@detox/transport/src/index', 22 | '@detox/utils': 'node_modules/@detox/utils/src/index', 23 | 'array-map-set': 'node_modules/array-map-set/src/index', 24 | 'async-eventer': 'node_modules/async-eventer/src/index', 25 | 'es-dht': 'node_modules/es-dht/src/index', 26 | 'autosize': 'node_modules/autosize/dist/autosize', 27 | 'fixed-size-multiplexer': 'node_modules/fixed-size-multiplexer/src/index', 28 | 'k-bucket-sync': 'node_modules/k-bucket-sync/src/index', 29 | 'merkle-tree-binary': 'node_modules/merkle-tree-binary/src/index', 30 | 'hotkeys-js': 'node_modules/hotkeys-js/dist/hotkeys', 31 | 'marked': 'node_modules/marked/marked.min', 32 | 'pako': 'node_modules/pako/dist/pako', 33 | 'random-bytes-numbers': 'node_modules/random-bytes-numbers/src/index', 34 | 'ronion': 'node_modules/ronion/src/index', 35 | 'swipe-listener': 'node_modules/swipe-listener/dist/swipe-listener' 36 | }, 37 | 'packages': [ 38 | { 39 | 'name': 'aez.wasm', 40 | 'location': 'node_modules/aez.wasm', 41 | 'main': 'src/index' 42 | }, { 43 | 'name': 'blake2.wasm', 44 | 'location': 'node_modules/blake2.wasm', 45 | 'main': 'src/index' 46 | }, { 47 | 'name': 'ed25519-to-x25519.wasm', 48 | 'location': 'node_modules/ed25519-to-x25519.wasm', 49 | 'main': 'src/index' 50 | }, { 51 | 'name': 'noise-c.wasm', 52 | 'location': 'node_modules/noise-c.wasm', 53 | 'main': 'src/index' 54 | }, { 55 | 'name': 'supercop.wasm', 56 | 'location': 'node_modules/supercop.wasm', 57 | 'main': 'src/index' 58 | } 59 | ] 60 | }); 61 | } else { 62 | /** 63 | * Simple RequireJS-like implementation that replaces alameda and should be enough for bundled modules 64 | */ 65 | defined_modules = {}; 66 | current_module = { 67 | exports: null 68 | }; 69 | wait_for = {}; 70 | /** 71 | * @param {string} name 72 | * @param {string} base_name 73 | * 74 | * @return {*} 75 | */ 76 | get_defined_module = function(name, base_name){ 77 | if (name === 'exports') { 78 | return {}; 79 | } else if (name === 'module') { 80 | return current_module; 81 | } else { 82 | if (name.startsWith('./')) { 83 | name = base_name.split('/').slice(0, -1).join('/') + '/' + name.substr(2); 84 | } 85 | return defined_modules[name]; 86 | } 87 | }; 88 | /** 89 | * @param {!Array} dependencies 90 | * @param {string} base_name 91 | * @param {!Function} fail_callback 92 | * 93 | * @return {Array} 94 | */ 95 | load_dependencies = function(dependencies, base_name, fail_callback){ 96 | var loaded_dependencies; 97 | loaded_dependencies = []; 98 | if (dependencies.every(function(dependency){ 99 | var loaded_dependency; 100 | loaded_dependency = get_defined_module(dependency, base_name); 101 | if (!loaded_dependency) { 102 | fail_callback(dependency); 103 | return false; 104 | } 105 | loaded_dependencies.push(loaded_dependency); 106 | return true; 107 | })) { 108 | return loaded_dependencies; 109 | } else { 110 | return null; 111 | } 112 | }; 113 | /** 114 | * @param {string} name 115 | * @param {!Function} callback 116 | */ 117 | add_wait_for = function(name, callback){ 118 | (wait_for[name] || (wait_for[name] = [])).push(callback); 119 | }; 120 | /** 121 | * @param {!Array} dependencies 122 | * @param {!Function} callback 123 | * 124 | * @return {!Promise} 125 | */ 126 | window['require'] = window['requirejs'] = function(dependencies, callback){ 127 | return new Promise(function(resolve){ 128 | var loaded_dependencies; 129 | loaded_dependencies = load_dependencies(dependencies, '', function(dependency){ 130 | add_wait_for(dependency, function(){ 131 | return window['require'](dependencies, function(){ 132 | var loaded_dependencies, res$, i$, to$; 133 | res$ = []; 134 | for (i$ = 0, to$ = arguments.length; i$ < to$; ++i$) { 135 | res$.push(arguments[i$]); 136 | } 137 | loaded_dependencies = res$; 138 | if (callback) { 139 | callback.apply(null, loaded_dependencies); 140 | } 141 | resolve(loaded_dependencies); 142 | }); 143 | }); 144 | }); 145 | if (loaded_dependencies) { 146 | if (callback) { 147 | callback.apply(null, loaded_dependencies); 148 | } 149 | resolve(loaded_dependencies); 150 | } 151 | }); 152 | }; 153 | /** 154 | * @param {string} name 155 | * @param {!Array} dependencies 156 | * @param {!Function} wrapper 157 | */ 158 | window['define'] = function(name, dependencies, wrapper){ 159 | var loaded_dependencies; 160 | if (!wrapper) { 161 | wrapper = dependencies; 162 | dependencies = []; 163 | } 164 | loaded_dependencies = load_dependencies(dependencies, name, function(dependency){ 165 | add_wait_for(dependency, function(){ 166 | return define(name, dependencies, wrapper); 167 | }); 168 | }); 169 | if (loaded_dependencies) { 170 | defined_modules[name] = wrapper.apply(null, loaded_dependencies) || current_module.exports; 171 | if (wait_for[name]) { 172 | wait_for[name].forEach(function(resolve){ 173 | resolve(); 174 | }); 175 | delete wait_for[name]; 176 | } 177 | } 178 | }; 179 | define['amd'] = {}; 180 | } 181 | }).call(this); 182 | -------------------------------------------------------------------------------- /js/a.require.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | if typeof requirejs == 'function' 7 | requirejs['config']( 8 | 'baseUrl' : '.' 9 | 'paths' : 10 | '@detox/base-x' : 'node_modules/@detox/base-x/index' 11 | '@detox/chat' : 'node_modules/@detox/chat/src/index' 12 | '@detox/core' : 'node_modules/@detox/core/src/index' 13 | '@detox/crypto' : 'node_modules/@detox/crypto/src/index' 14 | '@detox/dht' : 'node_modules/@detox/dht/src/index' 15 | '@detox/nodes-manager' : 'node_modules/@detox/nodes-manager/src/index' 16 | '@detox/routing' : 'node_modules/@detox/routing/src/index' 17 | '@detox/simple-peer' : 'node_modules/@detox/simple-peer/simplepeer.min' 18 | '@detox/transport' : 'node_modules/@detox/transport/src/index' 19 | '@detox/utils' : 'node_modules/@detox/utils/src/index' 20 | 'array-map-set' : 'node_modules/array-map-set/src/index' 21 | 'async-eventer' : 'node_modules/async-eventer/src/index' 22 | 'es-dht' : 'node_modules/es-dht/src/index' 23 | 'autosize' : 'node_modules/autosize/dist/autosize' 24 | 'fixed-size-multiplexer' : 'node_modules/fixed-size-multiplexer/src/index' 25 | 'k-bucket-sync' : 'node_modules/k-bucket-sync/src/index' 26 | 'merkle-tree-binary' : 'node_modules/merkle-tree-binary/src/index' 27 | 'hotkeys-js' : 'node_modules/hotkeys-js/dist/hotkeys' 28 | 'marked' : 'node_modules/marked/marked.min' 29 | 'pako' : 'node_modules/pako/dist/pako' 30 | 'random-bytes-numbers' : 'node_modules/random-bytes-numbers/src/index' 31 | 'ronion' : 'node_modules/ronion/src/index' 32 | 'swipe-listener' : 'node_modules/swipe-listener/dist/swipe-listener' 33 | 'packages' : [ 34 | { 35 | 'name' : 'aez.wasm' 36 | 'location' : 'node_modules/aez.wasm' 37 | 'main' : 'src/index' 38 | } 39 | { 40 | 'name' : 'blake2.wasm', 41 | 'location' : 'node_modules/blake2.wasm', 42 | 'main' : 'src/index' 43 | } 44 | { 45 | 'name' : 'ed25519-to-x25519.wasm' 46 | 'location' : 'node_modules/ed25519-to-x25519.wasm' 47 | 'main' : 'src/index' 48 | } 49 | { 50 | 'name' : 'noise-c.wasm' 51 | 'location' : 'node_modules/noise-c.wasm' 52 | 'main' : 'src/index' 53 | } 54 | { 55 | 'name' : 'supercop.wasm' 56 | 'location' : 'node_modules/supercop.wasm' 57 | 'main' : 'src/index' 58 | } 59 | ] 60 | ) 61 | else 62 | /** 63 | * Simple RequireJS-like implementation that replaces alameda and should be enough for bundled modules 64 | */ 65 | defined_modules = {} 66 | current_module = {exports: null} 67 | wait_for = {} 68 | /** 69 | * @param {string} name 70 | * @param {string} base_name 71 | * 72 | * @return {*} 73 | */ 74 | get_defined_module = (name, base_name) -> 75 | if name == 'exports' 76 | {} 77 | else if name == 'module' 78 | current_module 79 | else 80 | if name.startsWith('./') 81 | name = base_name.split('/').slice(0, -1).join('/') + '/' + name.substr(2) 82 | defined_modules[name] 83 | /** 84 | * @param {!Array} dependencies 85 | * @param {string} base_name 86 | * @param {!Function} fail_callback 87 | * 88 | * @return {Array} 89 | */ 90 | load_dependencies = (dependencies, base_name, fail_callback) -> 91 | loaded_dependencies = [] 92 | if dependencies.every((dependency) -> 93 | loaded_dependency = get_defined_module(dependency, base_name) 94 | if !loaded_dependency 95 | fail_callback(dependency) 96 | return false 97 | loaded_dependencies.push(loaded_dependency) 98 | true 99 | ) 100 | loaded_dependencies 101 | else 102 | null 103 | /** 104 | * @param {string} name 105 | * @param {!Function} callback 106 | */ 107 | add_wait_for = (name, callback) !-> 108 | wait_for[][name].push(callback) 109 | /** 110 | * @param {!Array} dependencies 111 | * @param {!Function} callback 112 | * 113 | * @return {!Promise} 114 | */ 115 | window['require'] = window['requirejs'] = (dependencies, callback) -> 116 | new Promise (resolve) !-> 117 | loaded_dependencies = load_dependencies( 118 | dependencies 119 | '' 120 | (dependency) !-> 121 | add_wait_for( 122 | dependency 123 | -> 124 | window['require'](dependencies, (...loaded_dependencies) !-> 125 | if callback 126 | callback(...loaded_dependencies) 127 | resolve(loaded_dependencies) 128 | ) 129 | ) 130 | ) 131 | if loaded_dependencies 132 | if callback 133 | callback(...loaded_dependencies) 134 | resolve(loaded_dependencies) 135 | /** 136 | * @param {string} name 137 | * @param {!Array} dependencies 138 | * @param {!Function} wrapper 139 | */ 140 | window['define'] = (name, dependencies, wrapper) !-> 141 | if !wrapper 142 | wrapper = dependencies 143 | dependencies = [] 144 | loaded_dependencies = load_dependencies( 145 | dependencies 146 | name 147 | (dependency) !-> 148 | add_wait_for( 149 | dependency 150 | -> 151 | define(name, dependencies, wrapper) 152 | ) 153 | ) 154 | if loaded_dependencies 155 | defined_modules[name] = wrapper(...loaded_dependencies) || current_module.exports 156 | if wait_for[name] 157 | wait_for[name].forEach (resolve) !-> 158 | resolve() 159 | delete wait_for[name] 160 | define['amd'] = {} 161 | -------------------------------------------------------------------------------- /js/behaviors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | // Generated by LiveScript 1.5.0 7 | (function(){ 8 | function Wrapper(detoxChat, state){ 9 | var state_instance, experience_level, help; 10 | state_instance = { 11 | properties: { 12 | chatId: { 13 | type: String, 14 | value: 'detox-chat-app' 15 | }, 16 | state: Object 17 | }, 18 | created: function(){ 19 | this.state = state.get_instance(this.chatId); 20 | if (!this.state.ready()) { 21 | detox_chat_app.notify_warning('Previous state was not found, new identity generated', 60); 22 | this.state.set_seed(detoxChat.generate_seed()); 23 | this.state.add_secret(detoxChat.generate_secret().slice(0, 4), 'Default secret'); 24 | } 25 | } 26 | }; 27 | experience_level = [ 28 | state_instance, { 29 | advanced_user: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | developer: { 34 | type: Boolean, 35 | value: false 36 | }, 37 | ready: function(){ 38 | var state, this$ = this; 39 | state = this.state; 40 | this.advanced_user = state.get_settings_experience() >= state.EXPERIENCE_ADVANCED; 41 | this.developer = state.get_settings_experience() === state.EXPERIENCE_DEVELOPER; 42 | state.on('settings_experience_changed', function(experience){ 43 | this$.advanced_user = experience >= state.EXPERIENCE_ADVANCED; 44 | this$.developer = experience === state.EXPERIENCE_DEVELOPER; 45 | }); 46 | } 47 | } 48 | ]; 49 | help = [ 50 | state_instance, { 51 | properties: { 52 | help: Boolean 53 | }, 54 | ready: function(){ 55 | var this$ = this; 56 | this.help = this.state.get_settings_help(); 57 | this.state.on('settings_help_changed', function(help){ 58 | this$.help = help; 59 | }); 60 | } 61 | } 62 | ]; 63 | return { 64 | state_instance: state_instance, 65 | experience_level: experience_level, 66 | help: help 67 | }; 68 | } 69 | define(['@detox/chat', 'js/state'], Wrapper); 70 | }).call(this); 71 | -------------------------------------------------------------------------------- /js/behaviors.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | function Wrapper (detox-chat, state) 7 | state_instance = 8 | properties : 9 | chat-id : 10 | type : String 11 | value : 'detox-chat-app' 12 | state : Object 13 | created : !-> 14 | @state = state.get_instance(@chat-id) 15 | if !@state.ready() 16 | detox_chat_app.notify_warning('Previous state was not found, new identity generated', 60) 17 | @state.set_seed(detox-chat.generate_seed()) 18 | @state.add_secret(detox-chat.generate_secret().slice(0, 4), 'Default secret') 19 | experience_level = [ 20 | state_instance 21 | advanced_user : 22 | type : Boolean 23 | value : false 24 | developer : 25 | type : Boolean 26 | value : false 27 | ready : !-> 28 | state = @state 29 | @advanced_user = state.get_settings_experience() >= state.EXPERIENCE_ADVANCED 30 | @developer = state.get_settings_experience() == state.EXPERIENCE_DEVELOPER 31 | state 32 | .on('settings_experience_changed', (experience) !~> 33 | @advanced_user = experience >= state.EXPERIENCE_ADVANCED 34 | @developer = experience == state.EXPERIENCE_DEVELOPER 35 | ) 36 | ] 37 | # TODO: Instead of behavior and separate elements with callbacks, it would be nice to have a custom element for help buttons 38 | help = [ 39 | state_instance 40 | properties : 41 | help : Boolean 42 | ready : !-> 43 | @help = @state.get_settings_help() 44 | @state.on('settings_help_changed', (@help) !~>) 45 | ] 46 | {state_instance, experience_level, help} 47 | 48 | define(['@detox/chat', 'js/state'], Wrapper) 49 | -------------------------------------------------------------------------------- /js/global.ls: -------------------------------------------------------------------------------- 1 | /** 2 | * @package Detox chat app 3 | * @author Nazar Mokrynskyi 4 | * @license 0BSD 5 | */ 6 | const IN_APP = location.search == '?home' 7 | /** 8 | * Force passive listeners on in Polymer 9 | */ 10 | Polymer.setPassiveTouchGestures(true) 11 | /** 12 | * Register service worker 13 | */ 14 | if ('serviceWorker' of navigator) && window.detox_sw_path 15 | ([detox-chat]) <-! require(['@detox/chat']).then 16 | # Make sure WebAssembly stuff are loaded 17 | <~! detox-chat.ready 18 | navigator.serviceWorker.register(detox_sw_path) 19 | .then (registration) !-> 20 | registration.onupdatefound = !-> 21 | installingWorker = registration.installing 22 | 23 | installingWorker.onstatechange = !-> 24 | switch installingWorker.state 25 | case 'installed' 26 | if navigator.serviceWorker.controller 27 | if IN_APP 28 | detox_chat_app.notify_success('Application was updated in background and new version ready to be used, restart to enjoy it', 10) 29 | else 30 | detox_chat_app.notify_success('Website was updated in background and new version ready to be used, refresh page to enjoy it', 10) 31 | else 32 | if IN_APP 33 | detox_chat_app.notify_success('Application is ready to work offline', 10) 34 | else 35 | detox_chat_app.notify_success('Website is ready to work offline', 10) 36 | case 'redundant' 37 | console.error('The installing service worker became redundant') 38 | window.detox_service_worker_registration = registration 39 | .catch (e) !-> 40 | console.error('Error during service worker registration:', e) 41 | /** 42 | * Requesting persistent storage, so that data will not be lost unexpectedly under storage pressure 43 | */ 44 | if navigator.storage?.persist 45 | navigator.storage.persisted().then (persistent) !-> 46 | if !persistent 47 | console.info 'Persistent storage is not yet granted, requesting...' 48 | navigator.storage.persist().then (granted) !-> 49 | if granted 50 | console.info 'Persistent storage granted' 51 | else 52 | console.warn 'Persistent storage denied, data may be lost under storage pressure' 53 | else 54 | console.warn 'Persistent storage not supported, data may be lost under storage pressure' 55 | 56 | desktop_notification_permission_requested = false 57 | /** 58 | * @param {string} status 59 | * @param {string} title 60 | * @param {string=} details 61 | * @param {number=} timeout 62 | * @param {!Function} onclick 63 | */ 64 | !function page_notification (status, title, details, timeout, onclick) 65 | body = document.createElement('div') 66 | if details 67 | body.innerHTML = '
' 68 | body.querySelector('b').textContent = title 69 | body.insertAdjacentText('beforeend', details) 70 | else 71 | body.insertAdjacentText('beforeend', title) 72 | notification = csw.functions.notify(body, status, 'right', timeout) 73 | if onclick 74 | notification.addEventListener('click', onclick) 75 | /** 76 | * @param {string} title 77 | * @param {string=} details 78 | * @param {number=} timeout 79 | * @param {!Function} onclick 80 | */ 81 | !function desktop_notification (title, details, timeout, onclick) 82 | notification = new Notification(title, { 83 | body : details 84 | }) 85 | if onclick 86 | notification.addEventListener('click', onclick) 87 | if timeout 88 | setTimeout (!-> 89 | notification.close() 90 | ), 1000 * timeout 91 | /** 92 | * @param {string} status 93 | * @param {string} title 94 | * @param {string=} details 95 | * @param {string=} timeout 96 | * 97 | * @return {!Promise} 98 | */ 99 | function notify (status, title, details, timeout) 100 | new Promise (resolve) !-> 101 | if typeof details == 'number' 102 | timeout := details 103 | details := '' 104 | if document.hasFocus() || !Notification || Notification.permission == 'denied' 105 | page_notification(status, title, details, timeout, resolve) 106 | else if Notification.permission == 'default' 107 | if !desktop_notification_permission_requested 108 | desktop_notification_permission_requested := true 109 | if IN_APP 110 | message = "Application tried to show you a system notification while was inactive, but you have to grant permission for that first, do that after clicking on this notification" 111 | else 112 | message = "Website tried to show you a desktop notification while was inactive, but you have to grant permission for that first, do that after clicking on this notification" 113 | csw.functions.notify(message, 'warning', 'right') 114 | ..addEventListener( 115 | 'click' 116 | !-> 117 | Notification.requestPermission().then (permission) !-> 118 | switch permission 119 | case 'granted' 120 | csw.functions.notify('You will no longer miss important notifications 😉', 'success', 'right', 3) 121 | case 'denied' 122 | csw.functions.notify('In case you change your mind, desktop notifications can be re-enabled in browser settings 😉', 'success', 'right', 5) 123 | ) 124 | page_notification(status, title, details, timeout, resolve) 125 | else 126 | desktop_notification(title, details, timeout, resolve) 127 | 128 | window.{}detox_chat_app 129 | ..notify_error = -> 130 | notify('error', ...&) 131 | ..notify = -> 132 | notify('', ...&) 133 | ..notify_success = -> 134 | notify('success', ...&) 135 | ..notify_warning = -> 136 | notify('warning', ...&) 137 | ..play_sound = (file) !-> 138 | # Song will not play until user interacts, but we may try to play it, so let's not throw an error 139 | try 140 | new Audio(file) 141 | ..play() 142 | ..simple_modal = (content) -> 143 | current_time = +(new Date) 144 | modal = csw.functions.simple_modal(content) 145 | ..addEventListener('close', !-> 146 | if history.state == current_time 147 | history.back() 148 | ) 149 | if IN_APP 150 | history.pushState(current_time, '', '#modal') 151 | window.addEventListener( 152 | 'popstate' 153 | !-> 154 | modal.close() 155 | {once: true} 156 | ) 157 | modal 158 | ..installation_prompt = -> 159 | if IN_APP 160 | return 161 | if localStorage.installation_prompt_timeout 162 | if +localStorage.installation_prompt_timeout > +(new Date) 163 | # Do not annoy user with this prompt 164 | return 165 | # Do not show prompt for one month when called subsequent times 166 | localStorage.installation_prompt_timeout = +(new Date) + 60 * 60 * 24 * 30 167 | else 168 | # Do not show prompt for one week when called first time 169 | localStorage.installation_prompt_timeout = +(new Date) + 60 * 60 * 24 * 7 170 | installation_prompt_event.then (event) !-> 171 | csw.functions.notify(""" 172 | If you use this application often, consider to install it for easy access:

173 | 174 | """, 'right') 175 | .querySelector('csw-button[primary]>button') 176 | .addEventListener('click', !-> 177 | event.prompt() 178 | ) 179 | # Handle back hardware button in application mode, allow 2 seconds to press back button or leave application open 180 | if IN_APP 181 | history.pushState({loaded: true}, '') 182 | addEventListener('popstate', !-> 183 | if !history.state?.loaded 184 | csw.functions.notify('Press one more time to exit', 'bottom', 2) 185 | setTimeout (!-> 186 | history.pushState({loaded: true}, '') 187 | ), 2000 188 | ) 189 | else 190 | installation_prompt_event = new Promise (resolve) !-> 191 | window.addEventListener('beforeinstallprompt', (event) !-> 192 | # Prevent Chromium <= 67 from automatically showing the prompt 193 | event.preventDefault() 194 | # Resolve promise with event so that it can be used later if needed 195 | resolve(event) 196 | ) 197 | -------------------------------------------------------------------------------- /js/markdown.js: -------------------------------------------------------------------------------- 1 | // Generated by LiveScript 1.5.0 2 | /** 3 | * @package Detox chat app 4 | * @author Nazar Mokrynskyi 5 | * @license 0BSD 6 | */ 7 | (function(){ 8 | function Wrapper(marked){ 9 | var renderer, options; 10 | renderer = new marked.Renderer(); 11 | renderer.link = function(){ 12 | return marked.Renderer.prototype.link.apply(this, arguments).replace(/ 4 | * @license 0BSD 5 | */ 6 | function Wrapper (marked) 7 | renderer = new marked.Renderer() 8 | renderer.link = -> 9 | marked.Renderer::link.apply(@, &).replace(/ 22 | marked(markdown_text, options) 23 | 24 | define(['marked'], Wrapper) 25 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color" : "#181818", 3 | "display" : "standalone", 4 | "icons" : [ 5 | { 6 | "src" : "img/logo.svg", 7 | "type" : "image/svg+xml", 8 | "sizes" : "any" 9 | }, 10 | { 11 | "src" : "img/logo-128.png", 12 | "type" : "image/png", 13 | "sizes" : "128x128" 14 | }, 15 | { 16 | "src" : "img/logo-256.png", 17 | "type" : "image/png", 18 | "sizes" : "256x256" 19 | }, 20 | { 21 | "src" : "img/logo-600.png", 22 | "type" : "image/png", 23 | "sizes" : "600x600" 24 | } 25 | ], 26 | "name" : "Detox Chat", 27 | "short_name" : "Detox Chat", 28 | "start_url" : "index.html?home", 29 | "theme_color" : "#181818", 30 | "scope" : "/" 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "@detox/chat-app", 3 | "description" : "Reference implementation of Chat application on top of Detox network using Detox Chat protocol", 4 | "keywords" : [ 5 | "detox", 6 | "chat", 7 | "anonymous", 8 | "distributed", 9 | "p2p" 10 | ], 11 | "version" : "", 12 | "homepage" : "https://github.com/Detox/chat-app", 13 | "author" : "Nazar Mokrynskyi ", 14 | "repository" : { 15 | "type" : "git", 16 | "url" : "git://github.com/Detox/chat-app.git" 17 | }, 18 | "license" : "0BSD", 19 | "files" : [ 20 | "css", 21 | "dist", 22 | "html", 23 | "img", 24 | "js", 25 | "index.html", 26 | "index-debug.html" 27 | ], 28 | "scripts" : { 29 | "demo-http-server" : "http-server" 30 | }, 31 | "dependencies" : { 32 | "@detox/chat" : "^0.16.0", 33 | "@detox/core" : "^0.20.0", 34 | "@detox/utils" : "^1.10.0", 35 | "@polymer/polymer" : "^2.6.0", 36 | "@webcomponents/shadycss" : "^1.3.1", 37 | "@webcomponents/webcomponentsjs" : "^1.2.2", 38 | "alameda" : "^1.2.1", 39 | "autosize" : "^4.0.2", 40 | "cleverstyle-widgets" : "^0.10.3", 41 | "hotkeys-js" : "^3.3.5", 42 | "marked" : "^0.4.0", 43 | "swipe-listener" : "^1.0.6" 44 | }, 45 | "devDependencies" : { 46 | "clean-css" : "^4.1.11", 47 | "del" : "^3.0.0", 48 | "gulp" : "^4.0.0", 49 | "gulp-htmlmin" : "^4.0.0", 50 | "gulp-rename" : "^1.3.0", 51 | "gulp-requirejs-optimize" : "^1.3.0", 52 | "gulp-uglify" : "^3.0.0", 53 | "http-server" : "^0.11.1", 54 | "polymer-bundler" : "^3.1.1", 55 | "uglify-es" : "^3.3.10", 56 | "workbox-build" : "^3.3.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Detox chat app 2 | Reference implementation of Chat application on top of Detox network using Detox Chat protocol. 3 | 4 | WARNING: INSECURE UNTIL PROVEN THE OPPOSITE!!! 5 | 6 | ## Browser support 7 | First of all, only 2 latest stable versions of any browser are supported! Don't ever ask to support older ones. 8 | 9 | | Browser | Support level | 10 | |----------|-----------------------------------------------------| 11 | | Chromium | Fully supported | 12 | | Firefox | Fully supported | 13 | | Safari | Should work, but was not tested | 14 | | Edge | RTCDataChannel not supported at all, hopefully soon | 15 | 16 | ## Alpha testing 17 | Currently application is at alpha quality and only recommended for developers, not really suitable for early adopters yet. 18 | 19 | Builds directly from master branch are available at [detox.github.io/chat-app](https://detox.github.io/chat-app/), they may be broken from time to time though. 20 | 21 | WARNING: Alpha version can eat all of your CPU, RAM, network bandwidth, battery on mobile device or all at the same time. It may event eat pizza from your fridge. Don't blame me if it does. 22 | 23 | If you want to run debugging version from sources, do: 24 | ```bash 25 | git clone https://github.com/Detox/chat-app.git detox-chat-app 26 | cd detox-chat-app 27 | npm install 28 | npm run demo-http-server 29 | ``` 30 | 31 | And open browser window at `http://127.0.0.1:8081/index-debug.html`. 32 | Make sure to wait until node is connected to the network and announced to the network, so that someone can find you. 33 | 34 | ## Contribution 35 | Feel free to create issues and send pull requests (for big changes create an issue first and link it from the PR), they are highly appreciated! 36 | 37 | When reading LiveScript code make sure to configure 1 tab to be 4 spaces (GitHub uses 8 by default), otherwise code might be hard to read. 38 | 39 | ## License 40 | Free Public License 1.0.0 / Zero Clause BSD License 41 | 42 | https://opensource.org/licenses/FPL-1.0.0 43 | 44 | https://tldrlegal.com/license/bsd-0-clause-license 45 | -------------------------------------------------------------------------------- /sw.min.js: -------------------------------------------------------------------------------- 1 | importScripts("dist/workbox-v3.3.0/workbox-sw.js"),workbox.setConfig({modulePathPrefix:"dist/workbox-v3.3.0"}),workbox.core.setCacheNameDetails({prefix:"detox-chat-app"}),workbox.skipWaiting(),workbox.clientsClaim(),self.__precacheManifest=[{url:"dist/aez.wasm?a2fe3",revision:"a2fe3"},{url:"dist/blake2.wasm?bdab5",revision:"bdab5"},{url:"dist/brick-wall.svg?c63a0",revision:"c63a0"},{url:"dist/cross-stripes.svg?7e8c9",revision:"7e8c9"},{url:"dist/definite.mp3?e60cc",revision:"e60cc"},{url:"dist/ed25519-to-x25519.wasm?006dd",revision:"006dd"},{url:"dist/fa-solid-900.woff2?b2d77",revision:"b2d77"},{url:"dist/favicon.ico?16501",revision:"16501"},{url:"dist/index.min.html?bffdf",revision:"bffdf"},{url:"dist/knob.mp3?3628d",revision:"3628d"},{url:"dist/logo-128.png?e1b94",revision:"e1b94"},{url:"dist/logo-256.png?e5c43",revision:"e5c43"},{url:"dist/logo-600.png?73696",revision:"73696"},{url:"dist/logo.svg?7c632",revision:"7c632"},{url:"dist/manifest.json?d4e27",revision:"d4e27"},{url:"dist/noise-c.wasm?4f6d8",revision:"4f6d8"},{url:"dist/script.min.js?bcdd6",revision:"bcdd6"},{url:"dist/style.min.css?1718e",revision:"1718e"},{url:"dist/supercop.wasm?ed96f",revision:"ed96f"},{url:"dist/webcomponents.min.js?a862e",revision:"a862e"},{url:"index.html",revision:"01e25"}].concat(self.__precacheManifest||[]),workbox.precaching.suppressWarnings(),workbox.precaching.precacheAndRoute(self.__precacheManifest,{ignoreUrlParametersMatching:[/./]}); --------------------------------------------------------------------------------