@yz-social/civildefense.io 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/announce.js +24 -0
- package/docs/YZ-Brief.pdf +0 -0
- package/index.js +2 -0
- package/movie/camera.jpg +0 -0
- package/movie/movie.js +151 -0
- package/movie/script.js +272 -0
- package/movie/test.js +2 -0
- package/nginx/nginx.conf +83 -0
- package/nginx/yz.social +81 -0
- package/package.json +34 -0
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -0
- package/public/about/CivilDefense.mp4 +0 -0
- package/public/about/In case of Nazis, use CivilDefense.io.png +0 -0
- package/public/about/broadcast.png +0 -0
- package/public/about/civil-defense.png +0 -0
- package/public/about/conversation-and-image-high.png +0 -0
- package/public/about/conversation-and-image.png +0 -0
- package/public/about/en.html +157 -0
- package/public/about/es.html +142 -0
- package/public/about/flood-fire-ice-high.png +0 -0
- package/public/about/flood-fire-ice.png +0 -0
- package/public/about/hero-high.png +0 -0
- package/public/about/hero.png +0 -0
- package/public/about/index.html +8 -0
- package/public/about/nazis.png +0 -0
- package/public/about/script.js +69 -0
- package/public/about/streaming-radio-high.png +0 -0
- package/public/about/streaming-radio.png +0 -0
- package/public/about/style.css +306 -0
- package/public/control-safe-rectangle.html +40 -0
- package/public/favicon.ico +0 -0
- package/public/images/Achtung.png +0 -0
- package/public/images/YZ Owl.png +0 -0
- package/public/images/civil-defense-122.png +0 -0
- package/public/images/civil-defense-192.png +0 -0
- package/public/images/civil-defense-240.png +0 -0
- package/public/images/civil-defense-512.png +0 -0
- package/public/images/civil-defense.png +0 -0
- package/public/images/hero-small.png +0 -0
- package/public/images/qr-scan.svg +2 -0
- package/public/images/qr.png +0 -0
- package/public/images/qr.svg +155 -0
- package/public/images/recenter.svg +5 -0
- package/public/images/share.png +0 -0
- package/public/images/share.svg +2 -0
- package/public/index.html +170 -0
- package/public/javascripts/agent.js +324 -0
- package/public/javascripts/display.js +25 -0
- package/public/javascripts/hashtags.js +205 -0
- package/public/javascripts/main.js +394 -0
- package/public/javascripts/map.js +725 -0
- package/public/javascripts/p2pWebNetwork.js +245 -0
- package/public/javascripts/s2.js +77 -0
- package/public/javascripts/scripting.js +112 -0
- package/public/javascripts/service-manager.js +149 -0
- package/public/javascripts/translations.js +139 -0
- package/public/javascripts/versions.js +23 -0
- package/public/manifest.json +25 -0
- package/public/owl.ico +0 -0
- package/public/platformer.html +52 -0
- package/public/robots.txt +6 -0
- package/public/service-worker.js +218 -0
- package/public/stylesheets/style.css +442 -0
- package/routes/index.js +123 -0
- package/server/app.js +116 -0
- package/server/bridge.js +397 -0
- package/server/dirname.js +15 -0
- package/server/getLocation.js +18 -0
- package/server/identity.js +111 -0
- package/server/location.json +12 -0
- package/spec/axonSpec.gratuitousNameChangeForSignal +246 -0
- package/spec/axonSpec.js +280 -0
- package/spec/axonSpec.jsRemoveThePartAfterJS +252 -0
- package/spec/civildefenseSpec.js +61 -0
- package/spec/pubsubSpec.js +128 -0
- package/spec/support/jasmine.mjs +14 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const language = new URL(location).searchParams.get('lang') || navigator.language;
|
|
2
|
+
const lang = language.split('-')[0].toLowerCase(); // Get the language the the user has their browser to.
|
|
3
|
+
|
|
4
|
+
export function Int(strings, ...values) { // A tagged template function that converts to lang.
|
|
5
|
+
// E.g., if the browser lang is 'es', Int`Your Location` => "Tu Ubicación", and Int`#version` => "Versión"
|
|
6
|
+
return strings.reduce((result, substring, i) => {
|
|
7
|
+
const value = values[i] !== undefined ? values[i] : '';
|
|
8
|
+
// Apply operation to the string, then append the interpolated value
|
|
9
|
+
const content = translations[substring];
|
|
10
|
+
const translated = content?.[lang] || content?.["en"] || substring;
|
|
11
|
+
return result + translated + value;
|
|
12
|
+
}, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const translations = {
|
|
16
|
+
['Civil Defense']: {es: "Defensa Civil"},
|
|
17
|
+
['Anonymous']: {es: "sin nombre"},
|
|
18
|
+
['Your Location']: {es: "Tu Ubicación"},
|
|
19
|
+
['Default location. Geolocation unavailable.']: {es: "Ubicación predeterminada. Geoubicación no disponible."},
|
|
20
|
+
['Tap anywhere to mark a concern. Markers fade after 24 hours.']: {es: "Toca cualquier punto para marcar una preocupación. Los marcadores desaparecen después de 24 horas."},
|
|
21
|
+
|
|
22
|
+
['Location access denied. Using default location.']: {es: "Acceso a la ubicación denegado. Se utilizará la ubicación predeterminada."},
|
|
23
|
+
['No network connection.']: {es: "Sin conexión de red."},
|
|
24
|
+
['Geolocation not supported. Using default location.']: {es: "Geolocalización no compatible. Se utilizará la ubicación predeterminada."},
|
|
25
|
+
['Unable to get location.']: {es: "No se puede obtener la ubicación."},
|
|
26
|
+
['The service connection has closed. Please reload.']: {es: "La conexión de servicio se ha cerrado. Por favor, recargue la página."},
|
|
27
|
+
['Connection closed due to inactivity. Will reconnect on use.']: {es: "Conexión cerrada por inactividad. Se reconectará al usarla."},
|
|
28
|
+
['Connecting to network...']: {es: "Conectando a la red..."},
|
|
29
|
+
['Disconnected. Retrying in ']: {es: "Desconectado. Reintentando en "},
|
|
30
|
+
[' seconds.']: {es: " segundos."},
|
|
31
|
+
|
|
32
|
+
['cake']: {es: "pastel"},
|
|
33
|
+
['fire']: {es: "fuego"},
|
|
34
|
+
['flood']: {es: "inundación"},
|
|
35
|
+
['ice']: {es: "la migra"},
|
|
36
|
+
['help']: {es: "ayuda"},
|
|
37
|
+
['add topic']: {es: "añadir tema"},
|
|
38
|
+
|
|
39
|
+
['No additional information.']: {es: "No hay información adicional."},
|
|
40
|
+
['posted']: {es: "al corriente"},
|
|
41
|
+
['changed']: {es: "cambió"},
|
|
42
|
+
['reply here']: {es: "responder aquí"},
|
|
43
|
+
['remove']: {es: "eliminar"},
|
|
44
|
+
['cancel alert']: {es: "Cancelar alerta"},
|
|
45
|
+
['update']: {es: "actualizar"},
|
|
46
|
+
['for update to...']: {es: "para actualizar a..."},
|
|
47
|
+
['In Firefox, sharing must be explicitly enabled through the <a target="civildefense_help" href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Experimental_features#webshare_api">dom.webshare.enabled</a> preference in about:config.']: {es: 'En Firefox, la función de compartir debe habilitarse explícitamente mediante la preferencia <a target="civildefense_help" href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Experimental_features#webshare_api">dom.webshare.enabled</a> en about:config.'},
|
|
48
|
+
['This browser does not support sharing.']: {es: "Este navegador no admite la función de compartir."},
|
|
49
|
+
['This browser does not support file sharing.']: {es: "Este navegador no admite el intercambio de archivos."},
|
|
50
|
+
['This browser does not support sharing this type of file.']: {es: "Este navegador no admite compartir este tipo de archivo."},
|
|
51
|
+
|
|
52
|
+
['does not support notifications on WebViews embedded in other programs. Please use CivilDefense.io in native']: {es: "no admite notificaciones en WebViews integrados en otros programas. Por favor, utilice CivilDefense.io en el navegador nativo"},
|
|
53
|
+
['Apple only supports mobile notifications for web pages that have been']: {es: "Apple solo admite notificaciones móviles para páginas web que hayan sido"},
|
|
54
|
+
['installed to the home screen']: {es: "instaladoi en la pantalla de inicio"},
|
|
55
|
+
['Enable notifications']: {es: "Habilitar notificaciones"},
|
|
56
|
+
['Allow notifications']: {es: "Permitir notificaciones"},
|
|
57
|
+
['Permissions can be re-enabled through the']: {es: "Los permisos se pueden volver a habilitar a través de"},
|
|
58
|
+
['app']: {es: "Aplicación"},
|
|
59
|
+
['browser site settings']: {es: "configuración del sitio en el navegador"},
|
|
60
|
+
|
|
61
|
+
['#aboutReport']: {en: "Report immediate concerns to the public by tapping their location on the map.", es: "Informa de cualquier problema inmediato al público pulsando su ubicación en el mapa."},
|
|
62
|
+
['#aboutShared']: {en: "These locations are shared over anonymous p2p with other users in your area.", es: "Estas ubicaciones se comparten a través de redes P2P anónimas con otros usuarios de tu zona."},
|
|
63
|
+
['#aboutFade']: {en: "Reported concerns will fade away over 24 hours.", es: "Las preocupaciones manifestadas se disiparán en 24 horas."},
|
|
64
|
+
['#aboutAnyone1']: {en: "A connected mirror of this app can be", es: "Una réplica conectada de esta aplicación puede ser"},
|
|
65
|
+
['#aboutAnyone2']: {en: "run by anyone,", es: "administrado por cualquier persona,"},
|
|
66
|
+
['#aboutAnyone3']: {en: "in case this site is taken down.", es: "en caso de que este sitio sea dado de baja."},
|
|
67
|
+
['#learnMore']: {en: 'Learn More', es: "Más Información"},
|
|
68
|
+
['#version']: {en: "Version", es: "Versión"},
|
|
69
|
+
['#checkForUpdates']: {en: "check for updates", es: "Buscar actualizaciones"},
|
|
70
|
+
['#downloadUpdates']: {en: "install update", es: "Instalar actualización"},
|
|
71
|
+
['#newVersionHeader']: {en: "New version available", es: "Nueva versión disponible"},
|
|
72
|
+
['#updateNowQuestion']: {en: "Would you like to update now?", es: "¿Le gustaría actualizar ahora?"},
|
|
73
|
+
['#updateReload']: {en: "All CivilDefense.io tabs will reload.", es: "Todas las pestañas de CivilDefense.io se recargarán."},
|
|
74
|
+
['#updateDefer']: {en: "Alternatively, you can update later through the button in About.", es: "Alternativamente, puede actualizar más tarde a través del botón en «Acerca de»."},
|
|
75
|
+
['#downloadUpdates2']: {en: "yes, update", es: "Sí, actualizar."},
|
|
76
|
+
['#downloadDefer']: {en: "no, not yet", es: "No, todavía no."},
|
|
77
|
+
['No update at']: {es: "No hay actualizaciones a las"},
|
|
78
|
+
['is available']: {es: "está disponible"},
|
|
79
|
+
|
|
80
|
+
['#describePrivate1']: {en: "Here are your private labels for", es: "Aquí están sus etiquetas privadas para"},
|
|
81
|
+
['#describePrivate2']: {en: "to help you recognize posts from them:", es: "Para ayudarte a reconocer publicaciones de ellas:"},
|
|
82
|
+
['#describePublic']: {en: "The handles that they currently share publicly:", es: "Los nombres de usuario que comparten públicamente en la actualidad:"},
|
|
83
|
+
['#describeSystem']: {en: "The internal system handles for this person:", es: "El sistema interno gestiona lo siguiente para esta persona:"},
|
|
84
|
+
['#pickLabels']: {en: "You can pick the labels you like, or securely capture any of the following...", es: "Puede elegir las etiquetas que prefiera o capturar de forma segura cualquiera de las siguientes..."},
|
|
85
|
+
['handle']: {es: "el título"},
|
|
86
|
+
['your handle']: {es: "tu título"},
|
|
87
|
+
['Type a new topic name to see any alerts on the map with this topic.']: {es: "Escribe el nombre de un nuevo tema para ver en el mapa las alertas relacionadas con dicho tema."},
|
|
88
|
+
['Change the topic or remove the alert with the topic button in the upper right of the conversation dialog.']: {es: "Cambia el tema o elimina la alerta mediante el botón de tema situado en la esquina superior derecha del cuadro de diálogo de la conversación."},
|
|
89
|
+
['Topic "']: {es: "El tema «"},
|
|
90
|
+
['" has been removed. You can add it back with "add topic".']: {es: "» se ha eliminado. Puedes volver a añadirlo con «añadir tema»."},
|
|
91
|
+
['Tapping the map will now produce an alert for the "']: {es: "Al tocar el mapa, ahora se generará una alerta para el tema «"},
|
|
92
|
+
['" topic.']: {es: "»."},
|
|
93
|
+
['Turning "']: {es: "Volviendo a activar las alertas de «"},
|
|
94
|
+
['" alerts back on in the map.']: {es: "» en el mapa."},
|
|
95
|
+
['Turning off "']: {es: "Desactivando las alertas de «"},
|
|
96
|
+
['" alerts in the map. You can delete the topic altogether with the X.']: {es: "» en el mapa. Puedes eliminar el tema por completo con la X."},
|
|
97
|
+
|
|
98
|
+
['Click to see more information in another tab about what you can do with this app and how it is resistant to tracking, censorship, and takedown.']: {es: "Haz clic para ver más información en otra pestaña sobre lo que puedes hacer con esta aplicación y cómo resiste el rastreo, la censura y la retirada de contenido."},
|
|
99
|
+
['Click to see the source code and documentation for serving copies of this app in another tab.']: {es: "Haz clic para ver el código fuente y la documentación para servir copias de esta aplicación en otra pestaña."},
|
|
100
|
+
['Show conversation for this ']: {es: "Mostrar la conversación de esta alerta de "},
|
|
101
|
+
[' alert.']: {es: "."},
|
|
102
|
+
['Click to download.']: {es: "Haz clic para descargar."},
|
|
103
|
+
['Zoom in to show more detail in the map.']: {es: "Amplía la imagen para mostrar más detalles en el mapa."},
|
|
104
|
+
['Zoom out to show a larger area in the map.']: {es: "Aleja la vista para mostrar un área más amplia en el mapa."},
|
|
105
|
+
['Change the text label for you shown in the conversation for your alerts and replies.']: {es: "Cambia la etiqueta de texto que se muestra en la conversación para tus alertas y respuestas."},
|
|
106
|
+
['Change the image shown for you in the conversation for your alerts and replies.']: {es: "Cambia la imagen que se muestra en la conversación para tus alertas y respuestas."},
|
|
107
|
+
['Toggle whether alerts for this topic shown on the map. Separately, a radio button is shown when selected and sets this as the initial topic of the next alert you make, while an x button is shown when deselected and removes the topic.']: {es: "Alterna la visualización en el mapa de las alertas relacionadas con este tema. Por otro lado, al seleccionar el tema aparece un botón de opción que lo establece como el tema inicial de la próxima alerta que crees; al deseleccionarlo, aparece un botón con una «x» que elimina dicho tema."},
|
|
108
|
+
['Add a new topic for which the map should show any alerts.']: {es: "Añada un nuevo tema para el cual el mapa deba mostrar alertas."},
|
|
109
|
+
['Information about this app, and options to change how you appear to others.']: {es: "Información sobre esta aplicación y opciones para cambiar cómo apareces ante los demás."},
|
|
110
|
+
['Wipe from ']: {es: "Elimina de "},
|
|
111
|
+
[' all personal data and source files for this app.']: {es: " todos los datos personales y archivos fuente de esta aplicación."},
|
|
112
|
+
['Display QR code that can be scanned by a friend to show the same map on their device.']: {es: "Muestra un código QR que un amigo puede escanear para ver el mismo mapa en su dispositivo."},
|
|
113
|
+
[' share a link to this map and topics, with picture of map.']: {es: " comparte un enlace a este mapa y a los temas, junto con una imagen del mapa."},
|
|
114
|
+
['Recenter the map to where you are in the world.']: {es: "Vuelve a centrar el mapa en tu ubicación actual."},
|
|
115
|
+
['Post your reply.']: {es: "Publica tu respuesta."},
|
|
116
|
+
['Attach a file to your reply.']: {es: "Adjunta un archivo a tu respuesta."},
|
|
117
|
+
['Delete your reply.']: {es: "Elimina tu respuesta."},
|
|
118
|
+
['Change the topic or delete your alert.']: {es: "Cambia el tema o elimina tu alerta."},
|
|
119
|
+
['Check to see if a new version of the app is available.']: {es: "Comprueba si hay una nueva versión de la aplicación disponible."},
|
|
120
|
+
['Update to a new version of this app.']: {es: "Actualiza a una nueva versión de esta aplicación."},
|
|
121
|
+
['Share though .']: {es: "Comparte a través de "},
|
|
122
|
+
[' the text and attachments of this reply, with a link to open this alert.']: {es: " el texto y los archivos adjuntos de esta respuesta, junto con un enlace para abrir esta alerta."},
|
|
123
|
+
['Share through ']: {es: "Comparte un enlace a través de "},
|
|
124
|
+
[' a link to open this alert.']: {es: " para abrir esta alerta."},
|
|
125
|
+
['Control how others see me.']: {es: "Controla cómo me ven los demás."},
|
|
126
|
+
['Control how this person is labeled on my device.']: {es: "Controla cómo se etiqueta a esta persona en mi dispositivo."},
|
|
127
|
+
['Choose the label you want to appear for alerts and replies by this user.']: {es: "Selecciona la etiqueta que quieras que aparezca para las alertas y respuestas de este usuario."},
|
|
128
|
+
["Capture this unique text so that even if the user changes what they share, you'll still see this text for alerts and replies by this user."]: {es: "Captura este texto único para que, aunque el usuario cambie lo que comparte, sigas viendo este texto en las alertas y respuestas de dicho usuario."},
|
|
129
|
+
["Capture this name they are currently sharing, so that even if the user changes what they share, you'll still see this text for alerts and replies by this user."]: {es: "Guarda el nombre que están compartiendo actualmente, de modo que, aunque el usuario cambie lo que comparte, sigas viendo este texto en las alertas y respuestas de dicho usuario."},
|
|
130
|
+
["Capture this unique image so that even if the user changes what they share, they will still be shown to you with this picture for alerts and replies by this user."]: {es: "Captura esta imagen única para que, aunque el usuario cambie lo que comparte, se te siga mostrando con esta foto en las alertas y respuestas de dicho usuario."},
|
|
131
|
+
["Capture this image they are currently sharing, so that even if the user changes what they share, you'll still see this image for alerts and replies by this user."]: {es: "Guarda esta imagen que están compartiendo actualmente, de modo que, aunque el usuario cambie lo que comparte, sigas viendo esta imagen en las alertas y respuestas de dicho usuario."},
|
|
132
|
+
['Choose the picture you want to appear for alerts and replies by this user.']: {es: "Elige la imagen que quieras que aparezca para las alertas y respuestas de este usuario."},
|
|
133
|
+
['Enable local ']: {es: "Habilita notificaciones locales de "},
|
|
134
|
+
[' notifcations for map alerts, without going through any servers. Requires that the app be running.']: {es: " para alertas del mapa, sin pasar por ningún servidor. Requiere que la aplicación esté en ejecución."},
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
['Too many posts. (5 allowed every 5 minutes.) Removing oldest from this period.']: {es: "Demasiadas publicaciones. (Se permiten 5 cada 5 minutos). Eliminando las más antiguas de este período."},
|
|
138
|
+
['#wipe']: {en: "Wipe all CivilDefense.io data.", es: "Eliminar todos los datos de CivilDefense.io."}
|
|
139
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pkg from '../package.json' with {type: 'json'};
|
|
2
|
+
export const appVersion = pkg.version; // Overall semver of app. Used in display, and for comparison by service worker.
|
|
3
|
+
export const dataVersion = appVersion.split('.')[0]; // Compatability differentiator used below.
|
|
4
|
+
|
|
5
|
+
export function stripLeadingEmoji(string) { // Return string without any leading emoji (which might be of varying
|
|
6
|
+
// length) followed by an optional emoji break character and any whitespace.
|
|
7
|
+
// {Extended_Pictographic} is often recommended instead of {Emoji} as the latter includes numbers
|
|
8
|
+
// and symbols. However, the former misses, e.g., the flag emojis.
|
|
9
|
+
return string.replace(/^\p{Emoji}*\uFE0F?\s*/u, '') || string;
|
|
10
|
+
};
|
|
11
|
+
export function canonicalTag(tag) { // A string representing tag, without the (leading) emoji if any.
|
|
12
|
+
return stripLeadingEmoji(tag).toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function agentPersistKey(metadataType, agentTag) { // A label for looking up metadataType for agentTag.
|
|
16
|
+
return `${metadataType}-${agentTag}`;
|
|
17
|
+
}
|
|
18
|
+
export function agentTopic(metadataType, agentTag) { // Return topic name for public info about agent specified by tag.
|
|
19
|
+
return `public:${dataVersion}:${agentPersistKey(metadataType, agentTag)}`;
|
|
20
|
+
}
|
|
21
|
+
export function alertTopic(cellid, tag) { // Return topic name for public info about specified tag in cellid.
|
|
22
|
+
return `civildefense.io:${dataVersion}:${cellid}:${canonicalTag(tag)}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "CivilDefense.io",
|
|
3
|
+
"description": "Safely safely share live sightings on a map.",
|
|
4
|
+
"start_url": "/",
|
|
5
|
+
"display": "standalone",
|
|
6
|
+
"icons": [{
|
|
7
|
+
"src": "images/civil-defense-512.png",
|
|
8
|
+
"type": "image/png",
|
|
9
|
+
"sizes": "512x512"
|
|
10
|
+
}, {
|
|
11
|
+
"src": "images/civil-defense-192.png",
|
|
12
|
+
"sizes": "192x192",
|
|
13
|
+
"type": "image/png"
|
|
14
|
+
}, {
|
|
15
|
+
"src": "images/civil-defense-122.png",
|
|
16
|
+
"sizes": "122x122",
|
|
17
|
+
"type": "image/png"
|
|
18
|
+
}],
|
|
19
|
+
|
|
20
|
+
"comment": "In Chrome App Settings, the following sets the initial value of 'Opening supported links' (meaning in-scope links) to be 'Open in FairShare' (rather than 'Open in Chrome browser'.",
|
|
21
|
+
"handle_links": "preferred",
|
|
22
|
+
"launch_handler": {
|
|
23
|
+
"client_mode": "navigate-existing"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/public/owl.ico
ADDED
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
|
+
<title>Platform detection</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<p>userAgent: <span id="userAgent"></span></p>
|
|
10
|
+
<p>platform: <span id="platform"></span></p>
|
|
11
|
+
<p>agentData.platform: <span id="agentDataPlatform"><span></p>
|
|
12
|
+
<p>standalone: <span id="standalone"></span></p>
|
|
13
|
+
<p>isWebView: <span id="WebView"></span></p>
|
|
14
|
+
<p>isApple: <span id="Apple"></span></p>
|
|
15
|
+
<p>isMobile: <span id="Mobile"></span></p>
|
|
16
|
+
<p>osName: <span id="os"></span></p>
|
|
17
|
+
<p>regularized mobilePlatformName (i.e., iOS or Android IF mobile): <span id="mobilePlatform"></span></p>
|
|
18
|
+
<p>regularized mobileVendorName (i.e, Apple or Android IF mobile): <span id="mobileVendor"></span></p>
|
|
19
|
+
<p>native mobileBrowserName (i.e., Safari or Chrome IF mobile): <span id="mobileBrowser"></span></p>
|
|
20
|
+
<p>browserName: <span id="browserN"></span></p>
|
|
21
|
+
<script>
|
|
22
|
+
function isWebView() { return /CriOS|(WebView|wv|(iPhone|iPod|iPad)(?!.*Safari))/.test(navigator.userAgent); }
|
|
23
|
+
function isApple() { return navigator.platform.startsWith('Mac') || ['iPhone', 'iPad'].includes(navigator.platform); }
|
|
24
|
+
function isMobile() { return navigator.userAgentData?.mobile || /iPhone|iPad|iPod|Mobile/.test(navigator.userAgent); }
|
|
25
|
+
function isStandalone() { return window.matchMedia('(display-mode: standalone)').matches; }
|
|
26
|
+
function osName() { return navigator.userAgentData?.platform || navigator.userAgent.match(/Android/)?.[0] || navigator.platform; }
|
|
27
|
+
function mobilePlatformName() { return isMobile() && (isApple() ? 'iOS' : 'Android'); }
|
|
28
|
+
function mobileVendorName() { return isMobile() && (isApple() ? 'Apple' : 'Android'); }
|
|
29
|
+
function mobileBrowserName() { return isApple() ? 'Safari' : 'Chrome'; }
|
|
30
|
+
function browserName() {
|
|
31
|
+
if (navigator.userAgent.includes("Firefox")) return "Firefox";
|
|
32
|
+
if (navigator.userAgent.includes("Edg")) return "Edge";
|
|
33
|
+
if (navigator.userAgent.includes("Chrome")) return "Chrome";
|
|
34
|
+
if (navigator.userAgent.includes("CriOS")) return "Chrome";
|
|
35
|
+
if (navigator.userAgent.includes("Safari")) return "Safari";
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
userAgent.textContent = JSON.stringify(navigator.userAgent);
|
|
39
|
+
platform.textContent = JSON.stringify(navigator.platform);
|
|
40
|
+
agentDataPlatform.textContent = JSON.stringify(navigator.userAgentData?.platform);
|
|
41
|
+
standalone.textContent = JSON.stringify(window.matchMedia('(display-mode: standalone)').matches);
|
|
42
|
+
WebView.textContent = isWebView();
|
|
43
|
+
Apple.textContent = isApple();
|
|
44
|
+
Mobile.textContent = isMobile();
|
|
45
|
+
os.textContent = osName();
|
|
46
|
+
mobilePlatform.textContent = mobilePlatformName();
|
|
47
|
+
mobileVendor.textContent = mobileVendorName();
|
|
48
|
+
mobileBrowser.textContent = mobileBrowserName();
|
|
49
|
+
browserN.textContent = browserName();
|
|
50
|
+
</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const { Request, Response, URL, clients} = self;
|
|
2
|
+
// Little point in trying to automatically pull this through package.json, as we need a byte change in THIS file to trigger a new worker.
|
|
3
|
+
const serviceVersion = '4.4.1';
|
|
4
|
+
|
|
5
|
+
async function cacheFirst({request, event}) {
|
|
6
|
+
// Handle request from any cache, else fetch and store it in serviceCache.
|
|
7
|
+
|
|
8
|
+
// First try to get the resource from the cache (any version).
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const ignoreSearch = url.pathname === '/'; // Regardless of lat/lng, tags, etc. Hopefully no external map or font resources are on root!
|
|
11
|
+
const responseFromCache = await caches.match(request, {ignoreSearch});
|
|
12
|
+
if (responseFromCache) return responseFromCache;
|
|
13
|
+
|
|
14
|
+
// Next try to get the resource from the network.
|
|
15
|
+
try {
|
|
16
|
+
const responseFromNetwork = await fetch(request);
|
|
17
|
+
if (request.method !== 'GET' || request.cache === 'no-store') return responseFromNetwork; // Cache shouldn't allow anyway.
|
|
18
|
+
// Put clone of response in cache (so that original can be returned.
|
|
19
|
+
// Tell event to keep worker open while we put it, even though we return response immediately.
|
|
20
|
+
const cache = await caches.open(serviceVersion);
|
|
21
|
+
event.waitUntil(cache.put(request, responseFromNetwork.clone()));
|
|
22
|
+
return responseFromNetwork;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(request.url, error);
|
|
25
|
+
// There is nothing we can do, but we must always return a Response object
|
|
26
|
+
return new Response("Network error", {
|
|
27
|
+
status: 408,
|
|
28
|
+
headers: { "Content-Type": "text/plain" },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// EVENT HANDLERS
|
|
34
|
+
|
|
35
|
+
// Install all the resources we need, so that we can work offline.
|
|
36
|
+
// (Users, groups, and media are cached separately in indexeddb.)
|
|
37
|
+
self.addEventListener('install', event => {
|
|
38
|
+
console.log('Install service worker', serviceVersion);
|
|
39
|
+
// IF a service worker is updated, the old service worker is active, and by default,
|
|
40
|
+
// the new one will not be activated until the old one dies. This is our only chance to
|
|
41
|
+
// tell the browser to skipWaiting, and activate the new service worker right away,
|
|
42
|
+
// allowing restarted main code to compare versions and bootstrap itself onto the new main code.
|
|
43
|
+
//
|
|
44
|
+
// However, even though skipWaiting answers a promise, we do NOT want to waitUntil it resolves
|
|
45
|
+
// (as for claim in activate, below), because on Safari, that causes the new worker to activate
|
|
46
|
+
// BEFORE the main script's 'installed' state change fires, thus executing with a non-null
|
|
47
|
+
// serviceWorker.controller, and thus telling the user that there is a download available.
|
|
48
|
+
// Fortunately, leaving out the waitUntil seems to get the expected activation timing. And indeed,
|
|
49
|
+
// the MDN doc for skipWaiting does not use waitUntil either.
|
|
50
|
+
//
|
|
51
|
+
// Alas, there's still a screw case in Safari: The panic button unregisters service workers,
|
|
52
|
+
// but in Safari, the service worker stays running until the page is closed. Even a reload
|
|
53
|
+
// or setting location.href will keep the old service worker around. This means that a reload
|
|
54
|
+
// after panic will cause a harmless but confusing "new version available" popup. Instead,
|
|
55
|
+
// one must manually close the tab after panic.
|
|
56
|
+
let p = self.skipWaiting();
|
|
57
|
+
event.waitUntil(p);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
self.addEventListener('activate', async event => {
|
|
61
|
+
console.log('Activate service worker', serviceVersion);
|
|
62
|
+
// Apply to running clients now, so that first fresh install sees updatefound event.
|
|
63
|
+
// Otherwise, the service worker wouldn't fire until the code NEXT time the page loads after
|
|
64
|
+
// registration, and thus the initial load would not see any updatefound events.
|
|
65
|
+
event.waitUntil(self.clients.claim());
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
self.addEventListener('fetch', event => {
|
|
69
|
+
event.respondWith(cacheFirst({request: event.request, event}));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
async function cacheSource(version, event) { // Cache source in the given version.
|
|
73
|
+
console.log(`service-worker ${serviceVersion} is caching source in cache ${version}.`);
|
|
74
|
+
const cache = await caches.open(version);
|
|
75
|
+
await cache.addAll([
|
|
76
|
+
"/",
|
|
77
|
+
"/index.html", // Just in case anyone is specifying that.
|
|
78
|
+
"favicon.ico",
|
|
79
|
+
"manifest.json",
|
|
80
|
+
"package.json",
|
|
81
|
+
|
|
82
|
+
"javascripts/versions.js",
|
|
83
|
+
"javascripts/main.js",
|
|
84
|
+
"javascripts/display.js",
|
|
85
|
+
"javascripts/map.js",
|
|
86
|
+
"javascripts/hashtags.js",
|
|
87
|
+
"javascripts/s2.js",
|
|
88
|
+
"javascripts/agent.js",
|
|
89
|
+
"javascripts/translations.js",
|
|
90
|
+
"javascripts/service-manager.js",
|
|
91
|
+
"javascripts/p2pWebNetwork.js",
|
|
92
|
+
|
|
93
|
+
"stylesheets/style.css",
|
|
94
|
+
|
|
95
|
+
"images/qr-scan.svg",
|
|
96
|
+
"images/share.svg",
|
|
97
|
+
"images/recenter.svg",
|
|
98
|
+
"images/civil-defense-122.png",
|
|
99
|
+
"images/civil-defense-192.png",
|
|
100
|
+
"images/civil-defense-240.png",
|
|
101
|
+
"images/civil-defense-512.png",
|
|
102
|
+
|
|
103
|
+
"axona-protocol/src/index.js",
|
|
104
|
+
"axona-protocol/src/errors.js",
|
|
105
|
+
"axona-protocol/src/bridgeDirectory.js",
|
|
106
|
+
"axona-protocol/src/contracts/Transport.js",
|
|
107
|
+
"axona-protocol/src/contracts/DHT.js",
|
|
108
|
+
"axona-protocol/src/contracts/BootstrapService.js",
|
|
109
|
+
"axona-protocol/src/identity/index.js",
|
|
110
|
+
"axona-protocol/src/identity/nodeid.js",
|
|
111
|
+
"axona-protocol/src/pow/pow.js",
|
|
112
|
+
"axona-protocol/src/dht/AxonaPeer.js",
|
|
113
|
+
"axona-protocol/src/dht/AxonaDomain.js",
|
|
114
|
+
"axona-protocol/src/dht/DHTNode.js",
|
|
115
|
+
"axona-protocol/src/dht/NeuronNode.js",
|
|
116
|
+
"axona-protocol/src/dht/Synapse.js",
|
|
117
|
+
"axona-protocol/src/dht/Subscription.js",
|
|
118
|
+
"axona-protocol/src/pubsub/AxonaManager.js",
|
|
119
|
+
"axona-protocol/src/pubsub/authorClass.js",
|
|
120
|
+
"axona-protocol/src/pubsub/kill.js",
|
|
121
|
+
"axona-protocol/src/pubsub/touch.js",
|
|
122
|
+
"axona-protocol/src/pubsub/post.js",
|
|
123
|
+
"axona-protocol/src/pubsub/unpub.js",
|
|
124
|
+
"axona-protocol/src/pubsub/envelope.js",
|
|
125
|
+
"axona-protocol/src/pubsub/ed25519.js",
|
|
126
|
+
"axona-protocol/src/pubsub/metrics.js",
|
|
127
|
+
"axona-protocol/src/utils/region-names.js",
|
|
128
|
+
"axona-protocol/src/utils/geo.js",
|
|
129
|
+
"axona-protocol/src/utils/s2.js",
|
|
130
|
+
"axona-protocol/src/utils/hexid.js",
|
|
131
|
+
"axona-protocol/src/transport/handshake.js",
|
|
132
|
+
"axona-protocol/src/transport/handshake-auth.js",
|
|
133
|
+
"axona-protocol/src/transport/wire.js",
|
|
134
|
+
"axona-protocol/src/transport/web/index.js",
|
|
135
|
+
"axona-protocol/src/transport/web/mesh.js",
|
|
136
|
+
"axona-protocol/src/transport/web/mesh-auth.js",
|
|
137
|
+
"axona-protocol/src/transport/web/webrtc.js",
|
|
138
|
+
"axona-protocol/src/transport/web/bridge.js",
|
|
139
|
+
"axona-protocol/src/transport/web/composite.js",
|
|
140
|
+
"axona-protocol/src/transport/sim/index.js",
|
|
141
|
+
"axona-protocol/src/transport/sim/network.js",
|
|
142
|
+
"axona-protocol/src/transport/sim/transport.js",
|
|
143
|
+
"axona-protocol/src/persistence/interface.js",
|
|
144
|
+
"axona-protocol/src/crypto/noble-ed25519.js",
|
|
145
|
+
"axona-protocol/std/index.js",
|
|
146
|
+
"axona-protocol/std/chunk.js",
|
|
147
|
+
|
|
148
|
+
"leaflet/leaflet.css",
|
|
149
|
+
"leaflet/leaflet-src.esm.js",
|
|
150
|
+
"leaflet/images/marker-icon.png",
|
|
151
|
+
"leaflet/images/marker-icon-2x.png",
|
|
152
|
+
"leaflet/images/marker-shadow.png",
|
|
153
|
+
|
|
154
|
+
"pica/pica.min.js",
|
|
155
|
+
"minidenticons/minidenticons.min.js",
|
|
156
|
+
"s2js/s2js.esm.js",
|
|
157
|
+
"bigfloat/esm/index.js",
|
|
158
|
+
"bigfloat/esm/BigFloat32",
|
|
159
|
+
"bigfloat/esm/BigFloat53",
|
|
160
|
+
"bigfloat/esm/BigComplex",
|
|
161
|
+
"bigfloat/esm/BaseInfo32",
|
|
162
|
+
"bigfloat/esm/util",
|
|
163
|
+
|
|
164
|
+
// TODO: the libraries
|
|
165
|
+
].map(name => new Request(name, {cache: 'no-store'}))); // Might not be necessary, but if any browsers insist on their own caching...
|
|
166
|
+
await Promise.all([
|
|
167
|
+
// These are referenced within material web, but missing. Turns out we don't need them,
|
|
168
|
+
// but let's cache empty responses to keep the console cleaner.
|
|
169
|
+
"https://esm.run/npm/lit@3.3.1/+esm",
|
|
170
|
+
"https://esm.run/npm/tslib@2.8.1/+esm",
|
|
171
|
+
"https://esm.run/npm/lit@3.3.1/static-html.js/+esm",
|
|
172
|
+
"https://esm.run/npm/lit@3.3.1/decorators.js/+esm",
|
|
173
|
+
"https://esm.run/npm/lit@3.3.1/directives/style-map.js/+esm",
|
|
174
|
+
"https://esm.run/npm/lit@3.3.1/directives/class-map.js/+esm",
|
|
175
|
+
"https://esm.run/npm/lit@3.3.1/directives/when.js/+esm",
|
|
176
|
+
"https://esm.run/npm/lit@3.3.1/directives/live.js/+esm",
|
|
177
|
+
].map(url => cache.put(new Request(url),
|
|
178
|
+
new Response("", {headers: { "Content-Type": "text/javascript" }}))));
|
|
179
|
+
return version;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
self.addEventListener('message', async event => {
|
|
184
|
+
const {method, params} = event.data;
|
|
185
|
+
switch (method) {
|
|
186
|
+
case 'version':
|
|
187
|
+
event.waitUntil(event.source.postMessage({method: 'version', params: serviceVersion}));
|
|
188
|
+
break;
|
|
189
|
+
case 'cacheSource': // Cache source in the given version.
|
|
190
|
+
event.waitUntil(cacheSource(params, event)
|
|
191
|
+
.then(version => event.source.postMessage({method: 'cached', params: version})));
|
|
192
|
+
break;
|
|
193
|
+
default:
|
|
194
|
+
console.warn(`Unrecognized service worker message: "${event.data}".`);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
self.addEventListener('notificationclick', event => {
|
|
199
|
+
const {notification} = event;
|
|
200
|
+
const {title, body, tag, data} = notification;
|
|
201
|
+
notification.close();
|
|
202
|
+
if (!data) return console.log('no data in notification', notification);
|
|
203
|
+
// This looks to see if the current is already open and focuses if it is. Else opens one.
|
|
204
|
+
event.waitUntil(
|
|
205
|
+
clients
|
|
206
|
+
.matchAll({type: 'window', includeUncontrolled: true})
|
|
207
|
+
.then(async clientList => {
|
|
208
|
+
console.log('notification', {title, body, data, clientList});
|
|
209
|
+
for (const client of clientList) {
|
|
210
|
+
console.log('notification click found client');
|
|
211
|
+
return client.focus().then(() => client.postMessage({method: 'go', params: {subject: tag, ...data}}));
|
|
212
|
+
}
|
|
213
|
+
// Client has been closed. Open one.
|
|
214
|
+
console.log('notification click opening client', data.url);
|
|
215
|
+
return clients.openWindow(data.url);
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
});
|