├── webapp ├── .gitignore ├── src │ ├── index.ts │ └── snapmail-app.ts ├── favicon.ico ├── tsconfig.json ├── webhapp.workdir │ └── web-happ.yaml ├── index.html ├── package.json ├── web-dev-server.config.mjs └── rollup.config.js ├── assets ├── gbs.gif ├── icon.png ├── favicon.ico ├── favicon.png └── snapmail-ui.png ├── electron ├── web │ ├── icon.png │ ├── favicon.ico │ ├── favicon.png │ ├── error.html │ ├── error.css │ ├── splashscreen.html │ ├── splashscreen.css │ ├── networking.html │ └── networking.css ├── bin │ └── .gitignore ├── tsconfig.json ├── .eslintrc.json ├── src │ ├── spawn.ts │ ├── preload.js │ ├── logger.ts │ ├── networkSettings.ts │ ├── userSettings.ts │ ├── holochain.ts │ ├── constants.ts │ └── init.ts ├── afterSignHook.js └── package.json ├── we-applet ├── .gitignore ├── tsconfig.json ├── webhapp.workdir │ └── web-happ.yaml ├── src │ ├── appletServices │ │ ├── blockTypes.ts │ │ ├── search.ts │ │ └── getAttachableInfo.ts │ ├── index.ts │ ├── devtest.ts │ └── createSnapmailApplet.ts ├── index.html ├── web-dev-server.config.mjs ├── package.json └── rollup.config.js ├── scripts ├── ts-bindings.sh ├── other │ ├── ubuntu-deps.sh │ ├── download-dna.sh │ ├── build-ui.sh │ ├── download-dna.bat │ ├── dev-setup.sh │ └── download-zits.sh ├── npm │ ├── clean-npm.sh │ ├── clean.sh │ ├── dist-dna.sh │ └── install-submodules.sh └── update-version-number.sh ├── webcomponents ├── src │ ├── contexts.ts │ ├── viewModel │ │ ├── happDef.ts │ │ ├── snapmail.dvm.ts │ │ ├── snapmail.perspective.ts │ │ └── snapmail.zvm.ts │ ├── index.ts │ ├── elements │ │ ├── snapmail-mail-view.ts │ │ ├── snapmail-mail-write.ts │ │ ├── snapmail-att-view.ts │ │ └── snapmail-filebox.ts │ ├── electron.ts │ ├── constants.ts │ ├── bindings │ │ ├── snapmail.fn.ts │ │ ├── snapmail.proxy.ts │ │ └── snapmail.types.ts │ ├── utils.ts │ ├── global-styles.ts │ └── mail.ts ├── tsconfig.json ├── .gitignore ├── .eslintrc.cjs └── package.json ├── tsconfig.json ├── .github └── workflows │ ├── test-arm64.yml │ ├── release.yml │ ├── test.yml │ ├── build-webhapps.yml │ └── build-electron.yml ├── we.prodtest.config.json ├── .gitignore ├── README.md ├── package.json └── LICENSE.md /webapp/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .dist 3 | out-tsc/ 4 | -------------------------------------------------------------------------------- /webapp/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './snapmail-app'; 2 | -------------------------------------------------------------------------------- /assets/gbs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/assets/gbs.gif -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/assets/favicon.ico -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/webapp/favicon.ico -------------------------------------------------------------------------------- /assets/snapmail-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/assets/snapmail-ui.png -------------------------------------------------------------------------------- /electron/web/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/electron/web/icon.png -------------------------------------------------------------------------------- /electron/bin/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /electron/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/electron/web/favicon.ico -------------------------------------------------------------------------------- /electron/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glassbeadsoftware/snapmail/HEAD/electron/web/favicon.png -------------------------------------------------------------------------------- /we-applet/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | out-tsc 3 | node_modules 4 | *.tsbuildinfo 5 | *.happ 6 | *.dna 7 | *.zip 8 | target 9 | assets/ 10 | *.webhapp 11 | *.hc 12 | -------------------------------------------------------------------------------- /webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["src/**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /scripts/ts-bindings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | zits -i submodules/snapmail-rsm/zomes/snapmail -i submodules/snapmail-rsm/zomes/snapmail_model -o webcomponents/src/bindings/snapmail.ts 6 | -------------------------------------------------------------------------------- /webcomponents/src/contexts.ts: -------------------------------------------------------------------------------- 1 | import {createContext} from "@lit/context"; 2 | import {WeServices} from "@lightningrodlabs/we-applet"; 3 | 4 | export const weClientContext = createContext('we_client'); 5 | 6 | -------------------------------------------------------------------------------- /webapp/webhapp.workdir/web-happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: SnapMail Official 4 | ui: 5 | bundled: "../../artifacts/snapmail-webapp-ui.zip" 6 | happ_manifest: 7 | bundled: "../../artifacts/snapmail.happ" 8 | -------------------------------------------------------------------------------- /scripts/other/ubuntu-deps.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get update 2 | sudo apt-get install --fix-missing -y \ 3 | libgtk2.0-0 \ 4 | libx11-xcb-dev \ 5 | libxtst6 \ 6 | libnss3-dev \ 7 | libgtk-3-dev \ 8 | libxss1 \ 9 | libasound2 10 | -------------------------------------------------------------------------------- /we-applet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "out-tsc", 5 | "rootDir": "./src", 6 | "declaration": false, 7 | }, 8 | "include": ["src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /webcomponents/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "declarationDir": "dist", 5 | "rootDir": "src", 6 | "outDir": "dist" 7 | }, 8 | "include": ["src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /we-applet/webhapp.workdir/web-happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: SnapMail Official We-applet 4 | ui: 5 | bundled: "../../artifacts/snapmail-we_applet-ui.zip" 6 | happ_manifest: 7 | bundled: "../../artifacts/snapmail.happ" 8 | -------------------------------------------------------------------------------- /scripts/other/download-dna.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # DEPRECATED SCRIPT 4 | 5 | cd dna 6 | curl -s https://api.github.com/repos/glassbeadsoftware/snapmail-rsm/releases/latest | grep "browser_download_url.*dna" | cut -d '"' -f 4 | wget -qi - 7 | cd .. 8 | -------------------------------------------------------------------------------- /webcomponents/src/viewModel/happDef.ts: -------------------------------------------------------------------------------- 1 | import {HvmDef} from "@ddd-qc/lit-happ"; 2 | import {SnapmailDvm} from "./snapmail.dvm"; 3 | 4 | export const DEFAULT_SNAPMAIL_DEF: HvmDef = { 5 | id: "snapmail", 6 | dvmDefs: [{ctor: SnapmailDvm, isClonable: false}], 7 | } 8 | -------------------------------------------------------------------------------- /electron/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "resolveJsonModule": true, 6 | "outDir": "out-tsc", 7 | "rootDir": "src", 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /webcomponents/.gitignore: -------------------------------------------------------------------------------- 1 | ## editors 2 | /.idea 3 | /.vscode 4 | 5 | ## system files 6 | .DS_Store 7 | 8 | ## npm 9 | /node_modules/ 10 | /npm-debug.log 11 | 12 | ## testing 13 | /coverage/ 14 | 15 | ## temp folders 16 | /.tmp/ 17 | 18 | # build 19 | /_site/ 20 | /dist/ 21 | /out-tsc/ 22 | /storybook-static/ 23 | .hc -------------------------------------------------------------------------------- /electron/web/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |

Error 500

8 |

Sorry, it's me, not you.

9 |

:(

10 |
11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/other/build-ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo 3 | echo Building Snapmail UI 4 | 5 | # Check pre-conditions 6 | if [ $# != 1 ]; then 7 | echo 1>&2 "$0: Aborting. Missing argument ('dev' or 'prod')" 8 | exit 2 9 | fi 10 | 11 | # Generate Web app 12 | cd webapp 13 | npm run $1 14 | cp -r dist/* ../../electron/web 15 | cd .. 16 | 17 | # Done 18 | cd .. 19 | -------------------------------------------------------------------------------- /scripts/npm/clean-npm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TOP LEVEL 4 | rm -rf node_modules 5 | rm package-lock.json 6 | # WEBCOMPONENTS 7 | rm -rf webcomponents/node_modules 8 | rm webcomponents/package-lock.json 9 | # WE-APPLET 10 | rm -rf we-applet/node_modules/ 11 | rm we-applet/package-lock.json 12 | # WEB-APP 13 | rm -rf webapp/node_modules 14 | rm webapp/package-lock.json 15 | # ELECTRON 16 | rm -rf electron/node_modules 17 | rm electron/package-lock.json 18 | # WE APPLET 19 | rm -rf we-applet/node_modules 20 | rm we-applet/package-lock.json 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "checkJs": false, 7 | "allowJs": true, 8 | "noEmitOnError": true, 9 | "lib": ["es2020", "dom"], 10 | "strict": false, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "experimentalDecorators": true, 14 | "importHelpers": true, 15 | "sourceMap": true, 16 | "skipLibCheck": true, 17 | "inlineSources": true, 18 | "declaration": true 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /electron/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | "no-useless-escape": "off", 20 | "@typescript-eslint/no-empty-function": "off", 21 | "no-empty-function": "off" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /scripts/other/download-dna.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM curl -s https://api.github.com/repos/glassbeadsoftware/snapmail-rsm/releases/latest | grep "browser_download_url.*dna" | cut -d '"' -f 4 4 | 5 | FOR /F "tokens=* USEBACKQ" %%F IN (`"curl -s https://api.github.com/repos/glassbeadsoftware/snapmail-rsm/releases/latest | grep 'browser_download_url.*dna'"`) DO ( 6 | SET version=%%F 7 | ) 8 | REM echo %version% 9 | for /f "tokens=1,2,3 delims= " %%a in ("%version%") do ( 10 | set quoted=%%b 11 | ) 12 | REM echo %quoted% 13 | set link=%quoted:~1,-1% 14 | echo %link% 15 | 16 | bitsadmin /transfer myDownloadJob /download /priority high %link% %cd%\dna\snapmail.dna 17 | -------------------------------------------------------------------------------- /we-applet/src/appletServices/blockTypes.ts: -------------------------------------------------------------------------------- 1 | import {BlockName, BlockType} from "@lightningrodlabs/we-applet"; 2 | // import {wrapPathInSvg} from "@ddd-qc/we-utils"; 3 | // import {mdiFileOutline, mdiFilePlus} from "@mdi/js"; 4 | // import {FilesBlockType} from "@files/app"; 5 | 6 | /** */ 7 | export const blockTypes: Record = {}; 8 | 9 | // blockTypes[FilesBlockType.ImportFile] = { 10 | // label: "Import new file", 11 | // icon_src: wrapPathInSvg(mdiFilePlus), 12 | // view: "applet-view", 13 | // } 14 | // 15 | // blockTypes[FilesBlockType.PickFile] = { 16 | // label: "Pick file", 17 | // icon_src: wrapPathInSvg(mdiFileOutline), 18 | // view: "applet-view", 19 | // } 20 | 21 | -------------------------------------------------------------------------------- /webcomponents/src/index.ts: -------------------------------------------------------------------------------- 1 | /** Import the container media queries mixin */ 2 | import 'cqfill'; 3 | 4 | import './global-styles' 5 | 6 | export * from './bindings/snapmail.types'; 7 | export * from './bindings/snapmail.proxy'; 8 | 9 | export * from './elements/snapmail-att-view'; 10 | export * from './elements/snapmail-contacts'; 11 | export * from './elements/snapmail-filebox'; 12 | export * from './elements/snapmail-mail-view'; 13 | export * from './elements/snapmail-mail-write'; 14 | export * from './elements/snapmail-page'; 15 | 16 | export * from './viewModel/snapmail.dvm'; 17 | export * from './viewModel/snapmail.perspective'; 18 | export * from './viewModel/happDef'; 19 | 20 | export * from './contexts'; 21 | export * from './electron'; 22 | -------------------------------------------------------------------------------- /scripts/other/dev-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install dependencies 4 | sudo apt-get install -y build-essential libssl-dev pkg-config 5 | 6 | # install rust and webassembly 7 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 8 | source $HOME/.cargo/env 9 | rustup target install wasm32-unknown-unknown 10 | 11 | # install lair-keystore and hc 12 | npm run dna-install-deps 13 | # turn zomes into webassembly into zipped dna files 14 | npm run dna-pack 15 | # compile the main conductor, which bundles the dna 16 | cargo build --release 17 | mkdir electron/binaries 18 | cp target/release/acorn-conductor electron/binaries/acorn-conductor 19 | # create a throwaway directory for hosting the source chain 20 | mkdir tmp 21 | # run the conductor 22 | npm run dna-dev 23 | -------------------------------------------------------------------------------- /electron/src/spawn.ts: -------------------------------------------------------------------------------- 1 | import { log } from "./logger"; 2 | import { CURRENT_DIR } from "./constants"; 3 | import { spawnSync } from "child_process"; 4 | 5 | 6 | /** */ 7 | export function pingBootstrap(url:string): boolean { 8 | const bin = "ping"; 9 | const args: any[] = ['-n', 1, url.substring(8)]; 10 | 11 | /** Spawn "holochain" subprocess */ 12 | log('info', 'Spawning ' + bin + ' (url: ' + url + ')'); 13 | const proc = spawnSync(bin, args, { 14 | cwd: CURRENT_DIR, 15 | //detached: false, 16 | timeout: 5000, 17 | encoding: 'utf8', //stdio: 'pipe', 18 | env: { 19 | ... process.env, 20 | RUST_BACKTRACE: "1", 21 | }, 22 | }); 23 | log('info', 'ping result: ' + proc.stdout); 24 | return proc.status == null || proc.status == 0; 25 | } 26 | -------------------------------------------------------------------------------- /scripts/npm/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TOP LEVEL 4 | rm -rf bin 5 | rm -rf artifacts 6 | rm .hc_live* 7 | # WEBCOMPONENTS 8 | rm -rf webcomponents/dist 9 | rm webcomponents/tsconfig.tsbuildinfo 10 | # WE-APPLET 11 | rm -rf we-applet/.rollup.cache/ 12 | rm -rf we-applet/out-tsc/ 13 | rm we-applet/dist/*.js 14 | rm we-applet/dist/*.map 15 | rm we-applet/.hc* 16 | rm we-applet/tsconfig.tsbuildinfo 17 | rm we-applet/webhapp.workdir/snapmail-we_applet.webhapp 18 | # WEB-APP 19 | rm -rf webapp/dist 20 | rm -rf webapp/out-tsc 21 | rm webapp/tsconfig.tsbuildinfo 22 | # ELECTRON 23 | rm -rf electron/out-builder 24 | rm -rf electron/out-tsc 25 | rm electron/bin/* 26 | rm electron/web/*.js 27 | rm electron/tsconfig.tsbuildinfo 28 | # WE APPLET 29 | rm -rf we-applet/out-tsc 30 | rm we-applet/.hc_live* 31 | -------------------------------------------------------------------------------- /webcomponents/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "project": ['./tsconfig.json'], 14 | "tsconfigRootDir": __dirname, 15 | "ecmaVersion": 12, 16 | "sourceType": "module" 17 | }, 18 | "ignorePatterns": ["src/bindings/*.ts", "dist/*"], 19 | "plugins": [ 20 | "@typescript-eslint" 21 | ], 22 | "rules": { 23 | "@typescript-eslint/no-this-alias": "off", 24 | "@typescript-eslint/no-empty-function": "off", 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /electron/web/error.css: -------------------------------------------------------------------------------- 1 | html { box-sizing: border-box; } 2 | 3 | *, 4 | *::before, 5 | *::after { box-sizing: inherit; } 6 | 7 | body * { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | body { 13 | font: normal 100%/1.15 "Merriweather", serif; 14 | background-color: #7e6960; 15 | color: #fff; 16 | } 17 | 18 | .wrapper { 19 | position: relative; 20 | max-width: 1298px; 21 | height: auto; 22 | margin: 2em auto 0 auto; 23 | } 24 | 25 | .box { 26 | max-width: 70%; 27 | min-height: auto; 28 | margin: 0 auto; 29 | padding: 1em 1em; 30 | text-align: center; 31 | } 32 | 33 | h1, p:not(:last-of-type) { text-shadow: 0 0 6px #737965; } 34 | 35 | h1 { 36 | margin: 0 0 1rem 0; 37 | font-size: 8em; 38 | } 39 | 40 | p { 41 | margin-bottom: 0.5em; 42 | font-size: 3em; 43 | } 44 | 45 | p:first-of-type { margin-top: 1em; } 46 | 47 | p img { vertical-align: bottom; } 48 | -------------------------------------------------------------------------------- /scripts/npm/dist-dna.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Script for copying holochain-runner binary to electron bin folder (used for distributing electron app) 5 | 6 | echo Executing \"$0\". 7 | 8 | # Check pre-conditions 9 | if [ $# != 1 ]; then 10 | echo 1>&2 "$0: Aborting. Missing argument: bin folder path" 11 | exit 2 12 | fi 13 | 14 | # Compile the WASM 15 | cargo build --release --target wasm32-unknown-unknown --manifest-path submodules/snapmail-rsm/Cargo.toml 16 | 17 | # Pack DNAs 18 | #mkdir -p artifacts 19 | $1/hc dna pack submodules/snapmail-rsm/workdir -o submodules/snapmail-rsm/workdir/snapmail.dna 20 | $1/hc app pack submodules/snapmail-rsm/workdir -o submodules/snapmail-rsm/workdir/snapmail.happ 21 | cp submodules/snapmail-rsm/workdir/snapmail.dna artifacts/snapmail.dna 22 | cp submodules/snapmail-rsm/workdir/snapmail.happ artifacts/snapmail.happ 23 | cp artifacts/snapmail.happ electron/bin/snapmail.happ 24 | -------------------------------------------------------------------------------- /scripts/npm/install-submodules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Script for downloading prebuilt submodule dependecies 6 | 7 | echo Executing \"$0\". 8 | 9 | # Check pre-conditions 10 | if [ $# != 1 ]; then 11 | echo 1>&2 "$0: Aborting. Missing argument: holochain version" 12 | exit 2 13 | fi 14 | 15 | hcversion=$1 16 | echo for holochain version $hcversion 17 | 18 | if [ "$hcversion" == "hc" ] || [ "$hcversion" == "" ] ; then 19 | echo Missing \"hc-version\" field in \"package.json\". 20 | exit 1 21 | fi 22 | echo \* Create 'submodules' folder 23 | rm -rf submodules 24 | mkdir submodules 25 | 26 | 27 | cd submodules 28 | echo \* Download latest DNA source code 29 | git clone -b hc-$hcversion https://github.com/glassbeadsoftware/snapmail-rsm 30 | 31 | echo \* Download latest install scripts 32 | git clone --depth 1 https://github.com/ddd-mtl/hc-prebuilt 33 | 34 | cd .. 35 | 36 | 37 | echo 38 | echo \* Done 39 | -------------------------------------------------------------------------------- /we-applet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Snapmail we-applet 12 | 13 | 14 | 15 | 19 | 20 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /electron/src/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | 3 | const BUILD_MODE = process.env.BUILD_MODE? process.env.BUILD_MODE : 'prod'; 4 | 5 | console.log("preload BUILD_MODE = " + JSON.stringify(process.env.BUILD_MODE)); 6 | 7 | const electronBridge = { 8 | send: (channel) => {ipcRenderer.send(channel)}, 9 | on: (channel, listener) => {ipcRenderer.on(channel, listener)}, 10 | newMailSync: (title, body) => { return ipcRenderer.sendSync('newMailSync', title, body); }, 11 | startingInfo: (startingHandle, dnaHash) => { return ipcRenderer.sendSync('startingInfo', startingHandle, dnaHash); }, 12 | newCountAsync: (newCount) => { return ipcRenderer.send('newCountAsync', newCount); }, 13 | BUILD_MODE, 14 | versions: { 15 | node: process.versions.node, 16 | chrome: process.versions.chrome, 17 | electron: process.versions.electron, 18 | } 19 | }; 20 | 21 | 22 | contextBridge.exposeInMainWorld('electronBridge', electronBridge) 23 | -------------------------------------------------------------------------------- /electron/src/logger.ts: -------------------------------------------------------------------------------- 1 | import * as electronLogger from "electron-log"; 2 | import {IS_DEV} from "./constants"; 3 | 4 | export function log(level:any, message:any) { 5 | electronLogger[level](message); 6 | } 7 | //module.exports.log = log; 8 | module.exports.logger = electronLogger; 9 | 10 | 11 | /** SET UP LOGGING */ 12 | 13 | electronLogger.transports.console.format = '[{h}:{i}:{s}][{level}] {text}'; 14 | electronLogger.transports.file.format = '[{h}:{i}:{s}][{level}] {text}'; 15 | //electronLogger.info('%cRed text. %cGreen text', 'color: red', 'color: green') 16 | 17 | log('info', ""); 18 | log('info', ""); 19 | log('info', "APP STARTED"); 20 | log('info', ""); 21 | 22 | if (IS_DEV) { 23 | electronLogger.transports.file.level = 'error'; // minimize disk writes ; Use console instead 24 | electronLogger.transports.console.level = 'debug'; 25 | log('debug', "DEBUG MODE ENABLED\n"); 26 | } else { 27 | //log('info', "DEBUG MODE DISABLED"); 28 | electronLogger.transports.console.level = 'info'; 29 | electronLogger.transports.file.level = 'info'; 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/test-arm64.yml: -------------------------------------------------------------------------------- 1 | name: Build arm64 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | build-webhapps: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: uraimo/run-on-arch-action@v2.5.0 11 | name: Run commands 12 | id: runcmd 13 | with: 14 | arch: aarch64 15 | distro: ubuntu22.04 16 | 17 | # Not required, but speeds up builds by storing container images in 18 | # a GitHub package registry. 19 | githubToken: ${{ github.token }} 20 | 21 | install: | 22 | apt-get update -q -y 23 | apt-get install -q -y git 24 | apt-get install -q -y npm 25 | apt-get install -q -y cargo 26 | apt-get install -q -y wget 27 | 28 | # 29 | run: | 30 | npm install 31 | npm run install:submodules 32 | npm run install:rust 33 | npm run install:hc 34 | npm run install:hash-zome 35 | npm run build:happ 36 | -------------------------------------------------------------------------------- /electron/afterSignHook.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const fs = require('fs') 3 | const path = require('path') 4 | const electronNotarize = require('electron-notarize') 5 | 6 | module.exports = async function (params) { 7 | if (process.platform !== 'darwin') { 8 | return 9 | } 10 | 11 | console.log('afterSign hook triggered', params) 12 | 13 | const appId = 'com.glassbead.snapmail' 14 | 15 | const appPath = path.join( 16 | params.appOutDir, 17 | `${params.packager.appInfo.productFilename}.app` 18 | ) 19 | if (!fs.existsSync(appPath)) { 20 | console.log('appPath file not found: ' + appPath) 21 | console.log('skip') 22 | return 23 | } 24 | 25 | console.log(`Notarizing ${appId} found at ${appPath}`) 26 | 27 | try { 28 | await electronNotarize.notarize({ 29 | appBundleId: appId, 30 | appPath: appPath, 31 | appleId: process.env.APPLE_ID_EMAIL, 32 | appleIdPassword: process.env.APPLE_ID_PASSWORD, 33 | }) 34 | } catch (error) { 35 | console.error(error) 36 | } 37 | 38 | console.log(`Done notarizing ${appId}`) 39 | } 40 | -------------------------------------------------------------------------------- /scripts/other/download-zits.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | platform="pc-windows-msvc" 4 | 5 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then 6 | platform="unknown-linux-gnu" 7 | elif [[ "$OSTYPE" == "darwin"* ]]; then 8 | platform="apple-darwin" 9 | #elif [[ "$OSTYPE" == "cygwin" ]]; then 10 | # # POSIX compatibility layer and Linux environment emulation for Windows 11 | #elif [[ "$OSTYPE" == "msys" ]]; then 12 | # # Lightweight shell and GNU utilities compiled for Windows (part of MinGW) 13 | #elif [[ "$OSTYPE" == "win32" ]]; then 14 | # # I'm not sure this can happen. 15 | #elif [[ "$OSTYPE" == "freebsd"* ]]; then 16 | # platform="linux" 17 | #else 18 | # Unknown. 19 | fi 20 | 21 | echo 22 | echo OSTYPE is: $OSTYPE 23 | echo platform : $platform 24 | 25 | tarfile=zits-x86_64-$platform.tar.gz 26 | 27 | if test -f "artifacts/$tarfile"; then 28 | echo zits file found 29 | exit 0 30 | fi 31 | 32 | value=`curl -s https://api.github.com/repos/ddd-mtl/zits/releases/tags/v1.6.1 | grep "/$tarfile" | cut -d '"' -f 4` 33 | echo Donwloading \'$value\' 34 | wget --directory-prefix=artifacts $value 35 | 36 | tar -xvzf artifacts/$tarfile -C ./artifacts 37 | echo 38 | echo ls ./artifacts 39 | ls artifacts 40 | 41 | -------------------------------------------------------------------------------- /electron/src/networkSettings.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import fs from "fs"; 3 | import {log} from "./logger"; 4 | import {NETWORK_SETTINGS_FILENAME} from "./constants"; 5 | 6 | 7 | /** */ 8 | export interface NetworkSettings { 9 | proxyUrl: string, 10 | bootstrapUrl: string, 11 | canMdns: boolean, 12 | canProxy: boolean, 13 | } 14 | 15 | 16 | /** */ 17 | export function loadNetworkConfig(sessionDataPath: string): NetworkSettings | undefined { 18 | let settings = undefined; 19 | const configFilePath = path.join(sessionDataPath, NETWORK_SETTINGS_FILENAME); 20 | try { 21 | settings = JSON.parse(fs.readFileSync(configFilePath).toString()); 22 | } catch(error) { 23 | log("warn", "Network config file not found ; " + configFilePath) 24 | return undefined; 25 | } 26 | return settings; 27 | } 28 | 29 | 30 | /** */ 31 | export function saveNetworkConfig(sessionDataPath: string, networkSettings: NetworkSettings, ): boolean { 32 | 33 | const filepath = path.join(sessionDataPath, NETWORK_SETTINGS_FILENAME); 34 | try { 35 | fs.writeFileSync(filepath, JSON.stringify(networkSettings)); 36 | } catch (err) { 37 | log('error','Writing to file failed: ' + err); 38 | return false; 39 | } 40 | return true; 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release x64 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.* 7 | 8 | jobs: 9 | call-build-electron: 10 | uses: ./.github/workflows/build-electron.yml 11 | secrets: inherit 12 | with: 13 | must_sign: true 14 | 15 | # upload the artifacts 16 | upload-assets: 17 | needs: call-build-electron 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | # Download previously uploaded artifacts 22 | - uses: actions/download-artifact@v3 23 | with: 24 | name: all-happ-artifact 25 | path: artifacts 26 | # Display artifacts folder 27 | - name: Display artifacts folder 28 | run: ls 29 | working-directory: artifacts 30 | # upload all artifacts 31 | - name: upload binary (ubuntu only) 32 | if: ${{ runner.os == 'Linux' }} 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | working-directory: artifacts 36 | run: | 37 | rm -f *.blockmap 38 | echo snapmail* 39 | gh release upload "${GITHUB_REF#refs/tags/}" snapmail* --clobber 40 | echo Snapmail* 41 | gh release upload "${GITHUB_REF#refs/tags/}" Snapmail* --clobber 42 | -------------------------------------------------------------------------------- /webcomponents/src/viewModel/snapmail.dvm.ts: -------------------------------------------------------------------------------- 1 | import { DnaViewModel, ZvmDef } from "@ddd-qc/lit-happ"; 2 | import {AppSignalCb} from "@holochain/client"; 3 | import {SnapmailZvm} from "./snapmail.zvm"; 4 | import {AppSignal} from "@holochain/client/lib/api/app/types"; 5 | 6 | 7 | /** 8 | * TODO: Make a "passthrough" DVM generator in dna-client based on ZVM_DEFS 9 | */ 10 | export class SnapmailDvm extends DnaViewModel { 11 | 12 | /** -- DnaViewModel Interface -- */ 13 | 14 | static readonly DEFAULT_BASE_ROLE_NAME = "rSnapmail"; 15 | static readonly ZVM_DEFS: ZvmDef[] = [SnapmailZvm]; 16 | 17 | readonly signalHandler?: AppSignalCb = this.handleSignal; 18 | _hostHandler?: AppSignalCb; 19 | 20 | 21 | /** QoL Helpers */ 22 | get snapmailZvm(): SnapmailZvm {return this.getZomeViewModel(SnapmailZvm.DEFAULT_ZOME_NAME) as SnapmailZvm} 23 | 24 | 25 | /** -- ViewModel Interface -- */ 26 | 27 | // TODO 28 | protected hasChanged(): boolean {return true} 29 | 30 | // TODO 31 | get perspective(): void {return} 32 | 33 | 34 | /** Forward signal to runtime defined host callback */ 35 | handleSignal(signal: AppSignal): void { 36 | //console.log("SnapmailDvm.handleSignal()", this._hostHandler); 37 | if (this._hostHandler) { 38 | this._hostHandler(signal) 39 | } 40 | } 41 | 42 | /** */ 43 | setSignalHandler(cb: AppSignalCb): void { 44 | this._hostHandler = cb; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /webcomponents/src/elements/snapmail-mail-view.ts: -------------------------------------------------------------------------------- 1 | import {css, html, LitElement} from "lit"; 2 | import { state, property, customElement } from "lit/decorators.js"; 3 | import {ScopedElementsMixin} from "@open-wc/scoped-elements"; 4 | import {MailItem} from "../bindings/snapmail.types"; 5 | import {into_mailText} from "../mail"; 6 | import {UsernameMap} from "../viewModel/snapmail.perspective"; 7 | 8 | 9 | /** */ 10 | @customElement("snapmail-mail-view") 11 | export class SnapmailMailView extends ScopedElementsMixin(LitElement) { 12 | 13 | @property({type: Object}) 14 | inMailItem: MailItem; 15 | 16 | @property({type: Object}) 17 | usernameMap: UsernameMap; 18 | 19 | /** */ 20 | render() { 21 | console.log(".render()", this.inMailItem); 22 | 23 | let mailText = ''; 24 | if (this.inMailItem && this.usernameMap) { 25 | mailText = into_mailText(this.usernameMap, this.inMailItem); 26 | } 27 | 28 | /** */ 29 | return html` 30 | 34 | 35 | 36 | `; 37 | } 38 | 39 | /** */ 40 | static get styles() { 41 | return [ 42 | css` 43 | `]; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /we.prodtest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "Batiment 7", 5 | "networkSeed": "test", 6 | "icon": { 7 | "type": "filesystem", 8 | "path": "S:\\media\\images\\b7.jpg" 9 | }, 10 | "creatingAgent": { 11 | "agentNum": 1, 12 | "agentProfile": { 13 | "nickname": "Alex", 14 | "avatar": { 15 | "type": "filesystem", 16 | "path": "S:\\media\\images\\500x_3327649797_ba00fef6e1_b.jpg" 17 | } 18 | } 19 | }, 20 | "joiningAgents": [ 21 | { 22 | "agentNum": 2, 23 | "agentProfile": { 24 | "nickname": "Billy", 25 | "avatar": { 26 | "type": "filesystem", 27 | "path": "S:\\media\\images\\308954_10150302956291669_246474565_n.jpg" 28 | } 29 | } 30 | } 31 | ], 32 | "applets": [ 33 | { 34 | "name": "Snapmail", 35 | "instanceName": "Snapmail", 36 | "registeringAgent": 1, 37 | "joiningAgents": [2] 38 | } 39 | ] 40 | } 41 | ], 42 | "applets": [ 43 | { 44 | "name": "Snapmail", 45 | "subtitle": "P2P email\n", 46 | "description": "...", 47 | "icon": { 48 | "type": "filesystem", 49 | "path": "./assets/icon.png" 50 | }, 51 | "source": { 52 | "type": "filesystem", 53 | "path": "./artifacts/snapmail-we_applet.webhapp" 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /electron/web/splashscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Snapmail 5 | 6 | 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 | version 0.3.1 18 |
19 |
20 |
21 | Setting up Holochain... 22 |
23 |
24 | A minimalist messaging application on holochain 25 |
26 |
27 | © 2021-2023 Glass Bead Software, LLC. 28 |
29 | Licensed under the Cryptographic Autonomy License v1.0. 30 |
31 |
32 |
33 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /scripts/update-version-number.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script for updating version number across the code base 4 | 5 | # Check pre-conditions 6 | if [ $# != 1 ]; then 7 | echo 1>&2 "$0: Aborting. Missing argument: new version number" 8 | exit 2 9 | fi 10 | 11 | 12 | # Change electron/package.json 13 | OLD_VER=`awk -F ":" '/"version"/ {print $2}' ./electron/package.json | sed 's/"//g' | sed 's/,//g' | sed 's/ //g'` 14 | echo "./electron/package.json $OLD_VER -> $1" 15 | sed -i "s/\"version\": \"$OLD_VER\"/\"version\": \"$1\"/" ./electron/package.json 16 | 17 | # Change we-applet/package.json 18 | OLD_VER=`awk -F ":" '/"version"/ {print $2}' ./we-applet/package.json | sed 's/"//g' | sed 's/,//g' | sed 's/ //g'` 19 | echo "./we-applet/package.json $OLD_VER -> $1" 20 | sed -i "s/\"version\": \"$OLD_VER\"/\"version\": \"$1\"/" ./we-applet/package.json 21 | 22 | # Change webcomponents/package.json 23 | OLD_VER=`awk -F ":" '/"version"/ {print $2}' ./webcomponents/package.json | sed 's/"//g' | sed 's/,//g' | sed 's/ //g'` 24 | echo "./webcomponents/package.json $OLD_VER -> $1" 25 | sed -i "s/\"version\": \"$OLD_VER\"/\"version\": \"$1\"/" ./webcomponents/package.json 26 | 27 | # Change webapp/package.json 28 | OLD_VER=`awk -F ":" '/"version"/ {print $2}' ./webapp/package.json | sed 's/"//g' | sed 's/,//g' | sed 's/ //g'` 29 | echo "./webapp/package.json $OLD_VER -> $1" 30 | sed -i "s/\"version\": \"$OLD_VER\"/\"version\": \"$1\"/" ./webapp/package.json 31 | 32 | 33 | # Change electron/web/splashscreen.html 34 | LINE=`echo version $1` 35 | echo $LINE 36 | sed -i "17s/.*/ ${LINE}/" electron/web/splashscreen.html 37 | -------------------------------------------------------------------------------- /webcomponents/src/electron.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | export interface IpcRendererApi { 6 | send: (channel: string) => void, 7 | on: (channel: string, listener: (event: any, ...args: any[]) => void) => this; 8 | newMailSync: (title: string, body: string) => unknown, 9 | startingInfo: (startingHandle, dnaHash) => string, 10 | newCountAsync: (newCount) => unknown, 11 | BUILD_MODE: string, 12 | versions: { 13 | node: string, 14 | chrome: string, 15 | electron: string, 16 | } 17 | } 18 | 19 | 20 | /** APP SETUP */ 21 | 22 | export let BUILD_MODE: string; 23 | export const MY_ELECTRON_API = 'electronBridge' in window? window.electronBridge as IpcRendererApi : undefined; 24 | export const IS_ELECTRON = typeof MY_ELECTRON_API !== 'undefined' 25 | if (MY_ELECTRON_API) { 26 | BUILD_MODE = MY_ELECTRON_API.BUILD_MODE; 27 | } else { 28 | try { 29 | BUILD_MODE = process.env.BUILD_MODE; 30 | } catch (e) { 31 | console.log("BUILD_MODE not defined. Defaulting to 'prod'"); 32 | BUILD_MODE = 'prod'; 33 | } 34 | } 35 | 36 | //export const IS_DEV = BUILD_MODE === 'dev'; 37 | 38 | console.log(" BUILD_MODE =", BUILD_MODE) 39 | console.log(" IS_ELECTRON =", IS_ELECTRON); 40 | 41 | // /** Remove console.log() in PROD */ 42 | // if (BUILD_MODE === 'prod') { 43 | // console.log("console.log() disabled"); 44 | // console.log = () => {}; 45 | // } 46 | 47 | 48 | /** */ 49 | export function updateTray(newCount: number): void { 50 | if (!MY_ELECTRON_API) { 51 | return; 52 | } 53 | const reply = MY_ELECTRON_API.newCountAsync(newCount); 54 | console.log({reply}); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test-workflow x64 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | must_sign: 7 | required: false 8 | type: boolean 9 | default: false 10 | 11 | jobs: 12 | call-build-electron: 13 | uses: ./.github/workflows/build-electron.yml 14 | secrets: inherit 15 | with: 16 | must_sign: ${{ inputs.must_sign }} 17 | 18 | # upload the artifacts 19 | upload-assets: 20 | needs: call-build-electron 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | # Download previously uploaded artifacts 25 | - uses: actions/download-artifact@v3 26 | with: 27 | name: all-happ-artifact 28 | path: artifacts 29 | # Display artifacts folder 30 | - name: Display artifacts folder 31 | run: ls 32 | working-directory: artifacts 33 | # Delete .exe release asset 34 | - name: Delete .exe release asset 35 | continue-on-error: true 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | working-directory: artifacts 39 | run: | 40 | gh release delete-asset "workflow-test" Snapmail*.exe -y 41 | # upload all artifacts 42 | - name: upload all artifacts 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | working-directory: artifacts 46 | run: | 47 | rm -f *.blockmap 48 | echo snapmail* 49 | gh release upload "workflow-test" snapmail* --clobber 50 | echo Snapmail* 51 | gh release upload "workflow-test" Snapmail* --clobber 52 | -------------------------------------------------------------------------------- /we-applet/src/index.ts: -------------------------------------------------------------------------------- 1 | import {DevTestNames, setup} from "@ddd-qc/we-utils"; 2 | import {AppletServices} from "@lightningrodlabs/we-applet"; 3 | import {blockTypes} from "./appletServices/blockTypes"; 4 | import {devtestNames, setupSnapmailEntryView} from "./devtest"; 5 | import {getAttachableInfo} from "./appletServices/getAttachableInfo"; 6 | import {search} from "./appletServices/search"; 7 | import {createSnapmailApplet} from "./createSnapmailApplet"; 8 | import {SnapmailEntryType} from "@snapmail/elements"; 9 | 10 | 11 | 12 | 13 | /** */ 14 | export async function setupSnapmailApplet() { 15 | /** Determine appletView */ 16 | let APPLET_VIEW = "main"; 17 | try { 18 | APPLET_VIEW = process.env.APPLET_VIEW; 19 | //console.log(`HAPP_ENV defined by process.ENV: "${happEnv}"`); 20 | } catch (e) { 21 | } 22 | console.log("Snapmail we-applet setup() APPLET_VIEW", APPLET_VIEW); 23 | switch(APPLET_VIEW) { 24 | /** Entry views */ 25 | case SnapmailEntryType.InMail: 26 | case SnapmailEntryType.OutMail: return setupSnapmailEntryView(); 27 | ///** Block views */ 28 | //case FilesBlockType.ViewMail: 29 | //case FilesBlockType.CreateMail: 30 | //case FilesBlockType.ViewInbox: 31 | /** Main View */ 32 | case "main": 33 | default: return setupSnapmailsMainView(); 34 | } 35 | } 36 | 37 | 38 | /** */ 39 | async function setupSnapmailsMainView() { 40 | const appletServices: AppletServices = { 41 | attachmentTypes: async (_appletClient) => ({}), 42 | getAttachableInfo, 43 | blockTypes, 44 | search, 45 | }; 46 | 47 | return setup(appletServices, createSnapmailApplet, devtestNames); 48 | } 49 | 50 | 51 | export default setupSnapmailApplet; 52 | -------------------------------------------------------------------------------- /webcomponents/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@snapmail/elements", 3 | "version": "0.4.0", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint --ext .ts .", 9 | "tsc": "tsc", 10 | "build": "rm -rf dist && tsc", 11 | "build:watch": "tsc --watch --preserveWatchOutput", 12 | "build:old": "tsc && npm run analyze -- --exclude dist", 13 | "analyze": "cem analyze --litelement" 14 | }, 15 | "type": "module", 16 | "devDependencies": { 17 | "@custom-elements-manifest/analyzer": "^0.4.17", 18 | "@typescript-eslint/eslint-plugin": "^5.39.0", 19 | "@typescript-eslint/parser": "^5.39.0", 20 | "@rollup/plugin-typescript": "^8.2.5", 21 | "@web/dev-server": "0.0.13", 22 | "eslint": "^8.24.0", 23 | "eslint-plugin-import": "^2.26.0", 24 | "npm-run-all": "^4.1.5" 25 | }, 26 | "dependencies": { 27 | "@vaadin/button": "24.3.3", 28 | "@vaadin/combo-box": "24.3.3", 29 | "@vaadin/dialog": "24.3.3", 30 | "@vaadin/icon": "24.3.3", 31 | "@vaadin/icons": "24.3.3", 32 | "@vaadin/input-container": "24.3.3", 33 | "@vaadin/grid": "24.3.3", 34 | "@vaadin/list-box": "24.3.3", 35 | "@vaadin/menu-bar": "24.3.3", 36 | "@vaadin/multi-select-combo-box": "24.3.3", 37 | "@vaadin/notification": "24.3.3", 38 | "@vaadin/progress-bar": "24.3.3", 39 | "@vaadin/select": "24.3.3", 40 | "@vaadin/split-layout": "24.3.3", 41 | "@vaadin/horizontal-layout": "24.3.3", 42 | "@vaadin/vertical-layout": "24.3.3", 43 | "@vaadin/text-field": "24.3.3", 44 | "@vaadin/text-area": "24.3.3", 45 | "@vaadin/upload": "24.3.3", 46 | "cqfill": "^0.6.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /webcomponents/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const redDot = String.fromCodePoint(0x1F534); 2 | export const greenDot = String.fromCodePoint(0x1F7E2); 3 | //const blueDot = String.fromCodePoint(0x1F535); 4 | export const whiteDot = String.fromCodePoint(0x26AA); 5 | 6 | export const redStopEmoji = String.fromCodePoint(0x1F6D1); 7 | export const greenCheckEmoji = String.fromCodePoint(0x2714); 8 | 9 | export const hourGlassEmoji = String.fromCodePoint(0x23F3); 10 | 11 | 12 | /* Map of (name -> [agentId]) */ 13 | export const SYSTEM_GROUP_LIST = ['All', 'new...']; 14 | 15 | 16 | 17 | /** Styles for vaadin-grid */ 18 | export const stylesTemplate = document.createElement('template'); 19 | stylesTemplate.innerHTML = ` 20 | 79 | `; 80 | -------------------------------------------------------------------------------- /webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snapmail", 3 | "version": "0.4.0", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint --ext .ts .", 9 | "build": "rm -rf dist && tsc", 10 | "build:watch": "tsc -w --incremental --preserveWatchOutput", 11 | "dist": "npm run build && rollup -c rollup.config.js", 12 | "start:ui-new": "concurrently --names tsc,dev-server \"npm run build:watch\" \"web-dev-server --config ./web-dev-server.config.mjs\"", 13 | "start:ui": "concurrently --names tsc,dev-server \"npm run build:watch\" \"web-dev-server --config ./web-dev-server.config.mjs\"" 14 | }, 15 | 16 | "devDependencies": { 17 | "@babel/preset-env": "^7.15.0", 18 | "@rollup/plugin-babel": "^5.3.0", 19 | "@rollup/plugin-commonjs": "18.0.0", 20 | "@rollup/plugin-node-resolve": "^13.0.4", 21 | "@rollup/plugin-replace": "^3.0.0", 22 | "@rollup/plugin-typescript": "^8.2.5", 23 | "@web/dev-server": "^0.1.21", 24 | "@web/rollup-plugin-html": "^1.11.0", 25 | "@web/rollup-plugin-import-meta-assets": "^1.0.7", 26 | "babel-plugin-template-html-minifier": "^4.1.0", 27 | "concurrently": "^5.3.0", 28 | "deepmerge": "^4.2.2", 29 | "exits": "^2.0.1", 30 | "path-exists-cli": "^2.0.0", 31 | "rollup": "^2.56.2", 32 | "rollup-plugin-node-builtins": "^2.1.2", 33 | "rollup-plugin-node-globals": "^1.4.0", 34 | "rollup-plugin-terser": "^7.0.2", 35 | "rollup-plugin-workbox": "^6.2.0", 36 | "rollup-plugin-copy": "^3.4.0", 37 | "run-singleton-cli": "^0.0.5", 38 | "@typescript-eslint/eslint-plugin": "^5.39.0", 39 | "@typescript-eslint/parser": "^5.39.0", 40 | "eslint": "^8.24.0", 41 | "eslint-plugin-import": "^2.26.0" 42 | }, 43 | "dependencies": { 44 | "@snapmail/elements": "file:../webcomponents" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webcomponents/src/bindings/snapmail.fn.ts: -------------------------------------------------------------------------------- 1 | /* This file is generated by zits. Do not edit manually */ 2 | 3 | import {ZomeName, FunctionName} from '@holochain/client'; 4 | 5 | 6 | /** Array of all zome function names in "snapmail" */ 7 | export const snapmailFunctionNames: FunctionName[] = [ 8 | "entry_defs", 9 | "get_zome_info", 10 | "get_dna_info", 11 | "init_caps", 12 | 13 | 14 | 15 | "receive_dm", 16 | "find_manifest", 17 | "get_all_manifests", 18 | "get_chunk", 19 | "get_manifest", 20 | "get_missing_attachments", 21 | "get_missing_chunks", 22 | "write_chunk", 23 | "write_manifest", 24 | "get_enc_key", 25 | "get_my_enc_key", 26 | "test_encryption", 27 | "find_agent", 28 | "get_all_handles", 29 | "get_handle", 30 | "get_my_handle", 31 | "get_my_handle_history", 32 | "ping_agent", 33 | "create_empty_handle", 34 | "set_handle", 35 | "acknowledge_mail", 36 | "commit_pending_ack", 37 | "commit_confirmation", 38 | "check_ack_inbox", 39 | "check_mail_inbox", 40 | "delete_mail", 41 | "get_all_mails", 42 | "get_all_unacknowledged_inmails", 43 | "get_mail", 44 | "get_outmail_state", 45 | "get_outmail_delivery_state", 46 | "has_ack_been_delivered", 47 | "is_outack_sent", 48 | "request_acks", 49 | "resend_outacks", 50 | "resend_outmails", 51 | "commit_inmail", 52 | "commit_pending_mail", 53 | "send_mail",]; 54 | 55 | 56 | /** Generate tuple array of function names with given zomeName */ 57 | export function generateSnapmailZomeFunctionsArray(zomeName: ZomeName): [ZomeName, FunctionName][] { 58 | const fns: [ZomeName, FunctionName][] = []; 59 | for (const fn of snapmailFunctionNames) { 60 | fns.push([zomeName, fn]); 61 | } 62 | return fns; 63 | } 64 | 65 | 66 | /** Tuple array of all zome function names with default zome name "snapmail" */ 67 | export const snapmailZomeFunctions: [ZomeName, FunctionName][] = generateSnapmailZomeFunctionsArray("snapmail"); 68 | -------------------------------------------------------------------------------- /electron/web/splashscreen.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | } 5 | 6 | 7 | html { 8 | overflow: hidden; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | /* to match the main window */ 15 | background-color: #f7f5f3; 16 | } 17 | 18 | .splash-wrapper { 19 | display: flex; 20 | flex-direction: row; 21 | } 22 | 23 | .splash-image-wrapper { 24 | background-color: #f7f5f3; 25 | background-image: url('icon.png'); 26 | background-repeat: no-repeat; 27 | background-size: contain; 28 | background-position: center; 29 | width: 360px; 30 | height: 450px; 31 | padding: 0px; 32 | box-sizing: border-box; 33 | position: relative; 34 | display: flex; 35 | flex-direction: row; 36 | align-items: flex-end; 37 | } 38 | 39 | .splash-image-caption { 40 | font-family: 'PlusJakartaSans-bold', Helvetica, sans-serif; 41 | color: #ffffff; 42 | font-size: 12px; 43 | } 44 | 45 | .splash-content-wrapper { 46 | flex: 1; 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: space-between; 50 | padding: 30px 40px; 51 | } 52 | 53 | .splash-logo { 54 | font-family: 'gilroyextrabold', Helvetica, sans-serif; 55 | font-size: 40px; 56 | color: #222222; 57 | margin-bottom: 2px; 58 | } 59 | 60 | .splash-version { 61 | font-family: 'gilroyextrabold', Helvetica, sans-serif; 62 | font-size: 14px; 63 | color: #898989; 64 | margin-left: 2px; 65 | } 66 | 67 | .splash-loading-message { 68 | font-family: 'gilroyextrabold', Helvetica, sans-serif; 69 | font-size: 18px; 70 | color: #222222; 71 | } 72 | 73 | .splash-description { 74 | font-family: 'PlusJakartaSans-regular', Helvetica, sans-serif; 75 | font-size: 14px; 76 | color: #585858; 77 | width: 400px; 78 | } 79 | 80 | .splash-license { 81 | font-family: 'PlusJakartaSans-bold', Helvetica, sans-serif; 82 | font-size: 14px; 83 | color: #898989; 84 | } 85 | -------------------------------------------------------------------------------- /webcomponents/src/viewModel/snapmail.perspective.ts: -------------------------------------------------------------------------------- 1 | import {MailItem} from "../bindings/snapmail.types"; 2 | import {AgentPubKeyB64} from "@holochain/client"; 3 | import {Dictionary} from "@ddd-qc/cell-proxy"; 4 | 5 | export type UsernameMap = Dictionary; 6 | 7 | 8 | /** */ 9 | export interface SnapmailPerspective { 10 | /* Map of (agentIdB64 -> username) */ 11 | usernameMap: UsernameMap, 12 | /* Map of (agentIdB64 -> timestamp of last ping) */ 13 | pingMap: Dictionary, 14 | /* Map of (agentIdB64 -> bool) */ 15 | responseMap: Dictionary, 16 | /* Map of (mailId -> mailItem) */ 17 | mailMap: Dictionary, 18 | // /** folderName -> mailId */ 19 | // folderMap: Dictionary, 20 | /** */ 21 | myHandle: string, 22 | } 23 | 24 | 25 | export function defaultPerspective(): SnapmailPerspective { 26 | return { 27 | usernameMap: {}, 28 | pingMap: {}, 29 | responseMap: {}, 30 | mailMap: {}, 31 | //folderMap: {}, 32 | myHandle: "", 33 | } 34 | } 35 | 36 | 37 | export interface ContactGridItem { 38 | status: string, 39 | username: string, 40 | recipientType: string, 41 | agentIdB64: AgentPubKeyB64, 42 | } 43 | 44 | 45 | 46 | // export interface InMailStateMat { 47 | // Unacknowledged?: null, 48 | // AckUnsent?: null, 49 | // AckPending?: null, 50 | // AckDelivered?: null, 51 | // Deleted?: null, 52 | // } 53 | 54 | 55 | // export interface DeliveryStateMat { 56 | // Unsent?: null, 57 | // Pending?: null, 58 | // Delivered?: null, 59 | // } 60 | // 61 | // 62 | // export interface OutMailStateMat { 63 | // Unsent?: null, 64 | // AllSent?: null, 65 | // AllReceived?: null, 66 | // AllAcknowledged?: null, 67 | // Deleted?: null, 68 | // } 69 | 70 | 71 | // //export type MailState = OutMailState | InMailState 72 | // export interface MailStateMat { 73 | // In?: InMailStateMat, 74 | // Out?: OutMailStateMat, 75 | // } 76 | 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cargo 2 | .idea 3 | .hc* 4 | */.running 5 | */*.tsbuildinfo 6 | 7 | release/ 8 | artifacts/* 9 | submodules/**/* 10 | bin/ 11 | 12 | electron/out-builder/** 13 | electron/out-tsc/** 14 | electron/web/*.js 15 | electron/web/index.html 16 | webcomponents/custom-elements.json 17 | *.zip 18 | *.webhapp 19 | *.happ 20 | *.map 21 | 22 | .vscode/** 23 | 24 | # build dependencies 25 | bin 26 | package-lock.json 27 | 28 | # build generated 29 | snapmail_dna_address 30 | 31 | # runtime data 32 | storage/**/* 33 | 34 | # copied binaries 35 | hc 36 | lair-keystore 37 | snapmail-lair-keystore 38 | holochain 39 | snapmail-holochain 40 | 41 | .DS_Store 42 | **/.DS_Store 43 | 44 | # Logs 45 | logs 46 | *.log 47 | npm-debug.log* 48 | yarn-debug.log* 49 | yarn-error.log* 50 | 51 | # Runtime data 52 | pids 53 | *.pid 54 | *.seed 55 | *.pid.lock 56 | 57 | # Directory for instrumented libs generated by jscoverage/JSCover 58 | lib-cov 59 | 60 | # Coverage directory used by tools like istanbul 61 | coverage 62 | 63 | # nyc test coverage 64 | .nyc_output 65 | 66 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 67 | .grunt 68 | 69 | # Bower dependency directory (https://bower.io/) 70 | bower_components 71 | 72 | # node-waf configuration 73 | .lock-wscript 74 | 75 | # Compiled binary addons (https://nodejs.org/api/addons.html) 76 | build/Release 77 | 78 | # Dependency directories 79 | node_modules/ 80 | jspm_packages/ 81 | 82 | # TypeScript v1 declaration files 83 | typings/ 84 | 85 | # Optional npm cache directory 86 | .npm 87 | 88 | # Optional eslint cache 89 | .eslintcache 90 | 91 | # Optional REPL history 92 | .node_repl_history 93 | 94 | # Output of 'npm pack' 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | .yarn-integrity 99 | 100 | # dotenv environment variables file 101 | .env 102 | 103 | # next.js build output 104 | .next 105 | -------------------------------------------------------------------------------- /webcomponents/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {CHUNK_MAX_SIZE} from "./bindings/snapmail.types"; 2 | 3 | /** Sleep via timeout promise */ 4 | export function delay(ms: number) { 5 | return new Promise(resolve => setTimeout(resolve, ms)); 6 | } 7 | 8 | 9 | /** */ 10 | export function arrayBufferToBase64(buffer: ArrayBuffer): string { 11 | let binary = ''; 12 | const bytes = new Uint8Array(buffer); 13 | const len = bytes.byteLength; 14 | for (let i = 0; i < len; i++) { 15 | binary += String.fromCharCode(bytes[i]); 16 | } 17 | return window.btoa( binary ); 18 | } 19 | 20 | 21 | /** */ 22 | export function base64ToArrayBuffer(base64: string): ArrayBufferLike { 23 | const binary_string = window.atob(base64); 24 | const len = binary_string.length; 25 | const bytes = new Uint8Array(len); 26 | for (let i = 0; i < len; i++) { 27 | bytes[i] = binary_string.charCodeAt(i); 28 | } 29 | return bytes.buffer; 30 | } 31 | 32 | 33 | /** */ 34 | async function sha256(message: string) { 35 | const utf8 = new TextEncoder().encode(message); 36 | const hashBuffer = await crypto.subtle.digest('SHA-256', utf8); 37 | const hashArray = Array.from(new Uint8Array(hashBuffer)); 38 | const hashHex = hashArray 39 | .map((bytes) => bytes.toString(16).padStart(2, '0')) 40 | .join(''); 41 | return hashHex; 42 | } 43 | 44 | 45 | /** */ 46 | function chunkSubstr(str: string, size: number): Array { 47 | const numChunks = Math.ceil(str.length / size); 48 | const chunks = new Array(numChunks); 49 | for (let i = 0, y = 0; i < numChunks; ++i, y += size) { 50 | chunks[i] = str.substring(y, y + size); 51 | } 52 | return chunks; 53 | } 54 | 55 | 56 | /** */ 57 | export async function splitFile(full_data_string: string) { 58 | const hash = await sha256(full_data_string); 59 | console.log('file hash: ' + hash) 60 | const chunks = chunkSubstr(full_data_string, CHUNK_MAX_SIZE); 61 | return { 62 | dataHash: hash, 63 | numChunks: chunks.length, 64 | chunks: chunks, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /we-applet/src/appletServices/search.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AgentPubKeyB64, 3 | AppAgentClient, 4 | decodeHashFromBase64, 5 | encodeHashToBase64, 6 | } from "@holochain/client"; 7 | import {AppletHash, HrlWithContext} from "@lightningrodlabs/we-applet/dist/types"; 8 | import {WeServices} from "@lightningrodlabs/we-applet/dist/api"; 9 | import {asCellProxy} from "@ddd-qc/we-utils"; 10 | import {MailItem, SNAPMAIL_DEFAULT_ROLE_NAME, SnapmailProxy} from "@snapmail/elements"; 11 | 12 | 13 | /** */ 14 | export interface SnapmailSearchContext { 15 | author: AgentPubKeyB64, 16 | mail: MailItem, 17 | } 18 | 19 | 20 | /** Return EntryHashs of Manifests whose name match the search filter */ 21 | export async function search( 22 | appletClient: AppAgentClient, 23 | appletHash: AppletHash, 24 | weServices: WeServices, 25 | searchFilter: string, 26 | ): Promise> { 27 | console.log("Snapmail/we-applet/search():", searchFilter); 28 | const searchLC = searchFilter.toLowerCase(); 29 | 30 | /** Get Cell proxy */ 31 | const mainAppInfo = await appletClient.appInfo(); 32 | const cellProxy = await asCellProxy( 33 | appletClient, 34 | undefined, 35 | mainAppInfo.installed_app_id, 36 | SNAPMAIL_DEFAULT_ROLE_NAME); 37 | console.log("Snapmail/we-applet/search(): cellProxy", cellProxy); 38 | const proxy/*: SnapmailProxy */ = new SnapmailProxy(cellProxy); 39 | const dnaHash = decodeHashFromBase64(proxy.cell.dnaHash); 40 | 41 | /** Search Private InMail */ 42 | const mails: MailItem[] = await proxy.getAllMails(); 43 | const matching: MailItem[] = mails.filter((mail) => mail.mail.subject.toLowerCase().includes(searchLC)); 44 | 45 | 46 | /** Transform results into HrlWithContext */ 47 | const results: Array = matching 48 | .map((mail) => { return { 49 | hrl: [dnaHash, mail.ah], 50 | context: {author: encodeHashToBase64(mail.author), mail} as SnapmailSearchContext, 51 | }}) 52 | 53 | /** Done */ 54 | return results; 55 | } 56 | -------------------------------------------------------------------------------- /webapp/web-dev-server.config.mjs: -------------------------------------------------------------------------------- 1 | // import { hmrPlugin, presets } from '@open-wc/dev-server-hmr'; 2 | import rollupReplace from '@rollup/plugin-replace'; 3 | import rollupCommonjs from '@rollup/plugin-commonjs'; 4 | import { fromRollup } from '@web/dev-server-rollup'; 5 | import rollupBuiltins from 'rollup-plugin-node-builtins'; 6 | 7 | const replace = fromRollup(rollupReplace); 8 | const commonjs = fromRollup(rollupCommonjs); 9 | const builtins = fromRollup(rollupBuiltins); 10 | 11 | const HAPP_BUILD_MODE = process.env.HAPP_BUILD_MODE? JSON.stringify(process.env.HAPP_BUILD_MODE) : 'prod'; 12 | console.log("web-dev-server HAPP_BUILD_MODE =", HAPP_BUILD_MODE); 13 | 14 | /** Use Hot Module replacement by adding --hmr to the start command */ 15 | const hmr = process.argv.includes('--hmr'); 16 | 17 | export default /** @type {import('@web/dev-server').DevServerConfig} */ ({ 18 | open: true, 19 | watch: !hmr, 20 | /** Resolve bare module imports */ 21 | nodeResolve: { 22 | preferBuiltins: false, 23 | browser: true, 24 | exportConditions: ['browser', HAPP_BUILD_MODE === 'Debug' ? 'development' : ''], 25 | }, 26 | 27 | /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */ 28 | // esbuildTarget: 'auto' 29 | 30 | rootDir: '../', 31 | 32 | /** Set appIndex to enable SPA routing */ 33 | appIndex: './index.html', 34 | 35 | plugins: [ 36 | replace({ 37 | preventAssignment: true, 38 | //'process.env.ENV': JSON.stringify(process.env.ENV), 39 | 'process.env.HAPP_BUILD_MODE': HAPP_BUILD_MODE, 40 | 'process.env.HAPP_ENV': JSON.stringify("Devtest"), 41 | 'process.env.HC_APP_PORT': JSON.stringify(process.env.HC_PORT || 8888), 42 | 'process.env.HC_ADMIN_PORT': JSON.stringify(process.env.ADMIN_PORT || 8889), 43 | ' COMB =': 'window.COMB =', 44 | delimiters: ['', ''], 45 | }), 46 | builtins(), 47 | commonjs({}), 48 | 49 | /** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */ 50 | // hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }), 51 | ], 52 | 53 | // See documentation for all available options 54 | }); 55 | -------------------------------------------------------------------------------- /.github/workflows/build-webhapps.yml: -------------------------------------------------------------------------------- 1 | name: build-webhapps 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | build-webhapps: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | # Install npm dependencies 12 | - name: Install npm dependencies 13 | shell: bash 14 | run: | 15 | npm install 16 | # Download submodules 17 | - name: Download submodules 18 | shell: bash 19 | run: | 20 | npm run install:submodules 21 | # install rust tools 22 | - name: install rust tools 23 | shell: bash 24 | run: | 25 | npm run install:rust 26 | # install hc tool 27 | - name: Install hc tool 28 | shell: bash 29 | run: | 30 | npm run install:hc 31 | # Install hash-zome 32 | - name: Install hash-zome 33 | run: | 34 | npm run install:hash-zome 35 | # Build happ 36 | - name: Build happ 37 | shell: bash 38 | run: | 39 | npm run build:happ 40 | # Build webapp 41 | - name: Build webapp 42 | run: | 43 | npm run build:webapp 44 | # Dist webapp 45 | - name: Dist webapp 46 | run: | 47 | npm run dist -w webapp 48 | # Package web-happ 49 | - name: Package web-happ 50 | run: | 51 | npm run package:webapp 52 | # build we-applet 53 | - name: build we-applet 54 | shell: bash 55 | run: | 56 | npm run build -w we-applet 57 | # Package we-applet 58 | - name: Package we-applet 59 | shell: bash 60 | run: | 61 | npm run package:we-applet 62 | # Regroup artifacts 63 | - name: Regroup artifacts 64 | run: | 65 | cp submodules/snapmail-rsm/target/wasm32-unknown-unknown/release/snapmail_model.wasm ./artifacts 66 | cp submodules/snapmail-rsm/target/wasm32-unknown-unknown/release/snapmail.wasm ./artifacts 67 | # List uploaded files 68 | - name: List uploaded files 69 | run: ls -R 70 | working-directory: ./artifacts 71 | # "upload" artifacts 72 | - uses: actions/upload-artifact@master 73 | with: 74 | name: all-happ-artifact 75 | path: artifacts/ 76 | -------------------------------------------------------------------------------- /we-applet/web-dev-server.config.mjs: -------------------------------------------------------------------------------- 1 | // import { hmrPlugin, presets } from '@open-wc/dev-server-hmr'; 2 | import { fromRollup } from "@web/dev-server-rollup"; 3 | import rollupReplace from "@rollup/plugin-replace"; 4 | import rollupCommonjs from "@rollup/plugin-commonjs"; 5 | import rollupBuiltins from 'rollup-plugin-node-builtins'; 6 | //import rollupGlobals from 'rollup-plugin-node-globals'; 7 | 8 | const replace = fromRollup(rollupReplace); 9 | const commonjs = fromRollup(rollupCommonjs); 10 | const builtins = fromRollup(rollupBuiltins); 11 | //const globals = fromRollup(rollupGlobals); 12 | 13 | const HAPP_BUILD_MODE = process.env.HAPP_BUILD_MODE? process.env.HAPP_BUILD_MODE : 'prod'; 14 | console.log("web-dev-server HAPP_BUILD_MODE =", HAPP_BUILD_MODE); 15 | 16 | console.log("web-dev-server: process.env.APPLET_VIEW: ", process.env.APPLET_VIEW); 17 | const APPLET_VIEW = process.env.APPLET_VIEW? process.env.APPLET_VIEW : "main"; 18 | 19 | /** Use Hot Module replacement by adding --hmr to the start command */ 20 | const hmr = process.argv.includes("--hmr"); 21 | 22 | export default /** @type {import('@web/dev-server').DevServerConfig} */ ({ 23 | open: true, 24 | watch: !hmr, 25 | /** Resolve bare module imports */ 26 | nodeResolve: { 27 | preferBuiltins: false, 28 | browser: true, 29 | exportConditions: ['browser', HAPP_BUILD_MODE === 'Debug' ? 'development' : ''], 30 | }, 31 | 32 | /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */ 33 | // esbuildTarget: 'auto' 34 | 35 | /** Set appIndex to enable SPA routing */ 36 | appIndex: "./index.html", 37 | rootDir: '../', 38 | clearTerminalOnReload: false, 39 | 40 | plugins: [ 41 | replace({ 42 | "preventAssignment": true, 43 | 'process.env.HAPP_BUILD_MODE': JSON.stringify(HAPP_BUILD_MODE), 44 | 'process.env.HAPP_ENV': JSON.stringify("DevtestWe"), 45 | 'process.env.HC_APP_PORT': JSON.stringify(process.env.HC_PORT || 8888), 46 | 'process.env.HC_ADMIN_PORT': JSON.stringify(process.env.ADMIN_PORT || 8889), 47 | ' COMB =': 'window.COMB =', 48 | delimiters: ["", ""], 49 | }), 50 | builtins(), 51 | commonjs(), 52 | /** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */ 53 | // hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }), 54 | ], 55 | 56 | // See documentation for all available options 57 | }); 58 | -------------------------------------------------------------------------------- /we-applet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "we-applet", 3 | "version": "0.4.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "tsc", 7 | "build:watch": "tsc -w --preserveWatchOutput", 8 | "dist": "rm -rf dist && tsc && rollup --config rollup.config.js", 9 | "devtest": "npm run dist && cross-env HC_PORT=$(port) ADMIN_PORT=$(port) HAPP_BUILD_MODE='Debug' concurrently \"npm run start:happ\" \"npm run start:ui\"", 10 | "start:happ": "RUST_LOG=warn WASM_LOG=debug echo \"pass\" | ../bin/hc s --piped -f=$ADMIN_PORT generate ../artifacts/snapmail.happ --run=$HC_PORT -a snapmail-applet network mem", 11 | "start:happ:local" : "RUST_LOG=warn WASM_LOG=debug echo \"pass\" | ../hc s --piped -f=$ADMIN_PORT generate ../artifacts/snapmail.happ --run=$APP_PORT -a snapmail-applet network --bootstrap http://127.0.0.1:$BOOT_PORT webrtc ws://127.0.0.1:$SIGNAL_PORT", 12 | 13 | "start:local" : "cross-env HC_PORT=$(port) ADMIN_PORT=$(port) HAPP_BUILD_MODE='Debug' concurrently \"npm run start:happ:local\" \"npm run start:ui\"", 14 | "start:ui": "concurrently -k --names tsc,dev-server \"npm run build:watch\" \"wds --config ./web-dev-server.config.mjs\"", 15 | 16 | "serve:run" : "hc run-local-services --bootstrap-port $BOOT_PORT --signal-port $SIGNAL_PORT", 17 | "network:local2" : "npm run dist && cross-env BOOT_PORT=$(port) SIGNAL_PORT=$(port) concurrently \"npm run serve:run\" \"npm run start:local\" \"sleep 1 && npm run start:local\"" 18 | }, 19 | "devDependencies": { 20 | "@babel/preset-env": "^7.15.0", 21 | "@typescript-eslint/eslint-plugin": "^5.39.0", 22 | "@typescript-eslint/parser": "^5.39.0", 23 | "@rollup/plugin-babel": "^5.3.0", 24 | "@rollup/plugin-commonjs": "18.0.0", 25 | "@rollup/plugin-node-resolve": "^13.0.4", 26 | "@rollup/plugin-replace": "^3.0.0", 27 | "@web/dev-server": "^0.1.21", 28 | "@web/dev-server-rollup": "^0.3.10", 29 | "@web/rollup-plugin-html": "^1.11.0", 30 | "@web/rollup-plugin-import-meta-assets": "^1.0.7", 31 | "babel-plugin-template-html-minifier": "^4.1.0", 32 | "@joseph184/rollup-plugin-node-builtins": "^2.1.4", 33 | "concurrently": "^5.3.0", 34 | "deepmerge": "^4.2.2", 35 | "eslint": "^8.24.0", 36 | "eslint-plugin-import": "^2.26.0", 37 | "new-port-cli": "^1.0.0", 38 | "rollup": "^2.56.2", 39 | "rollup-plugin-copy": "^3.4.0" 40 | }, 41 | "dependencies": { 42 | "@snapmail/elements": "file:../webcomponents", 43 | "snapmail": "file:../webapp" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /electron/web/networking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |

Network status

8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 | Peers seen last 20 min: 17 |
18 |
0
19 | 20 |
21 |
22 | 23 | 24 |
25 | Checking bootstrap server... 26 |
27 |
28 | 29 |
30 |
31 | 32 | 33 |
34 | Checking proxy server... 35 |
36 |
37 | 38 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /we-applet/src/devtest.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createDefaultWeServicesMock, 3 | DevTestNames, 4 | AppletViewInfo, 5 | setupDevtest, 6 | AttachableViewInfo 7 | } from "@ddd-qc/we-utils"; 8 | import {EntryHash, fakeActionHash} from "@holochain/client"; 9 | import {emptyEntryAppletView} from "@ddd-qc/we-utils/dist/mocks/renderInfoMock"; 10 | import {snake} from "@ddd-qc/cell-proxy"; 11 | import {createSnapmailApplet, ViewFileContext} from "./createSnapmailApplet"; 12 | import {AppletView} from "@lightningrodlabs/we-applet"; 13 | import { 14 | SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME, 15 | SNAPMAIL_DEFAULT_ROLE_NAME, 16 | SnapmailDvm, 17 | SnapmailEntryType 18 | } from "@snapmail/elements"; 19 | 20 | export const devtestNames: DevTestNames = { 21 | installed_app_id: "snapmail-applet", 22 | provisionedRoleName: SNAPMAIL_DEFAULT_ROLE_NAME, 23 | } 24 | 25 | //---------------------------------------------------------------------------------------------------------------------- 26 | // Block Views 27 | //---------------------------------------------------------------------------------------------------------------------- 28 | 29 | // export type BlockViewInfo = { 30 | // type: "block"; 31 | // block: string; 32 | // context: any; 33 | // } 34 | 35 | /** */ 36 | export function setupSnapmailBlockView(blockName: string) { 37 | const context: ViewFileContext = { 38 | detail: "none", 39 | } 40 | const appletView = { 41 | type: "block", 42 | block: blockName, 43 | context, 44 | } as AppletView; 45 | return setupDevtest(createSnapmailApplet, devtestNames, createDefaultWeServicesMock, appletView); 46 | } 47 | 48 | 49 | //---------------------------------------------------------------------------------------------------------------------- 50 | // Entry Views 51 | //---------------------------------------------------------------------------------------------------------------------- 52 | 53 | /** */ 54 | export async function setupSnapmailEntryView() { 55 | console.log("setupSnapmailEntryView()"); 56 | const context: ViewFileContext = { 57 | detail: "none", 58 | } 59 | const appletView = createInMailRenderInfo(await fakeActionHash(), context); 60 | return setupDevtest(createSnapmailApplet, devtestNames, createDefaultWeServicesMock, appletView); 61 | } 62 | 63 | 64 | /** */ 65 | function createInMailRenderInfo(eh: EntryHash, context: ViewFileContext): AttachableViewInfo { 66 | const entryInfo = emptyEntryAppletView as AttachableViewInfo; 67 | entryInfo.roleName = SNAPMAIL_DEFAULT_ROLE_NAME; 68 | entryInfo.integrityZomeName = SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME; 69 | entryInfo.entryType = snake(SnapmailEntryType.InMail); 70 | entryInfo.hrlWithContext.hrl[1] = eh; 71 | entryInfo.hrlWithContext.context = context; 72 | 73 | return entryInfo; 74 | } 75 | -------------------------------------------------------------------------------- /we-applet/src/appletServices/getAttachableInfo.ts: -------------------------------------------------------------------------------- 1 | 2 | import {asCellProxy, wrapPathInSvg} from "@ddd-qc/we-utils"; 3 | import {AppAgentClient, encodeHashToBase64, RoleName, ZomeName} from "@holochain/client"; 4 | import {pascal} from "@ddd-qc/cell-proxy"; 5 | import {mdiFileOutline, mdiEmailOutline} from "@mdi/js"; 6 | import {Hrl, AttachableInfo} from "@lightningrodlabs/we-applet"; 7 | import { 8 | SnapmailDvm, 9 | SnapmailEntryType, 10 | GetMailOutput, SnapmailProxy, SNAPMAIL_DEFAULT_ROLE_NAME, SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME 11 | } from "@snapmail/elements"; 12 | import {HrlWithContext} from "@lightningrodlabs/we-applet/dist/types"; 13 | 14 | 15 | /** */ 16 | export async function getAttachableInfo( 17 | appletClient: AppAgentClient, 18 | roleName: RoleName, 19 | integrityZomeName: ZomeName, 20 | entryType: string, 21 | hrlc: HrlWithContext, 22 | ): Promise { 23 | console.log("Snapmail/we-applet/getEntryInfo():", roleName, integrityZomeName, hrlc); 24 | if (roleName != SNAPMAIL_DEFAULT_ROLE_NAME) { 25 | throw new Error(`Snapmail/we-applet/getEntryInfo(): Unknown role name '${roleName}'.`); 26 | } 27 | if (integrityZomeName != SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME) { 28 | throw new Error(`Snapmail/we-applet/getEntryInfo(): Unknown zome '${integrityZomeName}'.`); 29 | } 30 | 31 | const mainAppInfo = await appletClient.appInfo(); 32 | const pEntryType = pascal(entryType); 33 | 34 | console.log("Snapmail/we-applet/getEntryInfo(): pEntryType", pEntryType); 35 | switch (pEntryType) { 36 | case SnapmailEntryType.InMail: 37 | case SnapmailEntryType.OutMail: 38 | console.log("Snapmail/we-applet/getEntryInfo(): mail info", hrlc); 39 | const cellProxy = await asCellProxy( 40 | appletClient, 41 | undefined, // hrlc.hrl[0], 42 | mainAppInfo.installed_app_id, 43 | SnapmailDvm.DEFAULT_BASE_ROLE_NAME); 44 | console.log("Snapmail/we-applet/getEntryInfo(): cellProxy", cellProxy); 45 | const proxy/*: SnapmailProxy */ = new SnapmailProxy(cellProxy); 46 | console.log("Snapmail/we-applet/getEntryInfo(): getFile()", encodeHashToBase64(hrlc.hrl[1]), proxy); 47 | const mailOutput: GetMailOutput = await proxy.getMail(hrlc.hrl[1]); 48 | if (mailOutput == null) { 49 | return undefined; 50 | } 51 | console.log("Snapmail/we-applet/getEntryInfo(): mail", mailOutput); 52 | return { 53 | icon_src: wrapPathInSvg(mdiEmailOutline), 54 | name: mailOutput.mail.subject, 55 | }; 56 | break; 57 | default: 58 | throw new Error(`Snapmail/we-applet/getEntryInfo(): Unhandled entry type ${entryType}.`); 59 | } 60 | } 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /we-applet/rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import builtins from "rollup-plugin-node-builtins"; 5 | // import globals from "rollup-plugin-node-globals"; 6 | import babel from "@rollup/plugin-babel"; 7 | //import { importMetaAssets } from "@web/rollup-plugin-import-meta-assets"; 8 | //import { terser } from "rollup-plugin-terser"; 9 | import copy from "rollup-plugin-copy"; 10 | import html from "@web/rollup-plugin-html"; 11 | 12 | //const BUILD_MODE = process.env.BUILD_MODE || "prod"; 13 | 14 | const DIST_FOLDER = "dist" 15 | 16 | 17 | export default { 18 | input: "index.html", 19 | output: { 20 | entryFileNames: "index.js", 21 | //chunkFileNames: "[hash].js", 22 | assetFileNames: "assets[extname]", 23 | format: "es", 24 | dir: DIST_FOLDER, 25 | //sourcemap: false 26 | }, 27 | watch: { 28 | clearScreen: false, 29 | }, 30 | external: [], 31 | 32 | plugins: [ 33 | /** Enable using HTML as rollup entrypoint */ 34 | html({ 35 | //minify: true, 36 | //injectServiceWorker: true, 37 | //serviceWorkerPath: "dist/sw.js", 38 | }), 39 | /** Resolve bare module imports */ 40 | nodeResolve({ 41 | browser: true, 42 | preferBuiltins: false, 43 | }), 44 | //typescript({ experimentalDecorators: true, outDir: DIST_FOLDER }), 45 | builtins(), 46 | /** Minify JS */ 47 | //terser(), 48 | /** Bundle assets references via import.meta.url */ 49 | //importMetaAssets(), 50 | /** Compile JS to a lower language target */ 51 | babel({ 52 | exclude: /node_modules/, 53 | 54 | babelHelpers: "bundled", 55 | presets: [ 56 | [ 57 | require.resolve("@babel/preset-env"), 58 | { 59 | targets: [ 60 | "last 3 Chrome major versions", 61 | "last 3 Firefox major versions", 62 | "last 3 Edge major versions", 63 | "last 3 Safari major versions", 64 | ], 65 | modules: false, 66 | bugfixes: true, 67 | }, 68 | ], 69 | ], 70 | plugins: [ 71 | [ 72 | require.resolve("babel-plugin-template-html-minifier"), 73 | { 74 | modules: { 75 | lit: ["html", { name: "css", encapsulation: "style" }], 76 | }, 77 | failOnError: false, 78 | strictCSS: true, 79 | htmlMinifier: { 80 | collapseWhitespace: true, 81 | conservativeCollapse: true, 82 | removeComments: true, 83 | caseSensitive: true, 84 | minifyCSS: true, 85 | }, 86 | }, 87 | ], 88 | ], 89 | }), 90 | commonjs({}), 91 | copy({ 92 | targets: [ 93 | { src: "../assets/favicon.ico", dest: DIST_FOLDER }, 94 | ], 95 | }), 96 | ], 97 | }; 98 | -------------------------------------------------------------------------------- /electron/src/userSettings.ts: -------------------------------------------------------------------------------- 1 | import {screen} from "electron"; 2 | import {APP_DATA_PATH} from "./constants"; 3 | 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | //import * as path from 'path' 7 | //import * as fs from 'fs' 8 | 9 | /** 10 | * Object for handling/storing all user preferences 11 | */ 12 | export class SettingsStore { 13 | 14 | path: string; 15 | data: any /*Object*/; 16 | 17 | constructor(opts:any) { 18 | // Renderer process has to get `app` module via `remote`, whereas the main process can get it directly 19 | // app.getPath('userData') will return a string of the user's app data directory path. 20 | // const userDataPath = (app || remote.app).getPath('userData'); 21 | 22 | this.path = path.join(opts.storagePath, opts.configName + '.json'); 23 | 24 | this.data = parseSettingsFile(this.path, opts.defaults); 25 | } 26 | 27 | // This will just return the property on the `data` object 28 | get(key:any) { 29 | return this.data[key]; 30 | } 31 | 32 | // ...and this will set it 33 | set(key:any, val:any) { 34 | this.data[key] = val; 35 | // Wait, I thought using the node.js' synchronous APIs was bad form? 36 | // We're not writing a server so there's not nearly the same IO demand on the process 37 | // Also if we used an async API and our app was quit before the asynchronous write had a chance to complete, 38 | // we might lose that data. Note that in a real app, we would try/catch this. 39 | fs.writeFileSync(this.path, JSON.stringify(this.data)); 40 | } 41 | } 42 | 43 | 44 | /** */ 45 | function parseSettingsFile(filePath: string, defaults: any) { 46 | // We'll try/catch it in case the file doesn't exist yet, which will be the case on the first application run. 47 | // `fs.readFileSync` will return a JSON string which we then parse into a Javascript object 48 | try { 49 | return JSON.parse(fs.readFileSync(filePath)); 50 | } catch(error) { 51 | // if there was some kind of error, return the passed in defaults instead. 52 | return defaults; 53 | } 54 | } 55 | 56 | 57 | /** */ 58 | export function loadUserSettings(initialWidth: number, initialHeight: number): SettingsStore { 59 | // Get Settings 60 | const { width, height } = screen.getPrimaryDisplay().workAreaSize; 61 | const starting_width = Math.min(width, initialWidth); 62 | const starting_height = Math.min(height, initialHeight); 63 | 64 | const x = Math.floor((width - starting_width) / 2); 65 | const y = Math.floor((height - starting_height) / 2); 66 | 67 | const userSettings = new SettingsStore({ 68 | // We'll call our data file 'user-preferences' 69 | configName: 'user-preferences', 70 | storagePath: APP_DATA_PATH, 71 | defaults: { 72 | windowBounds: { width: starting_width, height: starting_height }, 73 | canAutoLaunch: false, 74 | windowPosition: {x, y}, 75 | dontConfirmOnExit: false, 76 | canNotify: false, 77 | lastUid: undefined, 78 | } 79 | }); 80 | return userSettings; 81 | } 82 | -------------------------------------------------------------------------------- /electron/web/networking.css: -------------------------------------------------------------------------------- 1 | html { box-sizing: border-box; } 2 | 3 | *, 4 | *::before, 5 | *::after { box-sizing: inherit; } 6 | 7 | body * { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | body { 13 | font: normal 100%/1.15 "Merriweather", serif; 14 | background-color: #f6f6f6; 15 | color: #101010; 16 | } 17 | 18 | .wrapper { 19 | position: relative; 20 | max-width: 1298px; 21 | height: auto; 22 | margin: 2em auto 0 auto; 23 | } 24 | 25 | .box { 26 | max-width: 70%; 27 | min-height: auto; 28 | margin: 0 auto; 29 | padding: 1em 1em; 30 | text-align: center; 31 | } 32 | 33 | h1, p:not(:last-of-type) { text-shadow: 0 0 6px #737965; } 34 | 35 | h1 { 36 | margin: 0 0 1rem 0; 37 | font-size: 4em; 38 | } 39 | 40 | p { 41 | margin-bottom: 0.5em; 42 | font-size: 3em; 43 | } 44 | 45 | p:first-of-type { margin-top: 1em; } 46 | 47 | p img { vertical-align: bottom; } 48 | 49 | .activity-message { 50 | font-family: 'gilroyextrabold', Helvetica, sans-serif; 51 | font-size: 18px; 52 | color: #222222; 53 | display: inline; 54 | } 55 | 56 | .lds-spinner { 57 | display: inline-block; 58 | position: relative; 59 | width: 80px; 60 | height: 80px; 61 | margin-top: 200px; 62 | } 63 | .lds-spinner div { 64 | transform-origin: 40px 40px; 65 | animation: lds-spinner 1.2s linear infinite; 66 | } 67 | .lds-spinner div:after { 68 | content: " "; 69 | display: block; 70 | position: absolute; 71 | top: 3px; 72 | left: 37px; 73 | width: 6px; 74 | height: 18px; 75 | border-radius: 20%; 76 | background: rgb(22, 118, 243); 77 | } 78 | .lds-spinner div:nth-child(1) { 79 | transform: rotate(0deg); 80 | animation-delay: -1.1s; 81 | } 82 | .lds-spinner div:nth-child(2) { 83 | transform: rotate(30deg); 84 | animation-delay: -1s; 85 | } 86 | .lds-spinner div:nth-child(3) { 87 | transform: rotate(60deg); 88 | animation-delay: -0.9s; 89 | } 90 | .lds-spinner div:nth-child(4) { 91 | transform: rotate(90deg); 92 | animation-delay: -0.8s; 93 | } 94 | .lds-spinner div:nth-child(5) { 95 | transform: rotate(120deg); 96 | animation-delay: -0.7s; 97 | } 98 | .lds-spinner div:nth-child(6) { 99 | transform: rotate(150deg); 100 | animation-delay: -0.6s; 101 | } 102 | .lds-spinner div:nth-child(7) { 103 | transform: rotate(180deg); 104 | animation-delay: -0.5s; 105 | } 106 | .lds-spinner div:nth-child(8) { 107 | transform: rotate(210deg); 108 | animation-delay: -0.4s; 109 | } 110 | .lds-spinner div:nth-child(9) { 111 | transform: rotate(240deg); 112 | animation-delay: -0.3s; 113 | } 114 | .lds-spinner div:nth-child(10) { 115 | transform: rotate(270deg); 116 | animation-delay: -0.2s; 117 | } 118 | .lds-spinner div:nth-child(11) { 119 | transform: rotate(300deg); 120 | animation-delay: -0.1s; 121 | } 122 | .lds-spinner div:nth-child(12) { 123 | transform: rotate(330deg); 124 | animation-delay: 0s; 125 | } 126 | @keyframes lds-spinner { 127 | 0% { 128 | opacity: 1; 129 | } 130 | 100% { 131 | opacity: 0; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /electron/src/holochain.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { app } from 'electron' 3 | import { ElectronHolochainOptions, StateSignal } from "@lightningrodlabs/electron-holochain" 4 | import {DNA_PATH, DNA_VERSION_FILENAME, getAdminPort, SNAPMAIL_APP_ID} from './constants' 5 | import {NetworkSettings} from "./networkSettings"; 6 | import fs from "fs"; 7 | import {log} from "./logger"; 8 | 9 | 10 | /** Messages displayed on the splashscreen */ 11 | export enum StateSignalText { 12 | IsFirstRun = 'Welcome to Snapmail...', 13 | IsNotFirstRun = 'Loading...', 14 | CreatingKeys = 'Creating cryptographic keys...', 15 | RegisteringDna = 'Registering Profiles DNA to Holochain...', 16 | InstallingApp = 'Installing DNA bundle to Holochain...', 17 | EnablingApp = 'Enabling DNA...', 18 | AddingAppInterface = 'Attaching API network port...', 19 | UnknownState = 'Application is in an unknown state...', 20 | } 21 | 22 | 23 | /** */ 24 | export function stateSignalToText(state: StateSignal): StateSignalText { 25 | switch (state) { 26 | case StateSignal.IsFirstRun: 27 | return StateSignalText.IsFirstRun 28 | case StateSignal.IsNotFirstRun: 29 | return StateSignalText.IsNotFirstRun 30 | case StateSignal.CreatingKeys: 31 | return StateSignalText.CreatingKeys 32 | case StateSignal.RegisteringDna: 33 | return StateSignalText.RegisteringDna 34 | case StateSignal.InstallingApp: 35 | return StateSignalText.InstallingApp 36 | case StateSignal.EnablingApp: 37 | return StateSignalText.EnablingApp 38 | case StateSignal.AddingAppInterface: 39 | return StateSignalText.AddingAppInterface 40 | } 41 | log('error', 'Unknown state: ' + JSON.stringify(state)) 42 | return StateSignalText.UnknownState 43 | } 44 | 45 | 46 | /** */ 47 | export async function createHolochainOptions(uid: string, storagePath: string, networkSettings: NetworkSettings): Promise { 48 | const keystorePath = path.join(storagePath, 'keystore-' + app.getVersion()) 49 | const datastorePath = path.join(storagePath, 'databases-' + app.getVersion()) 50 | //console.log('info', {__dirname}); 51 | const options: ElectronHolochainOptions = { 52 | happPath: DNA_PATH, 53 | datastorePath, 54 | keystorePath, 55 | //happPath: "C:\\github\\snapmail\\electron\\bin\\snapmail.happ\\", 56 | //happPath: "snapmail.happ", 57 | //happPath: DNA_PATH.replace(/\\/g, "/").replace(/:/g, ""), 58 | appId: SNAPMAIL_APP_ID + '-' + uid, 59 | //appId: MAIN_APP_ID, 60 | appWsPort: 0, 61 | adminWsPort: await getAdminPort(), 62 | proxyUrl: networkSettings.proxyUrl, 63 | bootstrapUrl: networkSettings.canProxy? networkSettings.bootstrapUrl : '', 64 | passphrase: "test-passphrase", 65 | } 66 | //console.log('info', {adminWsPort: options.adminWsPort}); 67 | return options; 68 | } 69 | 70 | 71 | /** */ 72 | export function loadDnaVersion(sessionDataPath: string): string | undefined { 73 | let dnaVersion = undefined; 74 | //const configFilePath = path.join(sessionDataPath, '../'); 75 | const configFilePath = path.join(sessionDataPath, DNA_VERSION_FILENAME); 76 | //log('debug', "loadDnaVersion() configFilePath = " + configFilePath); 77 | try { 78 | dnaVersion = fs.readFileSync(configFilePath).toString(); 79 | } catch(error) { 80 | log("warn", "File not found ; " + configFilePath) 81 | return undefined; 82 | } 83 | return dnaVersion; 84 | } 85 | -------------------------------------------------------------------------------- /webcomponents/src/global-styles.ts: -------------------------------------------------------------------------------- 1 | // eagerly import theme styles so as we can override them 2 | //import '@vaadin/vaadin-lumo-styles/all-imports'; 3 | 4 | 5 | function safeDecorator(fn:any) { 6 | // eslint-disable-next-line func-names 7 | return function(...args:any) { 8 | try { 9 | // @ts-ignore 10 | return fn.apply(this, args); 11 | } catch (error) { 12 | if ( 13 | error instanceof DOMException && 14 | //error.message.includes('has already been used with this registry') 15 | error.message.includes('has already been defined as a custom element') 16 | ) { 17 | console.warn("Double customElements.define waived") 18 | return false; 19 | } 20 | throw error; 21 | } 22 | }; 23 | } 24 | customElements.define = safeDecorator(customElements.define); 25 | 26 | // 27 | // const $_documentContainer = document.createElement('template'); 28 | // 29 | // 30 | // $_documentContainer.innerHTML = ` 31 | // 32 | // 36 | // 37 | // 38 | // 39 | // 115 | // 116 | // `; 117 | // 118 | // document.head.appendChild($_documentContainer.content); 119 | -------------------------------------------------------------------------------- /webapp/rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from "@rollup/plugin-node-resolve"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | //import builtins from "rollup-plugin-node-builtins"; 5 | //import globals from "rollup-plugin-node-globals"; 6 | 7 | import babel from "@rollup/plugin-babel"; 8 | import html from "@web/rollup-plugin-html"; 9 | //import { importMetaAssets } from "@web/rollup-plugin-import-meta-assets"; 10 | //import { terser } from "rollup-plugin-terser"; 11 | //import { generateSW } from "rollup-plugin-workbox"; 12 | import copy from "rollup-plugin-copy"; 13 | //import path from "path"; 14 | 15 | 16 | const DIST_FOLDER = "dist" 17 | 18 | 19 | export default { 20 | input: "index.html", 21 | output: { 22 | entryFileNames: "[hash].js", 23 | chunkFileNames: "[hash].js", 24 | assetFileNames: "[hash][extname]", 25 | format: "es", 26 | dir: DIST_FOLDER, 27 | }, 28 | watch: { 29 | clearScreen: false, 30 | }, 31 | 32 | plugins: [ 33 | /** Enable using HTML as rollup entrypoint */ 34 | html({ 35 | minify: true, 36 | //injectServiceWorker: true, 37 | //serviceWorkerPath: "dist/sw.js", 38 | }), 39 | /** Resolve bare module imports */ 40 | nodeResolve({ 41 | browser: true, 42 | preferBuiltins: false, 43 | }), 44 | copy({ 45 | targets: [{ src: "../assets/favicon.ico", dest: "dist" }], 46 | }), 47 | //builtins(), 48 | typescript({ experimentalDecorators: true, outDir: DIST_FOLDER }), 49 | //globals(), // removed because it cause build error 50 | /** Minify JS */ 51 | //terser(), 52 | /** Bundle assets references via import.meta.url */ 53 | //importMetaAssets(), 54 | /** Compile JS to a lower language target */ 55 | babel({ 56 | exclude: /node_modules/, 57 | 58 | babelHelpers: "bundled", 59 | presets: [ 60 | [ 61 | require.resolve("@babel/preset-env"), 62 | { 63 | targets: [ 64 | "last 3 Chrome major versions", 65 | "last 3 Firefox major versions", 66 | "last 3 Edge major versions", 67 | "last 3 Safari major versions", 68 | ], 69 | modules: false, 70 | bugfixes: true, 71 | }, 72 | ], 73 | ], 74 | plugins: [ 75 | [ 76 | require.resolve("babel-plugin-template-html-minifier"), 77 | { 78 | modules: { 79 | lit: ["html", { name: "css", encapsulation: "style" }], 80 | }, 81 | failOnError: false, 82 | strictCSS: true, 83 | htmlMinifier: { 84 | collapseWhitespace: true, 85 | conservativeCollapse: true, 86 | removeComments: true, 87 | caseSensitive: true, 88 | minifyCSS: true, 89 | }, 90 | }, 91 | ], 92 | ], 93 | }), 94 | /** Create and inject a service worker */ 95 | // generateSW({ 96 | // globIgnores: ["polyfills/*.js", "nomodule-*.js"], 97 | // navigateFallback: "/index.html", 98 | // // where to output the generated sw 99 | // swDest: path.join(DIST_FOLDER, "sw.js"), 100 | // // directory to match patterns against to be precached 101 | // globDirectory: path.join(DIST_FOLDER), 102 | // // cache any html js and css by default 103 | // globPatterns: ["**/*.{html,js,css,webmanifest}"], 104 | // skipWaiting: true, 105 | // clientsClaim: true, 106 | // runtimeCaching: [{ urlPattern: "polyfills/*.js", handler: "CacheFirst" }], 107 | // }), 108 | commonjs({}), 109 | ], 110 | }; 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snapmail 2 | SnapMail is an open-source P2P messaging app based on [Holochain](https://holochain.org/). 3 | It is the latest iteration of [Snapmail](http://www.glassbead.com/snapmail/index.shtml) from [Glass Bead Software](http://www.glassbead.com/). 4 | 5 | 6 | ![sshot](https://github.com/glassbeadsoftware/snapmail/blob/master/assets/snapmail-ui.png) 7 | 8 | This is the repository for the different native applications 9 | See [snapmail-rsm](https://github.com/glassbeadsoftware/snapmail-rsm) for holochain backend source code. 10 | 11 | ## Installation 12 | 13 | ### Prerequisite 14 | - Linux: `libssl` package must be installed on your system (and possibly `libssl-dev`) 15 | 16 | ### Steps 17 | 1. Go to the [release page](https://github.com/glassbeadsoftware/snapmail/releases) 18 | 2. Download the latest release file for your platform. 19 | 3. Execute the downloaded installer. 20 | 4. Launch the `Snapmail` executable. 21 | 5. You will be prompted to select a network access key. This is required for discovering other peers on the network. You will only communicate with peers using the same network access key. 22 | 6. You will be prompted to Set a username. 23 | 7. Other users on the same network will appear in the bottom right list box. 24 | 25 | ### Troubleshoot 26 | 27 | ##### Linux 28 | - Make sure the following executables have execution rights: `Snapmail`, `snapmail-lair-keystore`, `snapmail-holochain`. 29 | 30 | 31 | ## Releasing 32 | 33 | #### Steps required for updating version number: 34 | 1. Set version number in top-level `package.json` 35 | 2. `npm run update-version` 36 | 37 | 38 | #### Steps for preparing a new release for all platforms on github: 39 | 1. Tag a new release in the [release page](https://github.com/glassbeadsoftware/snapmail/releases). 40 | 2. Wait for CI to complete its [workflow](https://github.com/glassbeadsoftware/snapmail/actions). 41 | 42 | #### Steps for publishing the new release on Windows: 43 | 1. `npm run deploy-win` 44 | 45 | #### Steps for publishing the new release on Mac: 46 | 1. `npm run deploy-mac` 47 | 48 | 49 | ## Development 50 | 51 | #### Steps for updating holochain version: 52 | 1. Check `snapmail-rsm` repo has a branch for the new holochain version. 53 | 1. Update `hc-version` field in the top-level `package.json`. 54 | 3. Update the holochain-related dependencies in child `package.json`: 55 | 4. `webcomponents`: `lit-happ` & `holochain-client-js` 56 | 5. `webapp`: `lit-happ` & `holochain-client-js` 57 | 6. `we-applet`: `holochain-client-js` 58 | 6. `electron`: `@lightningrodlabs/electron-holochain` 59 | 3. Check `npm run install:zits`: Make sure `zits` is compatible for the version of holochain & `holochain-client-js`. 60 | 6. `git commit` 61 | 62 | 63 | ### Toolchain 64 | 65 | cargo, npm, rollup, typescript, eslint, electron-builder 66 | 67 | ## Project structure 68 | 69 | | Directory | Description | 70 | |:-------------------------------------------| :-------------------------------------------------------------------------------------------------------------------------- | 71 | | `/assets/` | Original media files used throughout the code base 72 | | `/electron/` | The electron app directory 73 | |     `src/` | The electron app source code 74 | |     `web/` | Final artifacts for the electron app (includes output from `webapp`) 75 | |     `bin/` | All the binaries we are dependent on and must ship with the app 76 | | `/scripts/` | Various shell scripts for building, testing, releasing 77 | | `/submodules/` | Temp folder for code dependencies 78 | | `/we-applet/` | We-applet source code 79 | |     `webhapp.workdir/` | "we-applet" webhapp work directory 80 | | `/webapp/` | Webapp source code. Used by electron app 81 | |     `webhapp.workdir/` | "Normal" webhapp work directory 82 | | `/webcomponents/` | Source code of the web components to be used by the webapps 83 | -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Snapmail", 3 | "version": "0.3.1", 4 | "description": "A minimalist P2P messaging application on holochain", 5 | "keywords": [ 6 | "Electron", 7 | "Holochain" 8 | ], 9 | "author": "Glass Bead Software, LLC and contributors", 10 | "license": "CAL-1.0", 11 | "main": "out-tsc/main.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/glassbeadsoftware/snapmail.git" 15 | }, 16 | "scripts": { 17 | "postinstall": "electron-builder install-app-deps", 18 | "lint": "eslint --ext .ts src", 19 | "import-webapp": "cp -v -r ../webapp/dist/* web", 20 | "build": "npm run import-webapp && tsc", 21 | "devtest": "npm run build && WASM_LOG=debug RUST_BACKTRACE=1 RUST_LOG=debug HAPP_BUILD_MODE='Debug' ELECTRON_DEBUG_NOTIFICATIONS=true ELECTRON_DEFAULT_ERROR_MODE=true ELECTRON_ENABLE_LOGGING=true ELECTRON_ENABLE_STACK_DUMPING=true electron . default --disable-gpu --disable-software-rasterizer", 22 | "prodtest": "npm run build && WASM_LOG=debug RUST_BACKTRACE=1 RUST_LOG=debug ELECTRON_ENABLE_STACK_DUMPING=true electron . default --disable-gpu --disable-software-rasterizer", 23 | "electron-dev": "tsc && WASM_LOG=debug RUST_BACKTRACE=1 RUST_LOG=debug HAPP_BUILD_MODE='Debug' && electron . dev --no-sandbox", 24 | "alex": "tsc && ./node_modules/.bin/electron . alex --no-sandbox", 25 | "billy": "tsc && ./node_modules/.bin/electron . billy --no-sandbox", 26 | "camille": "tsc && ./node_modules/.bin/electron . camille --no-sandbox", 27 | "start-no": "./node_modules/.bin/electron electron --no-sandbox --enable-logging", 28 | "deploy-win": "electron-builder build --win --publish always", 29 | "deploy-mac": "electron-builder build --mac --publish always", 30 | "dist:publish": "electron-builder build --publish onTag", 31 | "dist": "electron-builder", 32 | "dist:build": "electron-builder build", 33 | "dist:linux": "electron-builder --linux tar.gz", 34 | "dist:linux-app": "electron-builder --linux AppImage", 35 | "dist:mac": "electron-builder --mac", 36 | "dist:win": "electron-builder --win", 37 | "dist:arm64": "electron-builder --arm64 --linux tar.gz" 38 | }, 39 | "build": { 40 | "productName": "Snapmail", 41 | "afterSign": "./afterSignHook.js", 42 | "appId": "com.glassbead.snapmail", 43 | "copyright": "Copyright © 2021-2023 Glass Bead Software LLC", 44 | "asar": false, 45 | "files": [ 46 | "web/**/*", 47 | "out-tsc/*", 48 | "bin/*", 49 | "node_modules/**/*", 50 | "package.json" 51 | ], 52 | "directories": { 53 | "buildResources": "../assets", 54 | "output": "out-builder" 55 | }, 56 | "linux": { 57 | "target": "AppImage" 58 | }, 59 | "mac": { 60 | "target": "dmg" 61 | }, 62 | "win": { 63 | "target": "nsis" 64 | }, 65 | "publish": { 66 | "provider": "github", 67 | "releaseType": "prerelease", 68 | "publishAutoUpdate": true 69 | } 70 | }, 71 | "devDependencies": { 72 | "@types/auto-launch": "^5.0.2", 73 | "@types/electron-prompt": "^1.6.1", 74 | "@typescript-eslint/eslint-plugin": "^5.39.0", 75 | "@typescript-eslint/parser": "^5.39.0", 76 | "concurrently": "^6.2.1", 77 | "concurrently-repeat": "^0.0.1", 78 | "cross-env": "^7.0.3", 79 | "electron": "19.0.0", 80 | "electron-builder": "^23.6.0", 81 | "eslint": "^8.24.0", 82 | "typescript": "^4.9.0" 83 | }, 84 | "dependencies": { 85 | "@electron/remote": "^2.0.8", 86 | "@lightningrodlabs/electron-holochain": "0.6.1", 87 | "auto-launch": "^5.0.5", 88 | "command-exists": "^1.2.8", 89 | "dialogs": "^2.0.1", 90 | "electron-context-menu": "^0.12.1", 91 | "electron-debug": "^3.0.1", 92 | "electron-log": "^4.4.6", 93 | "electron-notarize": "^1.1.1", 94 | "electron-osx-sign": "^0.4.14", 95 | "electron-prompt": "^1.7.0", 96 | "electron-updater": "^4.3.9", 97 | "fix-path": "^2.1.0", 98 | "lint": "^0.7.0", 99 | "open": "^6.3.0", 100 | "request": "^2.88.2", 101 | "sudo-prompt": "^9.0.0", 102 | "tslib": "^2.4.0", 103 | "tree-kill": "^1.2.2", 104 | "websocket": "^1.0.34", 105 | "ws": "^8.5.0" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /webcomponents/src/elements/snapmail-mail-write.ts: -------------------------------------------------------------------------------- 1 | import {css, html, LitElement} from "lit"; 2 | import { state, property, customElement } from "lit/decorators.js"; 3 | import {ScopedElementsMixin} from "@open-wc/scoped-elements"; 4 | import {TextArea} from "@vaadin/text-area"; 5 | import {TextField} from "@vaadin/text-field"; 6 | import {Upload, UploadBeforeEvent, UploadFileRejectEvent} from "@vaadin/upload"; 7 | import {UploadFile} from "@vaadin/upload/src/vaadin-upload"; 8 | 9 | 10 | /** */ 11 | @customElement("snapmail-mail-write") 12 | export class SnapmailMailWrite extends ScopedElementsMixin(LitElement) { 13 | 14 | @property() 15 | subject: string; 16 | 17 | @property() 18 | content: string; 19 | 20 | 21 | 22 | /** -- Getters -- */ 23 | 24 | getSubject(): string {return this.outMailSubjectElem.value} 25 | getContent(): string {return this.outMailContentElem.value} 26 | 27 | get outMailSubjectElem() : TextField { 28 | return this.shadowRoot.getElementById("outMailSubjectArea") as TextField; 29 | } 30 | 31 | get outMailContentElem() : TextArea { 32 | return this.shadowRoot.getElementById("outMailContentArea") as TextArea; 33 | } 34 | 35 | get uploadElem() : Upload { 36 | return this.shadowRoot.getElementById("myUpload") as Upload; 37 | } 38 | 39 | 40 | get files(): UploadFile[] {return this.uploadElem.files} 41 | 42 | 43 | /** -- Methods -- */ 44 | 45 | /** */ 46 | reset() { 47 | this.outMailSubjectElem.value = ''; 48 | this.outMailContentElem.value = ''; 49 | /** clear each attachment */ 50 | this.uploadElem.files = []; 51 | } 52 | 53 | 54 | /** */ 55 | private onUpload(e: UploadBeforeEvent) { 56 | console.log('upload-before event: ', e); 57 | const file = e.detail.file; 58 | //const xhr = event.detail.xhr; 59 | //console.log({file}); 60 | 61 | e.preventDefault(); // Prevent the upload request 62 | 63 | /** Read file just so we can change vaadin's upload-file css */ 64 | const reader = new FileReader(); 65 | reader.addEventListener('loadend', (event: unknown) => { 66 | console.log('FileReader loadend event: ', event); 67 | /** Hide all unnecessary UI */ 68 | const uploadFiles = this.uploadElem.shadowRoot.querySelectorAll("vaadin-upload-file"); 69 | console.log({uploadFiles}); 70 | uploadFiles.forEach((uploadFile) => { 71 | const progressBar = uploadFile.shadowRoot.querySelector("vaadin-progress-bar"); 72 | progressBar.style.display = 'none'; 73 | const status: HTMLElement = uploadFile.shadowRoot.querySelector('[part="status"]'); 74 | status.style.display = 'none'; 75 | const start: HTMLElement = uploadFile.shadowRoot.querySelector('[part="start-button"]'); 76 | start.style.display = 'none'; 77 | }); 78 | }); 79 | reader.readAsArrayBuffer(file); 80 | } 81 | 82 | /** */ 83 | render() { 84 | return html` 85 | 86 | 91 | 96 | 103 | Maximum file size: 8 MB 104 | 105 | 106 | `; 107 | } 108 | 109 | /** */ 110 | static get styles() { 111 | return [ 112 | css` 113 | `]; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /electron/src/constants.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { app } from 'electron'; 3 | import {getRunnerVersion, PathOptions} from "@lightningrodlabs/electron-holochain" 4 | 5 | console.log({__dirname}) 6 | console.log("electron IS_PACKAGED", app.isPackaged); 7 | 8 | /** DEBUGGING */ 9 | export const IS_PACKAGED = app.isPackaged; 10 | export const IS_DEV = process.env.HAPP_BUILD_MODE? process.env.HAPP_BUILD_MODE == HappBuildModeType.Debug : false; 11 | export const DEVELOPMENT_UI_URL = path.join(__dirname, '../web') 12 | 13 | /** MISC */ 14 | export const SNAPMAIL_APP_ID = 'snapmail-app'; // WARN Must match APP_ID in webcomponents code 15 | /** A special log from the conductor specifying that the interfaces are ready to receive incoming connections */ 16 | export const REPORT_BUG_URL = `https://github.com/glassbeadsoftware/snapmail/issues/new`; 17 | 18 | /** NETWORK */ 19 | export const DEFAULT_BOOTSTRAP_URL = 'https://bootstrap-staging.holo.host'; 20 | export const DEFAULT_PROXY_URL ='kitsune-proxy://SYVd4CF3BdJ4DS7KwLLgeU3_DbHoZ34Y-qroZ79DOs8/kitsune-quic/h/165.22.32.11/p/5779/--' 21 | //kitsune-proxy://VYgwCrh2ZCKL1lpnMM1VVUee7ks-9BkmW47C_ys4nqg/kitsune-quic/h/kitsune-proxy.harris-braun.com/p/4010/--'; 22 | 23 | /** PATHS */ 24 | export const CONFIG_PATH = path.join(app.getPath('appData'), 'Snapmail'); 25 | //export const STORAGE_PATH = path.join(CONFIG_PATH, 'storage'); 26 | export const UID_LIST_FILENAME = 'uid-list.txt'; 27 | export const NETWORK_SETTINGS_FILENAME = 'network-preferences.json'; 28 | export const CURRENT_DIR = path.join(__dirname, '..'); 29 | export const DIST_DIR = "web"; 30 | export const FAVICON_PATH = `/web/favicon.png`; 31 | 32 | 33 | export const DNA_PATH = IS_PACKAGED 34 | ? path.join(app.getAppPath(), '../app/bin/snapmail.happ') 35 | : path.join(app.getAppPath(), 'bin/snapmail.happ') 36 | 37 | 38 | /* 39 | app.getPath('appData') = 40 | - %APPDATA% on Windows 41 | - $XDG_CONFIG_HOME or ~/.config on Linux 42 | - ~/Library/Application Support on macOS 43 | */ 44 | export const APP_DATA_PATH = path.join(app.getPath('appData'), 'Snapmail') 45 | export const USER_DATA_PATH = path.join(APP_DATA_PATH, 'users'); 46 | 47 | 48 | /** 49 | * in production must point to unpacked versions, not in an asar archive in development 50 | * fall back on defaults in the electron-holochain package 51 | */ 52 | const fileExt = process.platform === 'win32' ? '.exe' : ''; 53 | export const BINARY_PATHS: PathOptions | undefined = app.isPackaged 54 | ? { 55 | holochainRunnerBinaryPath: path.join(__dirname, `../../app/bin/holochain-runner${fileExt}`) 56 | } 57 | : undefined; 58 | 59 | //console.log({BINARY_PATHS}) 60 | 61 | /** Get Versions */ 62 | export const RUNNER_VERSION = getRunnerVersion(BINARY_PATHS?.holochainRunnerBinaryPath) 63 | 64 | export const DNA_VERSION_FILENAME = "dna_version.txt"; 65 | export const MODEL_ZOME_HASH_FILEPATH = 'bin/snapmail_zome_hash.txt'; 66 | 67 | /** WEB FILES PATH */ 68 | export const BACKGROUND_COLOR = '#fbf9f7' 69 | export const ICON_FILEPATH = path.join(CURRENT_DIR, "/web/icon.png") 70 | export const LINUX_ICON_FILE = path.join(__dirname, '../web/icon.png') 71 | export const SPLASH_FILE = path.join(__dirname, '../web/splashscreen.html') 72 | export const MAIN_FILE = path.join(__dirname, '../web/index.html') 73 | /** HTML PAGES URLS */ 74 | export const NETWORK_URL = 'file://' + CURRENT_DIR + '/'+ DIST_DIR +'/networking.html'; 75 | export const ERROR_URL = 'file://' + CURRENT_DIR + '/'+ DIST_DIR +'/error.html'; 76 | /** Shameful */ 77 | 78 | 79 | /** */ 80 | export async function getIndexUrl(): Promise { 81 | return 'file://' + CURRENT_DIR + '/'+ DIST_DIR +'/index.html?ADMIN='+ await getAdminPort() +'&APP='; 82 | } 83 | 84 | 85 | 86 | /** */ 87 | let g_adminPort = null; 88 | export async function getAdminPort(): Promise { 89 | if (g_adminPort === null) { 90 | g_adminPort = await getPortFree(); 91 | } 92 | return g_adminPort; 93 | } 94 | 95 | 96 | import net, {AddressInfo} from "net" 97 | import {HAPP_BUILD_MODE, HappBuildModeType} from "@ddd-qc/lit-happ"; 98 | 99 | async function getPortFree() { 100 | return new Promise( res => { 101 | const srv = net.createServer(); 102 | srv.listen(0, () => { 103 | const port = (srv.address() as AddressInfo).port; 104 | srv.close((err) => res(port)) 105 | }); 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /we-applet/src/createSnapmailApplet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AppAgentWebsocket, decodeHashFromBase64, encodeHashToBase64, 3 | } from "@holochain/client"; 4 | //import { msg } from "@lit/localize"; 5 | import { 6 | RenderInfo, 7 | WeServices, 8 | } from "@lightningrodlabs/we-applet"; 9 | import {AppletViewInfo, ProfilesApi} from "@ddd-qc/we-utils"; 10 | import {SnapmailApp} from "snapmail"; 11 | import {destructureCloneId, ExternalAppProxy} from "@ddd-qc/cell-proxy"; 12 | import { 13 | HCL, 14 | HappElement, 15 | HvmDef, 16 | DvmDef, 17 | DnaViewModel, snake, pascal, 18 | } from "@ddd-qc/lit-happ"; 19 | import {ProfilesDvm, ProfilesProxy} from "@ddd-qc/profiles-dvm"; 20 | import {Profile as ProfileMat} from "@ddd-qc/profiles-dvm/dist/bindings/profiles.types"; 21 | import {decode} from "@msgpack/msgpack"; 22 | 23 | 24 | export interface ViewFileContext { 25 | detail: string, 26 | } 27 | 28 | 29 | /** */ 30 | export async function createSnapmailApplet( 31 | renderInfo: RenderInfo, 32 | weServices: WeServices, 33 | ): Promise { 34 | 35 | if (renderInfo.type =="cross-applet-view") { 36 | throw Error("cross-applet-view not implemented by Snapmail"); 37 | } 38 | 39 | const appletViewInfo = renderInfo as unknown as AppletViewInfo; 40 | const mainAppAgentWs = appletViewInfo.appletClient as AppAgentWebsocket; 41 | 42 | const mainAppWs = mainAppAgentWs.appWebsocket; 43 | 44 | console.log("createSnapmailApplet() client", appletViewInfo.appletClient); 45 | console.log("createSnapmailApplet() thisAppletId", appletViewInfo.appletHash); 46 | 47 | const mainAppInfo = await appletViewInfo.appletClient.appInfo(); 48 | 49 | console.log("createSnapmailApplet() mainAppInfo", mainAppInfo, encodeHashToBase64(mainAppInfo.agent_pub_key)); 50 | 51 | const maybeMyProfile = await grabMyProfile(appletViewInfo); 52 | 53 | 54 | /** Create SnapmailApp */ 55 | const app = new SnapmailApp( 56 | mainAppWs, 57 | undefined, 58 | false, 59 | mainAppInfo.installed_app_id, 60 | appletViewInfo.view, 61 | weServices, 62 | appletViewInfo.appletHash, 63 | maybeMyProfile? maybeMyProfile.nickname : undefined); 64 | console.log("createSnapmailApplet() app", app); 65 | /** Done */ 66 | return app; 67 | } 68 | 69 | 70 | /** */ 71 | async function grabMyProfile(appletViewInfo: AppletViewInfo): Promise { 72 | const profilesClient = appletViewInfo.profilesClient; 73 | const mainAppInfo = await appletViewInfo.appletClient.appInfo(); 74 | 75 | /** Determine profilesAppInfo */ 76 | const mainAppAgentWs = appletViewInfo.appletClient as AppAgentWebsocket; 77 | const mainAppWs = mainAppAgentWs.appWebsocket; 78 | let profilesAppInfo = await profilesClient.client.appInfo(); 79 | console.log("createThreadsApplet() mainAppInfo", mainAppInfo); 80 | console.log("createThreadsApplet() profilesAppInfo", profilesAppInfo, profilesClient.roleName); 81 | 82 | /** Check if roleName is actually a cloneId */ 83 | let maybeCloneId = undefined; 84 | let baseRoleName = profilesClient.roleName; 85 | const maybeBaseRoleName = destructureCloneId(profilesClient.roleName); 86 | if (maybeBaseRoleName) { 87 | baseRoleName = maybeBaseRoleName[0]; 88 | maybeCloneId = profilesClient.roleName; 89 | } 90 | 91 | /** Determine profilesCellProxy */ 92 | const hcl = new HCL(profilesAppInfo.installed_app_id, baseRoleName, maybeCloneId); 93 | const profilesApi = new ProfilesApi(profilesClient); 94 | const profilesAppProxy = new ExternalAppProxy(profilesApi, 10 * 1000); 95 | await profilesAppProxy.fetchCells(profilesAppInfo.installed_app_id, baseRoleName); 96 | const profilesCellProxyGen = await profilesAppProxy.createCellProxy(hcl); 97 | const profilesCellProxy = new ProfilesProxy(profilesCellProxyGen); 98 | console.log("grabMyProfile() profilesCellProxy", profilesCellProxy); 99 | 100 | // /** Create Profiles DVM */ 101 | // const profilesDef: DvmDef = {ctor: ProfilesDvm, baseRoleName, isClonable: false}; 102 | // const profilesDvm: ProfilesDvm = new profilesDef.ctor(this, profilesAppProxy, new HCL(profilesAppInfo.installed_app_id, baseRoleName, maybeCloneId)) as ProfilesDvm; 103 | // console.log("createProfilesDvm() dvm", profilesDvm); 104 | // await this.setupWeProfilesDvm(profilesDvm as ProfilesDvm, encodeHashToBase64(profilesAppInfo.agent_pub_key)); 105 | // const maybeMyProfile = await profilesDvm.profilesZvm.probeProfile(profilesDvm.cell.agentPubKey); 106 | 107 | const rec = await profilesCellProxy.getAgentProfile(decodeHashFromBase64(profilesCellProxy.cell.agentPubKey)); 108 | if (!rec) { 109 | console.log("grabMyProfile() no Profile found") 110 | return undefined; 111 | } 112 | const profile: ProfileMat = decode((rec.entry as any).Present.entry) as ProfileMat; 113 | console.log("grabMyProfile() Profile found:", profile); 114 | 115 | return profile; 116 | } 117 | -------------------------------------------------------------------------------- /.github/workflows/build-electron.yml: -------------------------------------------------------------------------------- 1 | name: build-electron 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | must_sign: 7 | required: false 8 | type: boolean 9 | default: false 10 | 11 | jobs: 12 | # Build the happ 13 | call-build-webhapps: 14 | uses: ./.github/workflows/build-webhapps.yml 15 | 16 | # Build the electron app per platform 17 | build-electron: 18 | needs: call-build-webhapps 19 | strategy: 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | - macos-11 24 | - windows-latest 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v3 28 | # Set NPM shell (windows-only) 29 | - name: Set NPM shell (windows-only) 30 | if: ${{ runner.os == 'Windows' }} 31 | shell: bash 32 | run: | 33 | npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe" 34 | # Setup AzureSignTool for Windows signing 35 | - name: Setup AzureSignTool for Windows signing 36 | if: ${{ runner.os == 'Windows' && inputs.must_sign }} 37 | shell: bash 38 | run: | 39 | dotnet tool install --global AzureSignTool 40 | # Download submodules 41 | - name: Download submodules 42 | shell: bash 43 | run: | 44 | npm run install:submodules 45 | # Download previously uploaded artifacts 46 | - uses: actions/download-artifact@master 47 | with: 48 | name: all-happ-artifact 49 | path: artifacts 50 | # Display artifacts folder 51 | - name: Display artifacts folder 52 | run: ls 53 | working-directory: artifacts 54 | # Dispatch artifacts 55 | - name: Dispatch artifacts 56 | run: | 57 | cp ./artifacts/snapmail.happ ./electron/bin 58 | cp ./artifacts/snapmail_zome_hash.txt ./electron/bin 59 | # Install npm dependencies 60 | - name: Install npm dependencies 61 | shell: bash 62 | run: | 63 | npm install 64 | # list electron/bin 65 | - name: list electron/bin 66 | continue-on-error: true 67 | run: ls 68 | working-directory: electron/bin 69 | # build webapp 70 | - name: build webapp 71 | shell: bash 72 | run: | 73 | npm run build:webapp 74 | # Dist webapp 75 | - name: Dist webapp 76 | run: | 77 | npm run dist -w webapp 78 | # build electron 79 | - name: build electron 80 | shell: bash 81 | run: | 82 | npm run build -w electron 83 | # Setup sign & notarize # HBE_APPLE_CERTIFICATE_PASS 84 | - name: Setup sign and notarize (macos only) 85 | if: ${{ runner.os == 'macOs' && inputs.must_sign }} 86 | uses: figleafteam/import-codesign-certs@v2 87 | with: 88 | p12-file-base64: ${{ secrets.HBE_APPLE_CERTIFICATE_BASE64 }} 89 | p12-password: ${{ secrets.HBE_APPLE_CERTIFICATE_PASS }} 90 | # Dist Electron 91 | - name: Dist Electron 92 | shell: bash 93 | env: 94 | #WIN_CSC_LINK: ${{ steps.write_file.outputs.filePath }} 95 | #WIN_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_PFX_PASSWORD }} 96 | APPLE_DEV_IDENTITY: ${{ secrets.APPLE_DEV_IDENTITY }} 97 | APPLE_ID_EMAIL: ${{ secrets.APPLE_ID_EMAIL }} 98 | APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} 99 | DEBUG: electron-osx-sign*,electron-notarize* 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | run: | 102 | npm run dist:electron 103 | # list out folder 104 | - name: list out-builder folder 105 | continue-on-error: true 106 | run: ls 107 | working-directory: electron/out-builder 108 | # Move binary for each platform 109 | - name: Move binary 110 | env: 111 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 112 | run: | 113 | cp electron/out-builder/Snapmail* ./artifacts 114 | # List artifacts folder 115 | - name: List artifacts folder 116 | run: ls -R 117 | working-directory: artifacts 118 | # Sign Windows certificate 119 | - name: Sign Windows certificate 120 | if: ${{ runner.os == 'Windows' && inputs.must_sign }} 121 | shell: bash 122 | env: 123 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 124 | working-directory: artifacts 125 | run: | 126 | echo exe filename: $(ls|grep "\.exe$") 127 | AzureSignTool sign -kvu "${{ secrets.AZURE_KEY_VAULT_URI }}" -kvi "${{ secrets.AZURE_CLIENT_ID }}" -kvt "${{ secrets.AZURE_TENANT_ID }}" -kvs "${{ secrets.AZURE_CLIENT_SECRET }}" -kvc ${{ secrets.AZURE_CERT_NAME }} -tr http://timestamp.digicert.com -v "$(ls|grep "\.exe$")" 128 | # "upload" artifacts 129 | - uses: actions/upload-artifact@master 130 | with: 131 | name: all-happ-artifact 132 | path: artifacts/ 133 | -------------------------------------------------------------------------------- /webapp/src/snapmail-app.ts: -------------------------------------------------------------------------------- 1 | import { html } from "lit"; 2 | import { state, customElement } from "lit/decorators.js"; 3 | import {ContextProvider} from '@lit/context'; 4 | import {AdminWebsocket, AppWebsocket, InstalledAppId} from "@holochain/client"; 5 | import {DEFAULT_SNAPMAIL_DEF, IS_ELECTRON, SnapmailDvm, weClientContext} from "@snapmail/elements"; 6 | import {HvmDef, HappElement, cellContext} from "@ddd-qc/lit-happ"; 7 | import {AppletHash, AppletView, WeServices} from "@lightningrodlabs/we-applet"; 8 | 9 | 10 | const SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME = "snapmail" 11 | 12 | let HC_APP_PORT: number; 13 | let HC_ADMIN_PORT: number; 14 | 15 | if (IS_ELECTRON) { 16 | const APP_ID = 'snapmail-app' 17 | console.log("URL =", window.location.toString()) 18 | const searchParams = new URLSearchParams(window.location.search); 19 | const urlPort = searchParams.get("APP"); 20 | if(!urlPort) { 21 | console.error("Missing APP value in URL", window.location.search) 22 | } 23 | HC_APP_PORT = Number(urlPort); 24 | const urlAdminPort = searchParams.get("ADMIN"); 25 | HC_ADMIN_PORT = Number(urlAdminPort); 26 | const NETWORK_ID = searchParams.get("UID"); 27 | console.log(NETWORK_ID); 28 | DEFAULT_SNAPMAIL_DEF.id = APP_ID + '-' + NETWORK_ID; // override installed_app_id 29 | } else { 30 | try { 31 | HC_APP_PORT = Number(process.env.HC_APP_PORT); 32 | HC_ADMIN_PORT = Number(process.env.HC_ADMIN_PORT); 33 | } catch (e) { 34 | console.log("HC_APP_PORT not defined") 35 | } 36 | } 37 | 38 | //console.log(" HAPP_ID =", DEFAULT_SNAPMAIL_DEF.id) 39 | console.log(" HC_APP_PORT =", HC_APP_PORT); 40 | console.log("HC_ADMIN_PORT =", HC_ADMIN_PORT); 41 | //console.log(" IS_ELECTRON =", IS_ELECTRON); 42 | 43 | 44 | /** */ 45 | @customElement("snapmail-app") 46 | export class SnapmailApp extends HappElement { 47 | 48 | /** */ 49 | constructor(appWs?: AppWebsocket, 50 | private _adminWs?: AdminWebsocket, 51 | private _canAuthorizeZfns?: boolean, 52 | readonly appId?: InstalledAppId, 53 | public appletView?: AppletView, 54 | private _weServices?: WeServices, 55 | private _appletHash?: AppletHash, 56 | private _startingNickname?: string, 57 | ) { 58 | super(appWs? appWs : HC_APP_PORT, appId); 59 | console.log(" ctor", appId, appWs); 60 | if (_canAuthorizeZfns == undefined) { 61 | this._canAuthorizeZfns = true; 62 | } 63 | if (_weServices) { 64 | console.log(`\t\tProviding context "${weClientContext}" | in host `, _weServices, this); 65 | new ContextProvider(this, weClientContext, _weServices); 66 | } 67 | } 68 | 69 | 70 | static readonly HVM_DEF: HvmDef = DEFAULT_SNAPMAIL_DEF; 71 | 72 | @state() private _loaded = false; 73 | @state() private _hasHolochainFailed = true; 74 | 75 | 76 | get snapmailDvm(): SnapmailDvm { return this.hvm.getDvm(SnapmailDvm.DEFAULT_BASE_ROLE_NAME)! as SnapmailDvm } 77 | 78 | 79 | /** -- Methods -- */ 80 | 81 | /** */ 82 | async hvmConstructed() { 83 | console.log("hvmConstructed()", this._adminWs, this._canAuthorizeZfns) 84 | 85 | /** Authorize all zome calls */ 86 | if (!this._adminWs && this._canAuthorizeZfns) { 87 | this._adminWs = await AdminWebsocket.connect(new URL(`ws://localhost:${HC_ADMIN_PORT}`)); 88 | console.log("hvmConstructed() connect() called", this._adminWs); 89 | } 90 | if (this._adminWs && this._canAuthorizeZfns) { 91 | await this.hvm.authorizeAllZomeCalls(this._adminWs); 92 | console.log("*** Zome call authorization complete"); 93 | } else { 94 | if (!this._canAuthorizeZfns) { 95 | console.warn("No adminWebsocket provided (Zome call authorization done)") 96 | } else { 97 | console.log("Zome call authorization done externally") 98 | } 99 | } 100 | 101 | /** Probe EntryDefs */ 102 | const allAppEntryTypes = await this.snapmailDvm.fetchAllEntryDefs(); 103 | console.log("happInitialized(), allAppEntryTypes", allAppEntryTypes); 104 | console.log(`${SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME} entries`, allAppEntryTypes[SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME]); 105 | if (allAppEntryTypes[SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME].length == 0) { 106 | console.warn(`No entries found for ${SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME}`); 107 | } else { 108 | this._hasHolochainFailed = false; 109 | } 110 | 111 | /** Provide Cell Context */ 112 | //console.log({cell: this.snapmailDvm.cell}); 113 | new ContextProvider(this, cellContext, this.snapmailDvm.cell); 114 | } 115 | 116 | 117 | /** */ 118 | //async perspectiveInitializedOffline(): Promise {} 119 | 120 | 121 | /** */ 122 | async perspectiveInitializedOnline(): Promise { 123 | console.log(".perspectiveInitializedOnline()"); 124 | await this.hvm.probeAll(); 125 | console.log("*** probeAll complete"); 126 | this._loaded = true; 127 | } 128 | 129 | 130 | /** */ 131 | shouldUpdate(): boolean { 132 | const canUpdate = super.shouldUpdate(); 133 | console.log(".shouldUpdate()", canUpdate/*, this._offlinePerspectiveloaded*/); 134 | /** Wait for offlinePerspective */ 135 | return canUpdate /*&& this._offlinePerspectiveloaded*/; 136 | } 137 | 138 | 139 | /** */ 140 | render() { 141 | console.log(".render()", this._loaded) 142 | if (!this._loaded) { 143 | return html`Loading...`; 144 | } 145 | if(this._hasHolochainFailed) { 146 | return html`
147 | ${"Failed to connect to Holochain Conductor and/or \"Snapmail\" cell."}; 148 |
149 | `; 150 | } 151 | 152 | if (this._weServices) { 153 | 154 | } 155 | 156 | /** render page */ 157 | return html``; 158 | //return html`

HI MOM

`; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /electron/src/init.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import {app, dialog} from "electron"; 4 | 5 | import {log} from "./logger"; 6 | import {MODEL_ZOME_HASH_FILEPATH} from "./constants"; 7 | 8 | 9 | /** Prompt fatal error message */ 10 | function fatalError(message: string, error?: any) { 11 | log('error', message); 12 | log('error', error); 13 | dialog.showMessageBoxSync({ 14 | title: 'Snapmail: Fatal error', 15 | buttons: ['exit'], 16 | type: 'error', 17 | message, 18 | detail: JSON.stringify(error), 19 | }); 20 | process.abort(); 21 | } 22 | 23 | 24 | /** */ 25 | export function initApp( 26 | userDataPath: string, 27 | appDataPath: string, 28 | dnaVersionFilename: string, 29 | uidListFilename: string, 30 | ) { 31 | /** Read snapmail_zome_hash.txt in app folder */ 32 | const modelZomeHash = loadModelZomeHash(); 33 | log('info', "MODEL ZOME HASH: " + modelZomeHash); 34 | 35 | /** -- Create missing dirs -- **/ 36 | try { 37 | if (!fs.existsSync(appDataPath)) { 38 | log('info', "Creating missing dir: " + appDataPath); 39 | fs.mkdirSync(appDataPath) 40 | } 41 | if (!fs.existsSync(userDataPath)) { 42 | log('info', "Creating missing dir: " + userDataPath); 43 | fs.mkdirSync(userDataPath) 44 | } 45 | } catch (e) { 46 | fatalError("Failed to create data folders on disk", e) 47 | } 48 | 49 | /** -- Determine Session ID -- **/ 50 | let sessionId; 51 | if (process.argv.length > 2) { 52 | sessionId = process.argv[2]; 53 | } else { 54 | sessionId = 'default'; 55 | } 56 | 57 | /** -- Setup storage folder -- **/ 58 | const sessionDataPath = path.join(userDataPath, sessionId); 59 | log('info', {sessionDataPath}); 60 | setupSessionStorage(sessionDataPath, modelZomeHash, dnaVersionFilename) 61 | 62 | /** -- UID List -- **/ 63 | let uidList: string[] = [] 64 | try { 65 | const uidListPath = path.join(sessionDataPath, uidListFilename); 66 | log('debug', 'Reading file ' + uidListPath); 67 | const uidListStr = fs.readFileSync(uidListPath).toString(); 68 | uidList = uidListStr.replace(/\r\n/g,'\n').split('\n'); 69 | uidList = uidList.filter(function (el) {return el !== '';}); 70 | log('debug', {uidList}); 71 | } catch(err) { 72 | if(err.code === 'ENOENT') { 73 | log('warn', 'File not found: ' + err); 74 | } else { 75 | log('warn','Loading config file failed: ' + err); 76 | } 77 | log('warn','continuing...'); 78 | } 79 | // if (uidList.length == 0) { 80 | // uidList.push(INITIAL_UID) 81 | // } 82 | 83 | //// -- Determine final conductor config file path -- // 84 | //g_configPath = path.join(g_storagePath, CONDUCTOR_CONFIG_FILENAME); 85 | //log('debug', {g_configPath}); 86 | //let g_appConfigPath = path.join(g_storagePath, APP_CONFIG_FILENAME); 87 | 88 | /** Done */ 89 | return {sessionDataPath, uidList} 90 | } 91 | 92 | 93 | /** */ 94 | function setupSessionStorage(sessionPath: string, modelZomeHash: string, dnaVersionFilename: string) { 95 | const dna_version_txt = path.join(sessionPath, dnaVersionFilename); 96 | /** Create storage and setup if none found */ 97 | if (!fs.existsSync(sessionPath)) { 98 | log('info', "Creating missing dir: " + sessionPath); 99 | try { 100 | fs.mkdirSync(sessionPath) 101 | fs.writeFileSync(dna_version_txt, modelZomeHash, 'utf-8'); 102 | log('info', 'dna_version written to disk: ' + modelZomeHash) 103 | } catch(e) { 104 | fatalError("Failed to setup storage folder on disk", e) 105 | } 106 | } else { 107 | /** Make sure it's a compatible version */ 108 | let storedDnaHash = ''; 109 | try { 110 | log('debug', 'Reading: ' + dna_version_txt); 111 | storedDnaHash = fs.readFileSync(dna_version_txt, 'utf-8'); 112 | } catch (e) { 113 | log('error', 'Failed to read the dna_version_txt file !'); 114 | log('error', e); 115 | } 116 | if (storedDnaHash !== modelZomeHash) { 117 | const msg = "The data found on disk is for a different version of Snapmails's data model:\n" + 118 | ' Stored version: ' + storedDnaHash + '\n' + 119 | "This app's version: " + modelZomeHash; 120 | log('error', msg); 121 | const canErase = promptVersionMismatch(msg); 122 | if (canErase) { 123 | try { 124 | fs.rmdirSync(sessionPath, {recursive: true}); 125 | /* Start over */ 126 | setupSessionStorage(sessionPath, modelZomeHash, dnaVersionFilename); 127 | } catch (e) { 128 | fatalError('Failed erasing current stored data', e); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | 136 | /** */ 137 | export function addUidToDisk(newUid: string, sessionDataPath: string, uidListFilename: string,): boolean { 138 | //log('info','addUidToDisk(): ' + newUid); 139 | //log('info','addUidToDisk() sessionDataPath = ' + sessionDataPath); 140 | const uidListPath = path.join(sessionDataPath, uidListFilename); 141 | try { 142 | fs.appendFileSync(uidListPath, newUid + '\n'); 143 | } catch (err) { 144 | log('error','Writing to file failed: ' + err); 145 | return false; 146 | } 147 | return true; 148 | } 149 | 150 | 151 | /** */ 152 | function loadModelZomeHash() { 153 | log('info', 'loadRunningZomeHash: ' + MODEL_ZOME_HASH_FILEPATH) 154 | if(fs.existsSync(MODEL_ZOME_HASH_FILEPATH)) { 155 | return fs.readFileSync(MODEL_ZOME_HASH_FILEPATH, 'utf-8'); 156 | } 157 | if(fs.existsSync('resources/app/' + MODEL_ZOME_HASH_FILEPATH)) { 158 | return fs.readFileSync('resources/app/' + MODEL_ZOME_HASH_FILEPATH, 'utf-8'); 159 | } 160 | if(fs.existsSync(app.getAppPath() + '/' + MODEL_ZOME_HASH_FILEPATH)) { 161 | return fs.readFileSync(app.getAppPath() + '/' + MODEL_ZOME_HASH_FILEPATH, 'utf-8'); 162 | } 163 | fatalError("Corrupt installation. Missing zome hash file."); 164 | } 165 | 166 | 167 | /** Return true if user wants to erase stored data */ 168 | function promptVersionMismatch(message: string) { 169 | const result = dialog.showMessageBoxSync({ 170 | title: `${app.getName()} - v${app.getVersion()}`, 171 | message: `Version mismatch`, 172 | detail: message, 173 | type: "warning", 174 | defaultId: 0, 175 | buttons: ['Erase stored data', 'Continue anyway', 'Exit'], 176 | }); 177 | switch (result) { 178 | case 0: { 179 | return true; 180 | break; 181 | } 182 | case 1: { 183 | return false; 184 | break; 185 | } 186 | case 2: { 187 | app.exit(); 188 | break; 189 | } 190 | default: 191 | break; 192 | } 193 | return false; 194 | } 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Snapmail", 3 | "version": "0.4.0", 4 | "config": { 5 | "hc_version": "0.2.4", 6 | "zits_version": "1.12.0", 7 | "bin": "bin", 8 | "we_test_folder": "../we-electron" 9 | }, 10 | "private": true, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/glassbeadsoftware/snapmail.git" 14 | }, 15 | "workspaces": [ 16 | "we-applet", 17 | "webcomponents", 18 | "webapp" 19 | ], 20 | "scripts": { 21 | "postinstall" : "mkdir -p artifacts", 22 | "install:submodules" : "bash scripts/npm/install-submodules.sh $npm_package_config_hc_version", 23 | "install:rust" : "bash submodules/hc-prebuilt/install-rust.sh", 24 | "install:hash-zome" : "bash submodules/hc-prebuilt/install-hash-zome.sh $npm_package_config_bin", 25 | "install:hc" : "bash submodules/hc-prebuilt/install-hc-cli.sh $npm_package_config_bin $npm_package_config_hc_version", 26 | "install:zits" : "cargo install zits --version $npm_package_config_zits_version", 27 | 28 | "version" : "$npm_package_config_bin/hc --version && holochain --version && lair-keystore --version", 29 | "hash-zome" : "bash submodules/hc-prebuilt/hash-zome.sh $npm_package_config_bin ./submodules/snapmail-rsm/target/wasm32-unknown-unknown/release/snapmail_model.wasm artifacts/snapmail_zome_hash.txt && cp artifacts/snapmail_zome_hash.txt electron/bin", 30 | 31 | "build:happ" : "bash scripts/npm/dist-dna.sh $npm_package_config_bin && npm run hash-zome", 32 | 33 | "build:webapp" : "npm run build -w webcomponents && npm run build -w webapp", 34 | 35 | "prepare-devtest" : "npm run clean:agents && npm run build:happ && bash scripts/ts-bindings.sh && npm run build:webapp", 36 | "devtest" : "npm run prepare-devtest && npm run start:mem", 37 | "devtest:we" : "npm run prepare-devtest && npm run dist -w webapp && HAPP_BUILD_MODE='Debug' npm run devtest -w we-applet", 38 | "devtest:electron" : "npm run prepare-devtest && npm run devtest -w electron", 39 | 40 | "prodtest" : "npm run clean:agents && npm run dist -w webapp && HC_PORT=$(port) ADMIN_PORT=$(port) concurrently \"npm run new-sandbox:local\" \"sleep 8 && npm run start:ui -w webapp\"", 41 | "prodtest:electron" : "npm run clean:agents && npm run build -w webcomponents && npm run dist -w webapp && npm run prodtest -w electron", 42 | 43 | "prodtestfull:we" : "npm run prepare-devtest && npm run dist:we && npm run prodtest:we", 44 | "prodtest:we" : "cross-env BOOT_PORT=$(port) SIGNAL_PORT=$(port) concurrently \"npm run serve:run\" \"cross-env AGENT_NUM=1 npm run prodtest:we-launch\"", 45 | "prodtest:we-launch" : "/c/Users/damien/AppData/Local/Programs/org.lightningrodlabs.we-electron-alpha/lightningrodlabs-we \"-b http://127.0.0.1:$BOOT_PORT\" \"-s ws://127.0.0.1:$SIGNAL_PORT\" --dev-config we.prodtest.config.json --agent-num $AGENT_NUM", 46 | 47 | "new-sandbox:mem" : "RUST_LOG=warn echo \"pass\" | $npm_package_config_bin/hc s --piped -f=$ADMIN_PORT generate artifacts/snapmail.happ --run=$HC_PORT -a snapmail network mem", 48 | "new-sandbox:local" : "RUST_LOG=warn echo \"pass\" | $npm_package_config_bin/hc s --piped -f=$ADMIN_PORT generate artifacts/snapmail.happ --run=$HC_PORT -a snapmail network --bootstrap http://127.0.0.1:$BOOT_PORT webrtc ws://127.0.0.1:$SIGNAL_PORT", 49 | 50 | "start:mem" : "cross-env HC_PORT=$(port) ADMIN_PORT=$(port) HAPP_BUILD_MODE='Debug' concurrently \"npm run build:watch -w webcomponents\" \"npm run new-sandbox:mem\" \"sleep 7 && npm run start:ui -w webapp\"", 51 | "start:local" : "cross-env HC_PORT=$(port) ADMIN_PORT=$(port) HAPP_BUILD_MODE='Debug' concurrently \"npm run build:watch -w webcomponents\" \"npm run new-sandbox:local\" \"sleep 7 && npm run start:ui -w webapp\"", 52 | 53 | 54 | "serve" : "cross-env BOOT_PORT=$(cat .hc_bootenv) SIGNAL_PORT=$(cat .hc_signalenv) npm run serve:run", 55 | "serve:run" : "hc run-local-services --bootstrap-port $BOOT_PORT --signal-port $SIGNAL_PORT", 56 | "serve:save" : "echo SIGNAL_PORT=$SIGNAL_PORT && echo $SIGNAL_PORT > .hc_signalenv && echo $BOOT_PORT > .hc_bootenv && echo BOOT_PORT=$BOOT_PORT", 57 | 58 | "restart" : "HAPP_BUILD_MODE='Debug' HC_PORT=$(port) ADMIN_PORT=$(port) concurrently \"npm run restart:happ\" \"sleep 8 && npm run start:ui -w webapp\"", 59 | "restart:happ" : "RUST_LOG=warn echo \"pass\" | $npm_package_config_bin/hc s --piped -f=$ADMIN_PORT run $SINDEX -p=$HC_PORT", 60 | "network" : "npm run clean:agents && concurrently-repeat \"npm run start:local\"", 61 | 62 | "start:launcher" : "echo pass | hc-launch --piped -n 1 artifacts/snapmail.webhapp network mdns", 63 | 64 | "dist:we" : "npm run dist -w we-applet && npm run package:we && npm run cp:we", 65 | "package:we" : "rm -rf artifacts/snapmail-we_applet-ui.zip & cd we-applet/dist && bestzip ../../artifacts/snapmail-we_applet-ui.zip * && cd ../.. && $npm_package_config_bin/hc web-app pack we-applet/webhapp.workdir -o artifacts/snapmail-we_applet.webhapp", 66 | "cp:we" : "cp artifacts/snapmail-we_applet.webhapp $npm_package_config_we_test_folder/testing-applets", 67 | 68 | "dist:electron" : "bash submodules/hc-prebuilt/copy-binaries.sh electron/bin && npm run dist:build -w electron", 69 | 70 | "package:webapp" : "rm -rf artifacts/snapmail-webapp-ui.zip & cd webapp/dist && bestzip ../../artifacts/snapmail-webapp-ui.zip * && cd ../.. && $npm_package_config_bin/hc web-app pack webapp/webhapp.workdir -o artifacts/snapmail.webhapp", 71 | 72 | "update-version" : "bash scripts/update-version-number.sh $npm_package_version", 73 | 74 | "dl-test-release" : "rm -rf release & mkdir -p release & gh release download workflow-test -D release -R glassbeadsoftware/snapmail", 75 | 76 | "clean" : "bash scripts/npm/clean.sh", 77 | "clean:npm" : "bash scripts/npm/clean-npm.sh", 78 | "clean:agents" : "rm -rf .hc*", 79 | "clean:all" : "npm run clean:agents & npm run clean:npm & npm run clean" 80 | }, 81 | "devDependencies": { 82 | "@types/ws": "^8.5.3", 83 | "bestzip": "^2.2.0", 84 | "concurrently": "^6.2.1", 85 | "concurrently-repeat": "^0.0.1", 86 | "cross-env": "^7.0.3", 87 | "new-port-cli": "^1.0.0", 88 | "tslib": "^2.4.0", 89 | "@lit/localize-tools": "^0.6.3", 90 | "typescript": "~5.2.0" 91 | }, 92 | "dependencies": { 93 | "@holochain/client": "=0.16.7", 94 | "@ddd-qc/cell-proxy": "=0.20.8", 95 | "@ddd-qc/lit-happ": "=0.20.8", 96 | "@ddd-qc/profiles-dvm": "=0.20.7", 97 | "@ddd-qc/we-utils": "=0.21.2", 98 | "@lightningrodlabs/we-applet": "0.14.0", 99 | "@holochain-open-dev/core-types": "0.7.0", 100 | "@lit/context": "1.0.1", 101 | "@lit/localize": "0.12.1", 102 | "lit": "3.0.2" 103 | }, 104 | "engines": { 105 | "npm": ">=7.0.0" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /webcomponents/src/elements/snapmail-att-view.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from "lit"; 2 | import {Grid, GridActiveItemChangedEvent, GridColumn} from "@vaadin/grid"; 3 | import { state, property,customElement } from "lit/decorators.js"; 4 | import {FileManifest, FindManifestOutput, Mail, MailItem} from "../bindings/snapmail.types"; 5 | import {SnapmailPerspective} from "../viewModel/snapmail.perspective"; 6 | import {base64ToArrayBuffer} from "../utils"; 7 | import {redStopEmoji, hourGlassEmoji, stylesTemplate, greenCheckEmoji} from "../constants"; 8 | import {AttGridItem} from "../mail"; 9 | import {ZomeElement} from "@ddd-qc/lit-happ"; 10 | import {SnapmailZvm} from "../viewModel/snapmail.zvm"; 11 | import {GridItemModel} from "@vaadin/grid/src/vaadin-grid"; 12 | 13 | 14 | /** */ 15 | @customElement("snapmail-att-view") 16 | export class SnapmailAttView extends ZomeElement { 17 | constructor() { 18 | super(SnapmailZvm.DEFAULT_ZOME_NAME); 19 | } 20 | 21 | @property({type: Object}) 22 | inMailItem: MailItem; 23 | 24 | @state() _items: AttGridItem[] = [] 25 | @state() _selectedItems: AttGridItem[] = []; 26 | 27 | 28 | /** -- Getter -- */ 29 | 30 | get attachmentGridElem() : Grid { 31 | return this.shadowRoot.getElementById("attachmentGrid") as Grid; 32 | } 33 | 34 | 35 | /** -- Methods -- */ 36 | 37 | /** */ 38 | protected firstUpdated(_changedProperties: PropertyValues) { 39 | super.firstUpdated(_changedProperties); 40 | 41 | this.attachmentGridElem.cellClassNameGenerator = (column, rowData: GridItemModel) => { 42 | //console.log({rowData}) 43 | let classes = ''; 44 | if (!rowData.item.hasFile) { 45 | classes += ' pending'; 46 | } else { 47 | //classes += ' newmail'; 48 | } 49 | return classes; 50 | }; 51 | 52 | this.attachmentGridElem.shadowRoot.appendChild(stylesTemplate.content.cloneNode(true)); 53 | } 54 | 55 | 56 | /** Return manifest with added content field */ 57 | async fetchFile(contentHash: string): Promise { 58 | let manifest: FindManifestOutput; 59 | try { 60 | manifest = await this._zvm.findManifest(contentHash); 61 | } catch(e) { 62 | return null; 63 | } 64 | const chunks = []; 65 | for (const chunkAddress of manifest.chunks) { 66 | try { 67 | const chunk = await this._zvm.getChunk(chunkAddress); 68 | chunks.push(chunk); 69 | } catch (e) { 70 | return null; 71 | } 72 | } 73 | /** concat chunks */ 74 | let content = ''; 75 | for (const chunk of chunks) { 76 | content += chunk; 77 | } 78 | manifest.content = content; 79 | return manifest; 80 | } 81 | 82 | 83 | /** */ 84 | async fillAttachmentGrid(mail: Mail): Promise { 85 | /** Convert each attachment to gridItem */ 86 | const items: AttGridItem[] = []; 87 | let missingCount = 0; 88 | console.log(" fillAttachmentGrid()", mail.attachments.length); 89 | for (const attachmentInfo of mail.attachments) { 90 | /** Check if attachment is available in local source-chain */ 91 | let hasDownloadedAttachment = false; 92 | try { 93 | /*const fileManifest =*/ await this._zvm.getManifest(attachmentInfo.manifest_eh); 94 | hasDownloadedAttachment = true; 95 | } catch (_e) { 96 | // TODO error message? 97 | missingCount += 1; 98 | } 99 | /** Convert to GridItem */ 100 | const item: AttGridItem = { 101 | fileId: attachmentInfo.data_hash, 102 | filename: attachmentInfo.filename, 103 | filesize: Math.ceil(attachmentInfo.orig_filesize / 1024), 104 | filetype: attachmentInfo.filetype, 105 | status: hasDownloadedAttachment? ' ' : redStopEmoji, 106 | hasFile: hasDownloadedAttachment, 107 | }; 108 | items.push(item); 109 | } 110 | 111 | /** Reset grid */ 112 | //console.log({items}) 113 | this._items = items; 114 | this._selectedItems = []; 115 | //this._activeItem = null; 116 | 117 | /** Done */ 118 | console.log(" fillAttachmentGrid() missingCount", missingCount); 119 | return missingCount; 120 | } 121 | 122 | 123 | /** */ 124 | async onActiveChanged(item: AttGridItem): Promise { 125 | console.log(" .onActiveChanged()", item) 126 | //this._activeItem = null; 127 | this._selectedItems = []; 128 | 129 | if (!item || !item.hasFile) { 130 | return; 131 | } 132 | 133 | if (!this._selectedItems.includes(item)) { 134 | item.status = hourGlassEmoji; 135 | this._selectedItems.push(item); 136 | item.disabled = true; 137 | } 138 | 139 | /** Get File on source chain */ 140 | const manifest: FileManifest | null = await this.fetchFile(item.fileId) 141 | 142 | if (!manifest) { 143 | return; 144 | } 145 | //console.log({ manifest }) 146 | item.status = greenCheckEmoji; 147 | //this.attachmentGridElem.deselectItem(item); 148 | 149 | /** DEBUG - check if content is valid base64 */ 150 | // if (!base64regex.test(manifest.content)) { 151 | // const invalid_hash = sha256(manifest.content); 152 | // console.error("File '" + manifest.filename + "' is invalid base64. hash is: " + invalid_hash); 153 | // } 154 | 155 | let filetype = manifest.filetype; 156 | const fields = manifest.filetype.split(':'); 157 | if (fields.length > 1) { 158 | const types = fields[1].split(';'); 159 | filetype = types[0]; 160 | } 161 | const byteArray = base64ToArrayBuffer(manifest.content) 162 | const blob = new Blob([byteArray], { type: filetype}); 163 | const url = URL.createObjectURL(blob); 164 | const a = document.createElement('a'); 165 | a.href = url; 166 | a.download = item.filename || 'download'; 167 | a.addEventListener('click', () => {}, false); 168 | a.click(); 169 | //this._activeItem = null; 170 | this._selectedItems = []; 171 | } 172 | 173 | 174 | // /** debug */ 175 | // updated() { 176 | // console.log(" .updated()", this._items); 177 | // } 178 | 179 | 180 | /** */ 181 | async willUpdate(changedProperties: PropertyValues): Promise { 182 | //console.log(".willUpdate()", changedProperties) 183 | if (changedProperties.has('inMailItem')) { 184 | const missingCount = await this.fillAttachmentGrid(this.inMailItem.mail); 185 | if (missingCount > 0) { 186 | await this._zvm.getMissingAttachments(this.inMailItem.author, this.inMailItem.ah); 187 | } 188 | } 189 | } 190 | 191 | 192 | /** */ 193 | render() { 194 | return html` 195 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | `; 207 | } 208 | 209 | 210 | /** */ 211 | static get styles() { 212 | return [ 213 | css` 214 | `]; 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /webcomponents/src/mail.ts: -------------------------------------------------------------------------------- 1 | /** Functions for manipulating mailItems */ 2 | 3 | import {ActionHashB64, AgentPubKey, encodeHashToBase64} from "@holochain/client"; 4 | 5 | import {MailItem} from "./bindings/snapmail.types"; 6 | import {UsernameMap} from "./viewModel/snapmail.perspective"; 7 | import {HAPP_BUILD_MODE, HappBuildModeType} from "@ddd-qc/lit-happ"; 8 | 9 | const checkMarkEmoji = String.fromCodePoint(0x2714); //FE0F 10 | const suspensionPoints = String.fromCodePoint(0x2026); 11 | const returnArrowEmoji = String.fromCodePoint(0x21A9); 12 | export const paperClipEmoji = String.fromCodePoint(0x1F4CE) 13 | 14 | 15 | export interface MailGridItem { 16 | id: ActionHashB64, 17 | username: string, 18 | subject: string, 19 | date: string, 20 | attachment: string 21 | status: string, 22 | content: string, 23 | mailItem: MailItem, 24 | } 25 | 26 | export interface AttGridItem { 27 | fileId: string, 28 | filename: string, 29 | filesize: number, 30 | filetype: string, 31 | status: string, 32 | hasFile: boolean, 33 | disabled?: boolean, 34 | } 35 | 36 | 37 | /** 38 | * All Folders for fileBox 39 | * @type {{ALL: string, TRASH: string, SENT: string, INBOX: string}} 40 | */ 41 | export const systemFolders = { 42 | ALL: String.fromCodePoint(0x1F4C1) + ' All', 43 | INBOX: String.fromCodePoint(0x1F4E5) + ' Inbox', 44 | SENT: String.fromCodePoint(0x1F4E4) + ' Sent', 45 | TRASH: String.fromCodePoint(0x1F5D1) + ' Trash' 46 | }; 47 | 48 | 49 | /** Return True if mail has been deleted */ 50 | export function isMailDeleted(mailItem: MailItem): boolean { 51 | //console.log({isMailDeleted_mail: mailItem}) 52 | if ("In" in mailItem.state) { 53 | const inState = mailItem.state.In; 54 | //console.log({inState}) 55 | return 'Deleted' in inState; 56 | } 57 | if ("Out" in mailItem.state) { 58 | const outState = mailItem.state.Out; 59 | //console.log({outState}) 60 | return 'Deleted' in outState; 61 | } 62 | console.error('isMailDeleted() Invalid mailItem object', mailItem) 63 | return false; 64 | } 65 | 66 | 67 | /** Return True if mail is an OutMail */ 68 | export function is_OutMail(mailItem: MailItem): boolean { 69 | if ("In" in mailItem.state) { 70 | return false; 71 | } 72 | if ("Out" in mailItem.state) { 73 | return true; 74 | } 75 | console.error('is_OutMail() Invalid mailItem object', mailItem) 76 | return false; 77 | } 78 | 79 | 80 | /** 81 | * Return True if mail has been acknowledged by this agent 82 | */ 83 | export function hasMailBeenOpened(mailItem: MailItem) { 84 | if (is_OutMail(mailItem)) { 85 | return true; 86 | } 87 | if ("In" in mailItem.state) { 88 | const inState = mailItem.state.In; 89 | return !('Unacknowledged' in inState); 90 | } 91 | console.error('hasMailBeenOpened() Invalid mailItem object') 92 | return false; 93 | } 94 | 95 | 96 | /** Return mailItem class */ 97 | export function determineMailCssClass(mailItem: MailItem): string { 98 | if ("Out" in mailItem.state) { 99 | const outMailState = mailItem.state.Out; 100 | if ('Unsent' in outMailState) return ''; // 'pending'; 101 | if ('AllSent' in outMailState) return ''; // 'partially'; 102 | if ('AllReceived' in outMailState) return ''; 103 | if ('AllAcknowledged' in outMailState) return ''; // 'received'; 104 | if ('Deleted' in outMailState) return 'deleted'; 105 | return outMailState === "Deleted" ? 'deleted' : ''; 106 | } 107 | 108 | if ("In" in mailItem.state) { 109 | const inState = mailItem.state.In; 110 | if ('Unacknowledged' in inState) return 'newmail'; 111 | if ('AckUnsent' in inState) return ''; //'pending'; 112 | if ('AckPending' in inState) return ''; // 'partially'; 113 | if ('AckDelivered' in inState) return ''; // 'received'; 114 | if ('Deleted' in inState) return 'deleted'; 115 | } 116 | console.error('determineMailCssClass() Invalid mailItem object', mailItem); 117 | } 118 | 119 | 120 | /** */ 121 | export function customDateString(unixTimestamp: number): string { 122 | const date = new Date(unixTimestamp * 1000); 123 | let hours: number | string = date.getHours(); 124 | let minutes: number | string = date.getMinutes(); 125 | if (minutes < 10) { 126 | minutes = '0' + minutes 127 | } 128 | if (hours < 10) { 129 | hours = '0' + hours 130 | } 131 | const dday = `${date.toDateString()}, ${hours}:${minutes}` 132 | return dday 133 | } 134 | 135 | 136 | /** */ 137 | function vecToUsernames(usernameMap: UsernameMap, agentVec: AgentPubKey[]): string { 138 | let line = ''; 139 | for (const item of agentVec) { 140 | if (line.length > 0) { 141 | line += ','; 142 | } 143 | line += ' ' + getUsername(usernameMap, item); 144 | } 145 | return line; 146 | } 147 | 148 | 149 | /** */ 150 | function getUsername(usernameMap: UsernameMap, agentHash: Uint8Array): string { 151 | const authorId = encodeHashToBase64(agentHash); 152 | let username = usernameMap[authorId] 153 | if (username === undefined) { 154 | username = "<" + authorId.substring(0, 8) + "...>"; 155 | } 156 | return username; 157 | } 158 | 159 | 160 | /** Determine which Username to display (recipient or author) */ 161 | function determineFromLine(usernameMap: UsernameMap, mailItem: MailItem): string { 162 | /* Outmail special case */ 163 | if (is_OutMail(mailItem)) { 164 | if (mailItem.mail.to.length > 0) { 165 | return 'To: ' + vecToUsernames(usernameMap, mailItem.mail.to) 166 | } else if (mailItem.mail.cc.length > 0) { 167 | return 'To: ' + vecToUsernames(usernameMap, mailItem.mail.cc) 168 | } else if (mailItem.bcc && mailItem.bcc.length > 0) { 169 | return 'To: ' + vecToUsernames(usernameMap, mailItem.bcc) 170 | } 171 | } 172 | return getUsername(usernameMap, mailItem.author); 173 | } 174 | 175 | 176 | /** Return mailItem status icon */ 177 | export function determineMailStatus(mailItem: MailItem): string { 178 | //console.log('determineMailStatus()', encodeHashToBase64(mailItem.ah)); 179 | const state = mailItem.state; 180 | // console.log("determineMailStatus() state", mailItem.state); 181 | if ("Out" in state) { 182 | const outMailState = state.Out; 183 | if ('Unsent' in outMailState) return suspensionPoints; 184 | if ('AllSent' in outMailState) return suspensionPoints; 185 | if ('AllReceived' in outMailState) return checkMarkEmoji; 186 | if ('AllAcknowledged' in outMailState) return checkMarkEmoji; 187 | if ('Deleted' in outMailState) return ''; 188 | } else { 189 | if ("In" in state) { 190 | if (mailItem.reply) { 191 | return returnArrowEmoji; 192 | } 193 | } 194 | } 195 | return ''; 196 | } 197 | 198 | 199 | 200 | 201 | /** */ 202 | export function into_gridItem(usernameMap: UsernameMap, mailItem: MailItem): MailGridItem { 203 | /* username */ 204 | // console.log('into_gridItem: ' + encodeHashToBase64(mailItem.author) + ' username: ' + username); 205 | const username = determineFromLine(usernameMap, mailItem); 206 | /* Date */ 207 | const dateStr = customDateString(mailItem.date) 208 | /* Attachment Status */ 209 | const attachmentStatus = mailItem.mail.attachments.length > 0? paperClipEmoji : ''; 210 | /* Status */ 211 | const status = determineMailStatus(mailItem); 212 | // Done 213 | const item: MailGridItem = { 214 | id: encodeHashToBase64(mailItem.ah), 215 | username: username, 216 | subject: mailItem.mail.subject == "" ? "" : mailItem.mail.subject, 217 | date: dateStr, 218 | attachment: attachmentStatus, 219 | status: status, 220 | content: mailItem.mail.payload, 221 | mailItem, 222 | }; 223 | return item; 224 | } 225 | 226 | 227 | /** */ 228 | export function into_mailText(usernameMap: UsernameMap, mailItem: MailItem): string { 229 | const subject = mailItem.mail.subject == "" ? "" : mailItem.mail.subject; 230 | const content = mailItem.mail.payload == "" ? "" : mailItem.mail.payload; 231 | 232 | let intext = 'Subject: ' + subject + '\n\n' 233 | + content + '\n\n' 234 | + 'Mail from: ' + usernameMap[encodeHashToBase64(mailItem.author)] + ' at ' + customDateString(mailItem.date); 235 | 236 | const to_line = vecToUsernames(usernameMap, mailItem.mail.to); 237 | 238 | const can_cc = mailItem.mail.cc.length > 0; 239 | const cc_line = vecToUsernames(usernameMap, mailItem.mail.cc); 240 | 241 | const can_bcc = mailItem.bcc.length > 0; 242 | const bcc_line = vecToUsernames(usernameMap, mailItem.bcc); 243 | 244 | intext += '\nTo: ' + to_line; 245 | if (can_cc) { 246 | intext += '\nCC: ' + cc_line; 247 | } 248 | if (can_bcc) { 249 | intext += '\nBCC: ' + bcc_line; 250 | } 251 | 252 | /** Debug info */ 253 | if (HAPP_BUILD_MODE == HappBuildModeType.Debug) { 254 | intext += '\n\nDEBUG INFO'; 255 | intext += '\nState: ' + JSON.stringify(mailItem.state); 256 | intext += '\nActionHash: ' + encodeHashToBase64(mailItem.ah); 257 | intext += '\nReply: ' + JSON.stringify(mailItem.reply); 258 | intext += '\nstatus: ' + JSON.stringify(mailItem.status); 259 | intext += '\nFiles: ' + mailItem.mail.attachments.length; 260 | } 261 | 262 | return intext; 263 | } 264 | -------------------------------------------------------------------------------- /webcomponents/src/bindings/snapmail.proxy.ts: -------------------------------------------------------------------------------- 1 | /* This file is generated by zits. Do not edit manually */ 2 | 3 | import {CAN_DM, CHUNK_MAX_SIZE, DIRECT_SEND_CHUNK_TIMEOUT_MS, DIRECT_SEND_TIMEOUT_MS, Directory, FILE_MAX_SIZE, REMOTE_ENDPOINT, SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME, SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME, SNAPMAIL_DEFAULT_ROLE_NAME, DeliveryState, DirectMessageProtocol, InMailState, LinkKind, MailState, OutMailState, RecipientKind, SignalProtocol, SnapmailEntry, AckMessage, AttachmentInfo, CommitPendingAckInput, CommitPendingMailInput, DeliveryConfirmation, DmPacket, FileChunk, FileManifest, FindManifestOutput, GetMailOutput, GetMissingAttachmentsInput, GetMissingChunksInput, Handle, HandleItem, InAck, InMail, Mail, MailItem, MailMessage, OutAck, OutMail, PendingAck, PendingMail, PubEncKey, SendMailInput, SnapmailSignal, WriteManifestInput, ZomeManifestVec, } from './snapmail.types'; 4 | import { 5 | /** types.ts */ 6 | HoloHash, 7 | AgentPubKey, 8 | DnaHash, 9 | WasmHash, 10 | EntryHash, 11 | ActionHash, 12 | AnyDhtHash, 13 | ExternalHash, 14 | KitsuneAgent, 15 | KitsuneSpace, 16 | HoloHashB64, 17 | AgentPubKeyB64, 18 | DnaHashB64, 19 | WasmHashB64, 20 | EntryHashB64, 21 | ActionHashB64, 22 | AnyDhtHashB64, 23 | InstalledAppId, 24 | Signature, 25 | CellId, 26 | DnaProperties, 27 | RoleName, 28 | InstalledCell, 29 | Timestamp, 30 | Duration, 31 | HoloHashed, 32 | NetworkInfo, 33 | FetchPoolInfo, 34 | /** hdk/action.ts */ 35 | SignedActionHashed, 36 | RegisterAgentActivity, 37 | ActionHashed, 38 | ActionType, 39 | Action, 40 | NewEntryAction, 41 | Dna, 42 | AgentValidationPkg, 43 | InitZomesComplete, 44 | CreateLink, 45 | DeleteLink, 46 | OpenChain, 47 | CloseChain, 48 | Update, 49 | Delete, 50 | Create, 51 | /** hdk/capabilities.ts */ 52 | CapSecret, 53 | CapClaim, 54 | GrantedFunctionsType, 55 | GrantedFunctions, 56 | ZomeCallCapGrant, 57 | CapAccessType, 58 | CapAccess, 59 | CapGrant, 60 | ///** hdk/countersigning.ts */ 61 | //CounterSigningSessionData, 62 | //PreflightRequest, 63 | //CounterSigningSessionTimes, 64 | //ActionBase, 65 | //CounterSigningAgents, 66 | //PreflightBytes, 67 | //Role, 68 | //CountersigningAgentState, 69 | /** hdk/dht-ops.ts */ 70 | DhtOpType, 71 | DhtOp, 72 | getDhtOpType, 73 | getDhtOpAction, 74 | getDhtOpEntry, 75 | getDhtOpSignature, 76 | /** hdk/entry.ts */ 77 | EntryVisibility, 78 | AppEntryDef, 79 | EntryType, 80 | EntryContent, 81 | Entry, 82 | /** hdk/record.ts */ 83 | Record as HcRecord, 84 | RecordEntry as HcRecordEntry, 85 | /** hdk/link.ts */ 86 | AnyLinkableHash, 87 | ZomeIndex, 88 | LinkType, 89 | LinkTag, 90 | RateWeight, 91 | RateBucketId, 92 | RateUnits, 93 | Link, 94 | /** api/admin/types.ts */ 95 | InstalledAppInfoStatus, 96 | DeactivationReason, 97 | DisabledAppReason, 98 | StemCell, 99 | ProvisionedCell, 100 | ClonedCell, 101 | CellType, 102 | CellInfo, 103 | AppInfo, 104 | MembraneProof, 105 | FunctionName, 106 | ZomeName, 107 | ZomeDefinition, 108 | IntegrityZome, 109 | CoordinatorZome, 110 | DnaDefinition, 111 | ResourceBytes, 112 | ResourceMap, 113 | CellProvisioningStrategy, 114 | CellProvisioning, 115 | DnaVersionSpec, 116 | DnaVersionFlexible, 117 | AppRoleDnaManifest, 118 | AppRoleManifest, 119 | AppManifest, 120 | AppBundle, 121 | AppBundleSource, 122 | NetworkSeed, 123 | ZomeLocation, 124 | } from '@holochain/client'; 125 | 126 | import { 127 | /** Common */ 128 | DhtOpHashB64, 129 | //DnaHashB64, (duplicate) 130 | //AnyDhtHashB64, (duplicate) 131 | DhtOpHash, 132 | /** DnaFile */ 133 | DnaFile, 134 | DnaDef, 135 | Zomes, 136 | WasmCode, 137 | /** entry-details */ 138 | EntryDetails, 139 | RecordDetails, 140 | Details, 141 | DetailsType, 142 | EntryDhtStatus, 143 | /** Validation */ 144 | ValidationStatus, 145 | ValidationReceipt, 146 | } from '@holochain-open-dev/core-types'; 147 | 148 | import {ZomeProxy} from '@ddd-qc/lit-happ'; 149 | import {snapmailFunctionNames} from './snapmail.fn'; 150 | 151 | /** 152 | * 153 | */ 154 | export class SnapmailProxy extends ZomeProxy { 155 | static readonly DEFAULT_ZOME_NAME = "snapmail" 156 | static readonly FN_NAMES = snapmailFunctionNames 157 | 158 | async initCaps(): Promise { 159 | return this.call('init_caps', null); 160 | } 161 | 162 | 163 | 164 | 165 | async receiveDm(dmPacket: DmPacket): Promise { 166 | return this.call('receive_dm', dmPacket); 167 | } 168 | 169 | async findManifest(dataHash: string): Promise { 170 | return this.call('find_manifest', dataHash); 171 | } 172 | 173 | async getAllManifests(): Promise { 174 | return this.call('get_all_manifests', null); 175 | } 176 | 177 | async getChunk(chunkEh: EntryHash): Promise { 178 | return this.call('get_chunk', chunkEh); 179 | } 180 | 181 | async getManifest(manifestAddress: AnyDhtHash): Promise { 182 | return this.call('get_manifest', manifestAddress); 183 | } 184 | 185 | async getMissingAttachments(input: GetMissingAttachmentsInput): Promise { 186 | return this.call('get_missing_attachments', input); 187 | } 188 | 189 | async getMissingChunks(input: GetMissingChunksInput): Promise { 190 | return this.call('get_missing_chunks', input); 191 | } 192 | 193 | async writeChunk(inputChunk: FileChunk): Promise { 194 | return this.call('write_chunk', inputChunk); 195 | } 196 | 197 | async writeManifest(input: WriteManifestInput): Promise { 198 | return this.call('write_manifest', input); 199 | } 200 | 201 | async getEncKey(from: AgentPubKey): Promise { 202 | return this.call('get_enc_key', from); 203 | } 204 | 205 | async getMyEncKey(): Promise { 206 | return this.call('get_my_enc_key', null); 207 | } 208 | 209 | async testEncryption(to: AgentPubKey): Promise { 210 | return this.call('test_encryption', to); 211 | } 212 | 213 | async findAgent(handle: string): Promise { 214 | return this.call('find_agent', handle); 215 | } 216 | 217 | async getAllHandles(): Promise { 218 | return this.call('get_all_handles', null); 219 | } 220 | 221 | async getHandle(agentId: AgentPubKey): Promise { 222 | return this.call('get_handle', agentId); 223 | } 224 | 225 | async getMyHandle(): Promise { 226 | return this.call('get_my_handle', null); 227 | } 228 | 229 | async getMyHandleHistory(initialHandleAddress: ActionHash): Promise { 230 | return this.call('get_my_handle_history', initialHandleAddress); 231 | } 232 | 233 | async pingAgent(destination: AgentPubKey): Promise { 234 | return this.call('ping_agent', destination); 235 | } 236 | 237 | async createEmptyHandle(): Promise { 238 | return this.call('create_empty_handle', null); 239 | } 240 | 241 | async setHandle(newUsername: string): Promise { 242 | return this.call('set_handle', newUsername); 243 | } 244 | 245 | async acknowledgeMail(inmailAh: ActionHash): Promise { 246 | return this.call('acknowledge_mail', inmailAh); 247 | } 248 | 249 | async commitPendingAck(input: CommitPendingAckInput): Promise { 250 | return this.call('commit_pending_ack', input); 251 | } 252 | 253 | async commitConfirmation(input: DeliveryConfirmation): Promise { 254 | return this.call('commit_confirmation', input); 255 | } 256 | 257 | async checkAckInbox(): Promise { 258 | return this.call('check_ack_inbox', null); 259 | } 260 | 261 | async checkMailInbox(): Promise { 262 | return this.call('check_mail_inbox', null); 263 | } 264 | 265 | async deleteMail(ah: ActionHash): Promise { 266 | return this.call('delete_mail', ah); 267 | } 268 | 269 | async getAllMails(): Promise { 270 | return this.call('get_all_mails', null); 271 | } 272 | 273 | async getAllUnacknowledgedInmails(): Promise { 274 | return this.call('get_all_unacknowledged_inmails', null); 275 | } 276 | 277 | async getMail(ah: ActionHash): Promise { 278 | return this.call('get_mail', ah); 279 | } 280 | 281 | async getOutmailState(outmailAh: ActionHash): Promise { 282 | return this.call('get_outmail_state', outmailAh); 283 | } 284 | 285 | async getOutmailDeliveryState(outmailAh: ActionHash): Promise> { 286 | return this.call('get_outmail_delivery_state', outmailAh); 287 | } 288 | 289 | async hasAckBeenDelivered(inmailAh: ActionHash): Promise { 290 | return this.call('has_ack_been_delivered', inmailAh); 291 | } 292 | 293 | async isOutackSent(outackAh: ActionHash): Promise { 294 | return this.call('is_outack_sent', outackAh); 295 | } 296 | 297 | async requestAcks(): Promise { 298 | return this.call('request_acks', null); 299 | } 300 | 301 | async resendOutacks(): Promise { 302 | return this.call('resend_outacks', null); 303 | } 304 | 305 | async resendOutmails(): Promise { 306 | return this.call('resend_outmails', null); 307 | } 308 | 309 | async commitInmail(inmail: InMail): Promise { 310 | return this.call('commit_inmail', inmail); 311 | } 312 | 313 | async commitPendingMail(input: CommitPendingMailInput): Promise { 314 | return this.call('commit_pending_mail', input); 315 | } 316 | 317 | async sendMail(input: SendMailInput): Promise { 318 | return this.call('send_mail', input); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /webcomponents/src/viewModel/snapmail.zvm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionHash, ActionHashB64, 3 | AgentPubKey, AgentPubKeyB64, 4 | AnyDhtHash, AppSignalCb, 5 | decodeHashFromBase64, 6 | encodeHashToBase64, 7 | EntryHash 8 | } from '@holochain/client'; 9 | import {ZomeViewModel} from "@ddd-qc/lit-happ"; 10 | import {SnapmailProxy} from "../bindings/snapmail.proxy"; 11 | import {defaultPerspective, SnapmailPerspective} from "./snapmail.perspective"; 12 | import { 13 | FileManifest, FindManifestOutput, MailItem, 14 | SendMailInput, SignalProtocol, SignalProtocolType, SnapmailSignal 15 | } from "../bindings/snapmail.types"; 16 | import {AppSignal} from "@holochain/client/lib/api/app/types"; 17 | import {determineMailCssClass, is_OutMail, isMailDeleted} from "../mail"; 18 | 19 | /** */ 20 | export class SnapmailZvm extends ZomeViewModel { 21 | 22 | static readonly ZOME_PROXY = SnapmailProxy; 23 | get zomeProxy(): SnapmailProxy {return this._zomeProxy as SnapmailProxy;} 24 | 25 | 26 | private _canPing = true; 27 | 28 | get canPing(): boolean { return this._canPing} 29 | 30 | /** -- ViewModel -- */ 31 | 32 | private _perspective: SnapmailPerspective = defaultPerspective(); 33 | 34 | 35 | /* */ 36 | get perspective(): SnapmailPerspective { 37 | return this._perspective; 38 | } 39 | 40 | 41 | /* */ 42 | protected hasChanged(): boolean { 43 | // TODO 44 | return true; 45 | } 46 | 47 | 48 | 49 | /** */ 50 | async initializePerspectiveOnline(): Promise { 51 | await this.probeAllInnerAsync(); 52 | 53 | } 54 | 55 | /** */ 56 | async probeAllInnerAsync(): Promise { 57 | await this.probeHandles(); 58 | await this.zomeProxy.checkAckInbox(); 59 | const newInMails = await this.zomeProxy.checkMailInbox(); 60 | await this.probeMails(); 61 | /** Send notification for each new inMail */ 62 | console.log("probeAllInnerAsync()", newInMails.length); 63 | const fakeAppSignal = { 64 | cell_id: this.cell.id, 65 | zome_name: this._zomeProxy.zomeName, 66 | payload: null, 67 | }; 68 | for (const new_mail_ah of newInMails) { 69 | const mailAh = encodeHashToBase64(new_mail_ah); 70 | const mailItem = this._perspective.mailMap[mailAh]; 71 | if (!mailItem) { 72 | console.warn("New InMail not found in perspective"); 73 | continue; 74 | } 75 | const signal: SnapmailSignal = { 76 | kind: SignalProtocolType.ReceivedMail, 77 | from: decodeHashFromBase64(this.cell.agentPubKey), // Set author to self so it doesn't process a popup 78 | payload: {ReceivedMail: mailItem} 79 | } 80 | fakeAppSignal.payload = signal; 81 | this._dvmParent.signalHandler(fakeAppSignal); 82 | } 83 | } 84 | 85 | 86 | /** */ 87 | probeAllInner() { 88 | /* await */ this.probeAllInnerAsync(); 89 | } 90 | 91 | 92 | /** */ 93 | readonly signalHandler: AppSignalCb = (appSignal: AppSignal) => { 94 | console.log('snapmail.zvm.signalHandler():', appSignal); 95 | //const signal: SnapmailSignal = appSignal.payload as SnapmailSignal; 96 | /*await */ this.probeMails(); 97 | } 98 | 99 | 100 | /** */ 101 | async probeHandles() { 102 | const handleItems = await this.zomeProxy.getAllHandles(); 103 | console.log("probeHandles()", handleItems); 104 | this._perspective.usernameMap = {}; 105 | for(const handleItem of handleItems) { 106 | /* TODO: exclude self from list when in prod? */ 107 | const agentId = encodeHashToBase64(handleItem.agent_pub_key); 108 | //console.log('' + handleItem.name + ': ' + agentIdB64); 109 | this._perspective.usernameMap[agentId] = handleItem.username; 110 | if(this._perspective.pingMap[agentId] === undefined) { 111 | console.log(" ADDING TO pingMap: ", agentId); 112 | this._perspective.pingMap[agentId] = 0; 113 | this._perspective.responseMap[agentId] = false; 114 | } 115 | } 116 | this.notifySubscribers(); 117 | } 118 | 119 | 120 | /** Get stats from mailMap */ 121 | countMails(): number[] { 122 | let trashCount = 0; 123 | let inboxCount = 0; 124 | let sentCount = 0; 125 | let newCount = 0; 126 | 127 | for (const mailItem of Object.values(this._perspective.mailMap)) { 128 | const isDeleted = isMailDeleted(mailItem); 129 | const isOutMail = is_OutMail(mailItem); 130 | if (isOutMail) { 131 | sentCount = sentCount + 1; 132 | } 133 | if (isDeleted) { 134 | trashCount = trashCount + 1; 135 | } 136 | if (!isDeleted && !isOutMail) { 137 | inboxCount = inboxCount + 1; 138 | } 139 | if (determineMailCssClass(mailItem) === 'newmail') { 140 | newCount = newCount + 1; 141 | } 142 | } 143 | return [newCount, inboxCount, sentCount, trashCount]; 144 | } 145 | 146 | 147 | /** Get latest mails and rebuild mailMap */ 148 | async probeMails() { 149 | const mailItems = await this.zomeProxy.getAllMails(); 150 | this._perspective.mailMap = {}; 151 | for (const mailItem of mailItems) { 152 | this._perspective.mailMap[encodeHashToBase64(mailItem.ah)] = mailItem; 153 | } 154 | this.notifySubscribers(); 155 | } 156 | 157 | 158 | /** Ping oldest pinged agent */ 159 | pingNextAgent(): void { 160 | console.log(" pingNextAgent() pingMap", this.perspective.pingMap); 161 | //console.log({responseMap: this.perspective.responseMap}); 162 | /* Skip if empty map */ 163 | if (Object.keys(this.perspective.pingMap).length === 0) { 164 | return; 165 | } 166 | this._canPing = false; 167 | /* Sort pingMap by value to get oldest pinged agent */ 168 | const sortedPings = Object.entries(this.perspective.pingMap) 169 | .sort((a, b) => a[1] - b[1]); 170 | //console.log(" sortedPings:", sortedPings); 171 | /* Ping first agent in sorted list */ 172 | const pingedAgentB64 = sortedPings[0][0]; 173 | const pingedAgent = decodeHashFromBase64(pingedAgentB64); 174 | //console.log("pinging: ", pingedAgentB64); 175 | if (pingedAgentB64 === this.cell.agentPubKey) { 176 | //console.log("pinging self"); 177 | this.storePingResult(pingedAgentB64, true); 178 | this._canPing = true; 179 | return; 180 | } 181 | //const contactGrid = this.contactGridElem; 182 | this.zomeProxy.pingAgent(pingedAgent) 183 | .then((result: boolean) => { 184 | this.storePingResult(pingedAgentB64, result); 185 | this._canPing = true; 186 | }) 187 | .catch((error) => { 188 | console.warn('Ping failed for: ' + pingedAgentB64); 189 | console.warn(error); 190 | this.storePingResult(pingedAgentB64, false); 191 | this._canPing = true; 192 | }) 193 | } 194 | 195 | 196 | /** */ 197 | storePingResult(agentId: AgentPubKeyB64, isAgentPresent: boolean) { 198 | //console.log("storePingResult() responseMap[" + agentId + "] | " + isAgentPresent) 199 | //console.log("storePingResult() before pingMap[" + agentId + "]", this.perspective.pingMap) 200 | this.perspective.responseMap[agentId] = isAgentPresent; 201 | this.perspective.pingMap[agentId] = Date.now(); 202 | //console.log("storePingResult() after pingMap", this.perspective.pingMap); 203 | this.notifySubscribers(); 204 | } 205 | 206 | 207 | 208 | /** -- -- */ 209 | 210 | async pingAgent(destination: AgentPubKey): Promise { 211 | return this.zomeProxy.pingAgent(destination); 212 | } 213 | 214 | 215 | /** -- Handle -- */ 216 | 217 | async setHandle(newName: string): Promise { 218 | return this.zomeProxy.setHandle(newName); 219 | } 220 | 221 | async getMyHandle(): Promise { 222 | return this.zomeProxy.getMyHandle(); 223 | } 224 | 225 | async sendMail(input: SendMailInput): Promise { 226 | const ah = await this.zomeProxy.sendMail(input); 227 | //await this.probeMails(); 228 | return ah; 229 | } 230 | 231 | 232 | /** -- Mail -- */ 233 | 234 | async acknowledgeMail(inmailAh: ActionHashB64): Promise { 235 | return this.zomeProxy.acknowledgeMail(decodeHashFromBase64(inmailAh)); 236 | } 237 | 238 | async deleteMail(ah: ActionHashB64): Promise { 239 | return this.zomeProxy.deleteMail(decodeHashFromBase64(ah)); 240 | } 241 | 242 | 243 | /** -- File -- */ 244 | 245 | async getMissingAttachments(from: AgentPubKey, inmail_ah: ActionHash): Promise { 246 | return this.zomeProxy.getMissingAttachments({from, inmail_ah}); 247 | 248 | } 249 | 250 | 251 | async getManifest(manifestAddress: AnyDhtHash): Promise { 252 | return this.zomeProxy.getManifest(manifestAddress); 253 | } 254 | 255 | 256 | async findManifest(contentHash: string): Promise { 257 | return this.zomeProxy.findManifest(contentHash); 258 | } 259 | 260 | async getChunk(chunkEh: EntryHash): Promise { 261 | return this.zomeProxy.getChunk(chunkEh); 262 | } 263 | 264 | /** */ 265 | async writeManifest( 266 | dataHash: string, 267 | filename: string, 268 | filetype: string, 269 | orig_filesize: number, 270 | chunks: EntryHash[]): Promise { 271 | const params = { 272 | data_hash: dataHash, 273 | filename, filetype, orig_filesize, 274 | chunks 275 | } 276 | return this.zomeProxy.writeManifest(params); 277 | } 278 | 279 | /** */ 280 | async writeChunk(dataHash: string, chunkIndex: number, chunk: string): Promise { 281 | const params = { 282 | data_hash: dataHash, 283 | chunk_index: chunkIndex, 284 | chunk 285 | } 286 | return this.zomeProxy.writeChunk(params); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /webcomponents/src/bindings/snapmail.types.ts: -------------------------------------------------------------------------------- 1 | /* This file is generated by zits. Do not edit manually */ 2 | 3 | import { 4 | /** types.ts */ 5 | HoloHash, 6 | AgentPubKey, 7 | DnaHash, 8 | WasmHash, 9 | EntryHash, 10 | ActionHash, 11 | AnyDhtHash, 12 | ExternalHash, 13 | KitsuneAgent, 14 | KitsuneSpace, 15 | HoloHashB64, 16 | AgentPubKeyB64, 17 | DnaHashB64, 18 | WasmHashB64, 19 | EntryHashB64, 20 | ActionHashB64, 21 | AnyDhtHashB64, 22 | InstalledAppId, 23 | Signature, 24 | CellId, 25 | DnaProperties, 26 | RoleName, 27 | InstalledCell, 28 | Timestamp, 29 | Duration, 30 | HoloHashed, 31 | NetworkInfo, 32 | FetchPoolInfo, 33 | /** hdk/action.ts */ 34 | SignedActionHashed, 35 | RegisterAgentActivity, 36 | ActionHashed, 37 | ActionType, 38 | Action, 39 | NewEntryAction, 40 | Dna, 41 | AgentValidationPkg, 42 | InitZomesComplete, 43 | CreateLink, 44 | DeleteLink, 45 | OpenChain, 46 | CloseChain, 47 | Update, 48 | Delete, 49 | Create, 50 | /** hdk/capabilities.ts */ 51 | CapSecret, 52 | CapClaim, 53 | GrantedFunctionsType, 54 | GrantedFunctions, 55 | ZomeCallCapGrant, 56 | CapAccessType, 57 | CapAccess, 58 | CapGrant, 59 | ///** hdk/countersigning.ts */ 60 | //CounterSigningSessionData, 61 | //PreflightRequest, 62 | //CounterSigningSessionTimes, 63 | //ActionBase, 64 | //CounterSigningAgents, 65 | //PreflightBytes, 66 | //Role, 67 | //CountersigningAgentState, 68 | /** hdk/dht-ops.ts */ 69 | DhtOpType, 70 | DhtOp, 71 | getDhtOpType, 72 | getDhtOpAction, 73 | getDhtOpEntry, 74 | getDhtOpSignature, 75 | /** hdk/entry.ts */ 76 | EntryVisibility, 77 | AppEntryDef, 78 | EntryType, 79 | EntryContent, 80 | Entry, 81 | /** hdk/record.ts */ 82 | Record as HcRecord, 83 | RecordEntry as HcRecordEntry, 84 | /** hdk/link.ts */ 85 | AnyLinkableHash, 86 | ZomeIndex, 87 | LinkType, 88 | LinkTag, 89 | RateWeight, 90 | RateBucketId, 91 | RateUnits, 92 | Link, 93 | /** api/admin/types.ts */ 94 | InstalledAppInfoStatus, 95 | DeactivationReason, 96 | DisabledAppReason, 97 | StemCell, 98 | ProvisionedCell, 99 | ClonedCell, 100 | CellType, 101 | CellInfo, 102 | AppInfo, 103 | MembraneProof, 104 | FunctionName, 105 | ZomeName, 106 | ZomeDefinition, 107 | IntegrityZome, 108 | CoordinatorZome, 109 | DnaDefinition, 110 | ResourceBytes, 111 | ResourceMap, 112 | CellProvisioningStrategy, 113 | CellProvisioning, 114 | DnaVersionSpec, 115 | DnaVersionFlexible, 116 | AppRoleDnaManifest, 117 | AppRoleManifest, 118 | AppManifest, 119 | AppBundle, 120 | AppBundleSource, 121 | NetworkSeed, 122 | ZomeLocation, 123 | } from '@holochain/client'; 124 | 125 | import { 126 | /** Common */ 127 | DhtOpHashB64, 128 | //DnaHashB64, (duplicate) 129 | //AnyDhtHashB64, (duplicate) 130 | DhtOpHash, 131 | /** DnaFile */ 132 | DnaFile, 133 | DnaDef, 134 | Zomes, 135 | WasmCode, 136 | /** entry-details */ 137 | EntryDetails, 138 | RecordDetails, 139 | Details, 140 | DetailsType, 141 | EntryDhtStatus, 142 | /** Validation */ 143 | ValidationStatus, 144 | ValidationReceipt, 145 | } from '@holochain-open-dev/core-types'; 146 | 147 | export const REMOTE_ENDPOINT = "receive_dm"; 148 | 149 | export interface DmPacket { 150 | from: AgentPubKey 151 | dm: DirectMessageProtocol 152 | } 153 | 154 | export enum DirectMessageProtocolType { 155 | Failure = 'Failure', 156 | Success = 'Success', 157 | Mail = 'Mail', 158 | Ack = 'Ack', 159 | Chunk = 'Chunk', 160 | FileManifest = 'FileManifest', 161 | RequestChunk = 'RequestChunk', 162 | RequestManifest = 'RequestManifest', 163 | UnknownEntry = 'UnknownEntry', 164 | Ping = 'Ping', 165 | } 166 | export type DirectMessageProtocolVariantFailure = {Failure: string} 167 | export type DirectMessageProtocolVariantSuccess = {Success: string} 168 | export type DirectMessageProtocolVariantMail = {Mail: MailMessage} 169 | export type DirectMessageProtocolVariantAck = {Ack: AckMessage} 170 | export type DirectMessageProtocolVariantChunk = {Chunk: FileChunk} 171 | export type DirectMessageProtocolVariantFileManifest = {FileManifest: FileManifest} 172 | export type DirectMessageProtocolVariantRequestChunk = {RequestChunk: EntryHash} 173 | export type DirectMessageProtocolVariantRequestManifest = {RequestManifest: EntryHash} 174 | export type DirectMessageProtocolVariantUnknownEntry = {UnknownEntry: null} 175 | export type DirectMessageProtocolVariantPing = {Ping: null} 176 | export type DirectMessageProtocol = 177 | | DirectMessageProtocolVariantFailure | DirectMessageProtocolVariantSuccess | DirectMessageProtocolVariantMail | DirectMessageProtocolVariantAck | DirectMessageProtocolVariantChunk | DirectMessageProtocolVariantFileManifest | DirectMessageProtocolVariantRequestChunk | DirectMessageProtocolVariantRequestManifest | DirectMessageProtocolVariantUnknownEntry | DirectMessageProtocolVariantPing; 178 | 179 | export interface MailMessage { 180 | mail: Mail 181 | outmail_eh: EntryHash 182 | mail_signature: Signature 183 | } 184 | 185 | export interface AckMessage { 186 | outmail_eh: EntryHash 187 | ack_signature: Signature 188 | } 189 | 190 | export type FindManifestOutput = FileManifest | null; 191 | 192 | export type ZomeManifestVec = FileManifest[]; 193 | 194 | export interface GetMissingAttachmentsInput { 195 | from: AgentPubKey 196 | inmail_ah: ActionHash 197 | } 198 | 199 | export interface GetMissingChunksInput { 200 | from: AgentPubKey 201 | manifest_eh: EntryHash 202 | } 203 | 204 | export interface WriteManifestInput { 205 | data_hash: string 206 | filename: string 207 | filetype: string 208 | orig_filesize: number 209 | chunks: EntryHash[] 210 | } 211 | 212 | export interface HandleItem { 213 | username: string 214 | agent_pub_key: AgentPubKey 215 | handle_eh: EntryHash 216 | } 217 | 218 | export interface CommitPendingAckInput { 219 | outack_eh: EntryHash 220 | outmail_eh: EntryHash 221 | original_sender: AgentPubKey 222 | } 223 | 224 | export type GetMailOutput = InMail | OutMail | null; 225 | 226 | export interface SendMailInput { 227 | subject: string 228 | payload: string 229 | reply_of?: ActionHash 230 | to: AgentPubKey[] 231 | cc: AgentPubKey[] 232 | bcc: AgentPubKey[] 233 | manifest_address_list: ActionHash[] 234 | } 235 | 236 | export interface CommitPendingMailInput { 237 | mail: PendingMail 238 | outmail_eh: EntryHash 239 | destination: AgentPubKey 240 | } 241 | 242 | /** Listing all Holochain Path used in this DNA */ 243 | export const Directory = "directory"; 244 | 245 | /** */ 246 | export interface SnapmailSignal { 247 | from: AgentPubKey 248 | kind: string 249 | payload: SignalProtocol 250 | } 251 | 252 | export enum SignalProtocolType { 253 | ReceivedMail = 'ReceivedMail', 254 | ReceivedAck = 'ReceivedAck', 255 | ReceivedFile = 'ReceivedFile', 256 | } 257 | export type SignalProtocolVariantReceivedMail = {ReceivedMail: MailItem} 258 | export type SignalProtocolVariantReceivedAck = {ReceivedAck: ActionHash} 259 | export type SignalProtocolVariantReceivedFile = {ReceivedFile: FileManifest} 260 | export type SignalProtocol = 261 | | SignalProtocolVariantReceivedMail | SignalProtocolVariantReceivedAck | SignalProtocolVariantReceivedFile; 262 | 263 | export const SNAPMAIL_DEFAULT_INTEGRITY_ZOME_NAME = "snapmail_model"; 264 | 265 | export const SNAPMAIL_DEFAULT_COORDINATOR_ZOME_NAME = "snapmail"; 266 | 267 | export const SNAPMAIL_DEFAULT_ROLE_NAME = "rSnapmail"; 268 | 269 | export const DIRECT_SEND_TIMEOUT_MS = 1000; 270 | 271 | export const DIRECT_SEND_CHUNK_TIMEOUT_MS = 10000; 272 | 273 | export const CHUNK_MAX_SIZE = 200 * 1024; 274 | 275 | export const FILE_MAX_SIZE = 10 * 1024 * 1024; 276 | 277 | /** PSEUDO CONDITIONAL COMPILATION FOR DEBUGGING / TESTING */ 278 | export const CAN_DM = true; 279 | 280 | /** Entry representing a file chunk. */ 281 | export interface FileChunk { 282 | data_hash: string 283 | chunk_index: number 284 | chunk: string 285 | } 286 | 287 | /** 288 | * Entry representing a file in chunks. 289 | * All chunks must be committed beforehand. 290 | */ 291 | export interface FileManifest { 292 | data_hash: string 293 | filename: string 294 | filetype: string 295 | orig_filesize: number 296 | chunks: EntryHash[] 297 | content?: string 298 | } 299 | 300 | /** Entry representing the username of an Agent */ 301 | export interface Handle { 302 | username: string 303 | } 304 | 305 | export enum SnapmailEntryType { 306 | PubEncKey = 'PubEncKey', 307 | Handle = 'Handle', 308 | InMail = 'InMail', 309 | OutMail = 'OutMail', 310 | OutAck = 'OutAck', 311 | InAck = 'InAck', 312 | PendingMail = 'PendingMail', 313 | PendingAck = 'PendingAck', 314 | DeliveryConfirmation = 'DeliveryConfirmation', 315 | FileChunk = 'FileChunk', 316 | FileManifest = 'FileManifest', 317 | } 318 | export type SnapmailEntryVariantPubEncKey = {PubEncKey: PubEncKey} 319 | export type SnapmailEntryVariantHandle = {Handle: Handle} 320 | export type SnapmailEntryVariantInMail = {InMail: InMail} 321 | export type SnapmailEntryVariantOutMail = {OutMail: OutMail} 322 | export type SnapmailEntryVariantOutAck = {OutAck: OutAck} 323 | export type SnapmailEntryVariantInAck = {InAck: InAck} 324 | export type SnapmailEntryVariantPendingMail = {PendingMail: PendingMail} 325 | export type SnapmailEntryVariantPendingAck = {PendingAck: PendingAck} 326 | export type SnapmailEntryVariantDeliveryConfirmation = {DeliveryConfirmation: DeliveryConfirmation} 327 | export type SnapmailEntryVariantFileChunk = {FileChunk: FileChunk} 328 | export type SnapmailEntryVariantFileManifest = {FileManifest: FileManifest} 329 | export type SnapmailEntry = 330 | | SnapmailEntryVariantPubEncKey | SnapmailEntryVariantHandle | SnapmailEntryVariantInMail | SnapmailEntryVariantOutMail | SnapmailEntryVariantOutAck | SnapmailEntryVariantInAck | SnapmailEntryVariantPendingMail | SnapmailEntryVariantPendingAck | SnapmailEntryVariantDeliveryConfirmation | SnapmailEntryVariantFileChunk | SnapmailEntryVariantFileManifest; 331 | 332 | /** List of all Link kinds handled by this Zome */ 333 | export type LinkKind = 334 | | {Members: null} | {AckInbox: null} | {MailInbox: null} | {Handle: null} | {Pending: null} | {Pendings: null} | {EncKey: null}; 335 | export enum LinkKindType { 336 | Members = 'Members', 337 | AckInbox = 'AckInbox', 338 | MailInbox = 'MailInbox', 339 | Handle = 'Handle', 340 | Pending = 'Pending', 341 | Pendings = 'Pendings', 342 | EncKey = 'EncKey', 343 | } 344 | 345 | /** Entry for a received Acknowledgement Receipt */ 346 | export interface DeliveryConfirmation { 347 | /** EntryHash to OutMail or OutAck on same chain */ 348 | package_eh: EntryHash 349 | recipient: AgentPubKey 350 | } 351 | 352 | /** Entry for a received Acknowledgement Receipt */ 353 | export interface InAck { 354 | outmail_eh: EntryHash 355 | from: AgentPubKey 356 | /** Signed outmail_eh */ 357 | from_signature: Signature 358 | } 359 | 360 | /** Entry representing a received mail. */ 361 | export interface InMail { 362 | mail: Mail 363 | date_received: number 364 | outmail_eh: EntryHash 365 | from: AgentPubKey 366 | from_signature: Signature 367 | } 368 | 369 | /** Possible states of an InMail entry */ 370 | export type InMailState = 371 | | {Unacknowledged: null} | {AckUnsent: null} | {AckPending: null} | {AckDelivered: null} | {Deleted: null}; 372 | export enum InMailStateType { 373 | Unacknowledged = 'Unacknowledged', 374 | AckUnsent = 'AckUnsent', 375 | AckPending = 'AckPending', 376 | AckDelivered = 'AckDelivered', 377 | Deleted = 'Deleted', 378 | } 379 | 380 | /** State of a single delivery of a mail or ack to a unique recipient */ 381 | export type DeliveryState = 382 | | {Unsent: null} | {Pending: null} | {Delivered: null}; 383 | export enum DeliveryStateType { 384 | Unsent = 'Unsent', 385 | Pending = 'Pending', 386 | Delivered = 'Delivered', 387 | } 388 | 389 | /** Possible states of an OutMail entry */ 390 | export type OutMailState = 391 | | {Unsent: null} | {AllSent: null} | {AllReceived: null} | {AllAcknowledged: null} | {Deleted: null}; 392 | export enum OutMailStateType { 393 | Unsent = 'Unsent', 394 | AllSent = 'AllSent', 395 | AllReceived = 'AllReceived', 396 | AllAcknowledged = 'AllAcknowledged', 397 | Deleted = 'Deleted', 398 | } 399 | 400 | export enum MailStateType { 401 | In = 'In', 402 | Out = 'Out', 403 | } 404 | export type MailStateVariantIn = {In: InMailState} 405 | export type MailStateVariantOut = {Out: OutMailState} 406 | export type MailState = 407 | | MailStateVariantIn | MailStateVariantOut; 408 | 409 | export interface MailItem { 410 | ah: ActionHash 411 | author: AgentPubKey 412 | mail: Mail 413 | state: MailState 414 | bcc: AgentPubKey[] 415 | date: number 416 | /** UI Things */ 417 | reply?: ActionHash 418 | reply_of?: ActionHash 419 | status?: string 420 | } 421 | 422 | export type RecipientKind = 423 | | {TO: null} | {CC: null} | {BCC: null}; 424 | export enum RecipientKindType { 425 | To = 'To', 426 | Cc = 'Cc', 427 | Bcc = 'Bcc', 428 | } 429 | 430 | /** 431 | * Core content of all *Mail Entries 432 | * Mail can have Zero public recipient (but must have at least one public or private recipient) 433 | */ 434 | export interface Mail { 435 | date_sent: number 436 | subject: string 437 | payload: string 438 | to: AgentPubKey[] 439 | cc: AgentPubKey[] 440 | attachments: AttachmentInfo[] 441 | } 442 | 443 | /** Metadata for a mail attachment */ 444 | export interface AttachmentInfo { 445 | manifest_eh: EntryHash 446 | data_hash: string 447 | filename: string 448 | filetype: string 449 | orig_filesize: number 450 | } 451 | 452 | /** Entry for an Acknowledgement Receipt of a Mail authored by this agent */ 453 | export interface OutAck { 454 | inmail_eh: EntryHash 455 | } 456 | 457 | /** Entry representing an authored mail. It is private. */ 458 | export interface OutMail { 459 | mail: Mail 460 | reply_of?: ActionHash 461 | bcc: AgentPubKey[] 462 | } 463 | 464 | /** Entry representing an AcknowledgmentReceipt on the DHT waiting to be received */ 465 | export interface PendingAck { 466 | outmail_eh: EntryHash 467 | /** Signed outmail_eh */ 468 | from_signature: Signature 469 | } 470 | 471 | /** 472 | * Entry representing a mail on the DHT waiting to be received by recipient. 473 | * The recipient is the agentId where the entry is linked from. 474 | * The mail is encrypted with the recipient's public encryption key. 475 | */ 476 | export interface PendingMail { 477 | encrypted_mail: unknown 478 | outmail_eh: EntryHash 479 | from_signature: Signature 480 | } 481 | 482 | /** Entry representing the Public Encryption Key of an Agent */ 483 | export interface PubEncKey { 484 | value: Uint8Array 485 | } 486 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Cryptographic Autonomy License version 1.0 2 | 3 | _This Cryptographic Autonomy License (the “License”) applies to any Work whose owner has marked it with any of the following notices:_ 4 | 5 | _“Licensed under the Cryptographic Autonomy License version 1.0,” or_ 6 | 7 | _“SPDX-License-Identifier: CAL-1.0,” or_ 8 | 9 | _“Licensed under the Cryptographic Autonomy License version 1.0, with Combined Work Exception,” or_ 10 | 11 | _“SPDX-License-Identifier: CAL-1.0 with Combined-Work-Exception.”_ 12 | 13 | --- 14 | 15 | ## 1. Purpose 16 | 17 | This License gives You unlimited permission to use and modify the software to which it applies (the “Work”), either as-is or in modified form, for Your private purposes, while protecting the owners and contributors to the software from liability. 18 | 19 | This License also strives to protect the freedom and autonomy of third parties who receive the Work from you. If any non-affiliated third party receives any part, aspect, or element of the Work from You, this License requires that You provide that third party all the permissions and materials needed to independently use and modify the Work without that third party having a loss of data or capability due to your actions. 20 | 21 | The full permissions, conditions, and other terms are laid out below. 22 | 23 | ## 2. Receiving a License 24 | 25 | In order to receive this License, You must agree to its rules. The rules of this License are both obligations of Your agreement with the Licensor and conditions to your License. You must not do anything with the Work that triggers a rule You cannot or will not follow. 26 | 27 | ### 2.1. Application 28 | 29 | The terms of this License apply to the Work as you receive it from Licensor, as well as to any modifications, elaborations, or implementations created by You that contain any licenseable portion of the Work (a “Modified Work”). Unless specified, any reference to the Work also applies to a Modified Work. 30 | 31 | ### 2.2. Offer and Acceptance 32 | 33 | This License is automatically offered to every person and organization. You show that you accept this License and agree to its conditions by taking any action with the Work that, absent this License, would infringe any intellectual property right held by Licensor. 34 | 35 | ### 2.3. Compliance and Remedies 36 | 37 | Any failure to act according to the terms and conditions of this License places Your use of the Work outside the scope of the License and infringes the intellectual property rights of the Licensor. In the event of infringement, the terms and conditions of this License may be enforced by Licensor under the intellectual property laws of any jurisdiction to which You are subject. You also agree that either the Licensor or a Recipient (as an intended third-party beneficiary) may enforce the terms and conditions of this License against You via specific performance. 38 | 39 | ## 3. Permissions and Conditions 40 | 41 | ### 3.1. Permissions Granted 42 | 43 | Conditioned on compliance with section 4, and subject to the limitations of section 3.2, Licensor grants You the world-wide, royalty-free, non-exclusive permission to: 44 | 45 | > a) Take any action with the Work that would infringe the non-patent intellectual property laws of any jurisdiction to which You are subject; and 46 | > 47 | > b) Take any action with the Work that would infringe any patent claims that Licensor can license or becomes able to license, to the extent that those claims are embodied in the Work as distributed by Licensor. 48 | 49 | ### 3.2. Limitations on Permissions Granted 50 | 51 | The following limitations apply to the permissions granted in section 3.1: 52 | 53 | > a) Licensor does not grant any patent license for claims that are only infringed due to modification of the Work as provided by Licensor, or the combination of the Work as provided by Licensor, directly or indirectly, with any other component, including other software or hardware. 54 | > 55 | > b) Licensor does not grant any license to the trademarks, service marks, or logos of Licensor, except to the extent necessary to comply with the attribution conditions in section 4.1 of this License. 56 | 57 | ## 4. Conditions 58 | 59 | If You exercise any permission granted by this License, such that the Work, or any part, aspect, or element of the Work, is distributed, communicated, made available, or made perceptible to a non-Affiliate third party (a “Recipient”), either via physical delivery or via a network connection to the Recipient, You must comply with the following conditions: 60 | 61 | ### 4.1. Provide Access to Source Code 62 | 63 | Subject to the exception in section 4.4, You must provide to each Recipient a copy of, or no-charge unrestricted network access to, the Source Code corresponding to the Work. 64 | 65 | The “Source Code” of the Work means the form of the Work preferred for making modifications, including any comments, configuration information, documentation, help materials, installation instructions, cryptographic seeds or keys, and any information reasonably necessary for the Recipient to independently compile and use the Source Code and to have full access to the functionality contained in the Work. 66 | 67 | #### 4.1.1. Providing Network Access to the Source Code 68 | 69 | Network access to the Notices and Source Code may be provided by You or by a third party, such as a public software repository, and must persist during the same period in which You exercise any of the permissions granted to You under this License and for at least one year thereafter. 70 | 71 | #### 4.1.2. Source Code for a Modified Work 72 | 73 | Subject to the exception in section 4.5, You must provide to each Recipient of a Modified Work Access to Source Code corresponding to those portions of the Work remaining in the Modified Work as well as the modifications used by You to create the Modified Work. The Source Code corresponding to the modifications in the Modified Work must be provided to the Recipient either a) under this License, or b) under a Compatible Open Source License. 74 | 75 | A “Compatible Open Source License” means a license accepted by the Open Source Initiative that allows object code created using both Source Code provided under this License and Source Code provided under the other open source license to be distributed together as a single work. 76 | 77 | #### 4.1.3. Coordinated Disclosure of Security Vulnerabilities 78 | 79 | You may delay providing the Source Code corresponding to a particular modification of the Work for up to ninety (90) days (the “Embargo Period”) if: a) the modification is intended to address a newly-identified vulnerability or a security flaw in the Work, b) disclosure of the vulnerability or security flaw before the end of the Embargo Period would put the data, identity, or autonomy of one or more Recipients of the Work at significant risk, c) You are participating in a coordinated disclosure of the vulnerability or security flaw with one or more additional Licensees, and d) Access to the Source Code pertaining to the modification is provided to all Recipients at the end of the Embargo Period. 80 | 81 | ### 4.2. Maintain User Autonomy 82 | 83 | In addition to providing each Recipient the opportunity to have Access to the Source Code, You cannot use the permissions given under this License to interfere with a Recipient’s ability to fully use an independent copy of the Work generated from the Source Code You provide with the Recipient’s own User Data. 84 | 85 | “User Data” means any data that is an input to or an output from the Work, where the presence of the data is necessary for substantially identical use of the Work in an equivalent context chosen by the Recipient, and where the Recipient has an existing ownership interest, an existing right to possess, or where the data has been generated by, for, or has been assigned to the Recipient. 86 | 87 | #### 4.2.1. No Withholding User Data 88 | 89 | Throughout any period in which You exercise any of the permissions granted to You under this License, You must also provide to any Recipient to whom you provide services via the Work, a no-charge copy, provided in a commonly used electronic form, of the Recipient’s User Data in your possession, to the extent that such User Data is available to You for use in conjunction with the Work. 90 | 91 | #### 4.2.2. No Technical Measures that Limit Access 92 | 93 | You may not, by the use of cryptographic methods applied to anything provided to the Recipient, by possession or control of cryptographic keys, seeds, or hashes, by other technological protection measures, or by any other method, limit a Recipient's ability to access any functionality present in the Recipient's independent copy of the Work, or deny a Recipient full control of the Recipient's User Data. 94 | 95 | #### 4.2.3. No Legal or Contractual Measures that Limit Access 96 | 97 | You may not contractually restrict a Recipient's ability to independently exercise the permissions granted under this License. You waive any legal power to forbid circumvention of technical protection measures that include use of the Work, and You waive any claim that the capabilities of the Work were limited or modified as a means of enforcing the legal rights of third parties against Recipients. 98 | 99 | ### 4.3. Provide Notices and Attribution 100 | 101 | You must retain all licensing, authorship, or attribution notices contained in the Source Code (the “Notices”), and provide all such Notices to each Recipient, together with a statement acknowledging the use of the Work. Notices may be provided directly to a Recipient or via an easy-to-find hyperlink to an Internet location also providing Access to Source Code. 102 | 103 | ### 4.4. Scope of Conditions in this License 104 | 105 | You are required to uphold the conditions of this License only relative to those who are Recipients of the Work from You. Other than providing Recipients with the applicable Notices, Access to Source Code, and a copy of and full control of their User Data, nothing in this License requires You to provide processing services to or engage in network interactions with anyone. 106 | 107 | ### 4.5. Combined Work Exception 108 | 109 | As an exception to condition that You provide Recipients Access to Source Code, any Source Code files marked by the Licensor as having the “Combined Work Exception,” or any object code exclusively resulting from Source Code files so marked, may be combined with other Software into a “Larger Work.” So long as you comply with the requirements to provide Recipients the applicable Notices and Access to the Source Code provided to You by Licensor, and you provide Recipients access to their User Data and do not limit Recipient’s ability to independently work with their User Data, any other Software in the Larger Work as well as the Larger Work as a whole may be licensed under the terms of your choice. 110 | 111 | ## 5. Term and Termination 112 | 113 | The term of this License begins when You receive the Work, and continues until terminated for any of the reasons described herein, or until all Licensor’s intellectual property rights in the Software expire, whichever comes first (“Term”). This License cannot be revoked, only terminated for the reasons listed below. 114 | 115 | ### 5.1. Effect of Termination 116 | 117 | If this License is terminated for any reason, all permissions granted to You under Section 3 by any Licensor automatically terminate. You will immediately cease exercising any permissions granted in this License relative to the Work, including as part of any Modified Work. 118 | 119 | ### 5.2. Termination for Non-Compliance; Reinstatement 120 | 121 | This License terminates automatically if You fail to comply with any of the conditions in section 4. As a special exception to termination for non-compliance, Your permissions for the Work under this License will automatically be reinstated if You come into compliance with all the conditions in section 2 within sixty (60) days of being notified by Licensor or an intended third party beneficiary of Your noncompliance. You are eligible for reinstatement of permissions for the Work one time only, and only for the sixty days immediately after becoming aware of noncompliance. Loss of permissions granted for the Work under this License due to either a) sustained noncompliance lasting more than sixty days or b) subsequent termination for noncompliance after reinstatement, is permanent, unless rights are specifically restored by Licensor in writing. 122 | 123 | ### 5.3 Termination Due to Litigation 124 | 125 | If You initiate litigation against Licensor, or any Recipient of the Work, either direct or indirect, asserting that the Work directly or indirectly infringes any patent, then all permissions granted to You by this License shall terminate. In the event of termination due to litigation, all permissions validly granted by You under this License, directly or indirectly, shall survive termination. Administrative review procedures, declaratory judgment actions, counterclaims in response to patent litigation, and enforcement actions against former Licensees terminated under this section do not cause termination due to litigation. 126 | 127 | ## 6. Disclaimer of Warranty and Limit on Liability 128 | 129 | As far as the law allows, the Work comes AS-IS, without any warranty of any kind, and no Licensor or contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim, or for any type of damages, including indirect, special, incidental, or consequential damages of any type arising as a result of this License or the use of the Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of profits, revenue, or any and all other commercial damages or losses. 130 | 131 | ## 7. Other Provisions 132 | 133 | ### 7.1. Affiliates 134 | 135 | An “Affiliate” means any other entity that, directly or indirectly through one or more intermediaries, controls, is controlled by, or is under common control with, the Licensee. Employees of a Licensee and natural persons acting as contractors exclusively providing services to Licensee are also Affiliates. 136 | 137 | ### 7.2. Choice of Jurisdiction and Governing Law 138 | 139 | A Licensor may require that any action or suit by a Licensee relating to a Work provided by Licensor under this License may be brought only in the courts of a particular jurisdiction and under the laws of a particular jurisdiction (excluding its conflict-of-law provisions), if Licensor provides conspicuous notice of the particular jurisdiction to all Licensees. 140 | 141 | ### 7.3. No Sublicensing 142 | 143 | This License is not sublicensable. Each time You provide the Work or a Modified Work to a Recipient, the Recipient automatically receives a license under the terms described in this License. You may not impose any further reservations, conditions, or other provisions on any Recipients’ exercise of the permissions granted herein. 144 | 145 | ### 7.4. Attorneys' Fees 146 | 147 | In any action to enforce the terms of this License, or seeking damages relating thereto, including by an intended third party beneficiary, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. A “prevailing party” is the party that achieves, or avoids, compliance with this License, including through settlement. This section shall survive the termination of this License. 148 | 149 | ### 7.5. No Waiver 150 | 151 | Any failure by Licensor to enforce any provision of this License will not constitute a present or future waiver of such provision nor limit Licensor’s ability to enforce such provision at a later time. 152 | 153 | ### 7.6. Severability 154 | 155 | If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any invalid or unenforceable portion will be interpreted to the effect and intent of the original portion. If such a construction is not possible, the invalid or unenforceable portion will be severed from this License but the rest of this License will remain in full force and effect. 156 | 157 | ### 7.7. License for the Text of this License 158 | 159 | The text of this license is released under the Creative Commons Attribution-ShareAlike 4.0 International License, with the caveat that any modifications of this license may not use the name “Cryptographic Autonomy License” or any name confusingly similar thereto to describe any derived work of this License. 160 | -------------------------------------------------------------------------------- /webcomponents/src/elements/snapmail-filebox.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from "lit"; 2 | import {Grid, GridActiveItemChangedEvent} from "@vaadin/grid"; 3 | import {HorizontalLayout} from "@vaadin/horizontal-layout"; 4 | import {TextField, TextFieldValueChangedEvent} from "@vaadin/text-field"; 5 | import {customElement, state} from "lit/decorators.js"; 6 | import {updateTray} from "../electron"; 7 | import {stylesTemplate} from "../constants"; 8 | import {MailItem} from "../bindings/snapmail.types"; 9 | import { 10 | determineMailCssClass, 11 | hasMailBeenOpened, 12 | into_gridItem, 13 | is_OutMail, 14 | isMailDeleted, 15 | MailGridItem, 16 | systemFolders 17 | } from "../mail"; 18 | import {MenuBarItem, MenuBarItemSelectedEvent} from "@vaadin/menu-bar"; 19 | import {HAPP_BUILD_MODE, HappBuildModeType, ZomeElement} from "@ddd-qc/lit-happ"; 20 | import {SnapmailPerspective} from "../viewModel/snapmail.perspective"; 21 | import {SnapmailZvm} from "../viewModel/snapmail.zvm"; 22 | 23 | import {Select, SelectChangeEvent} from "@vaadin/select"; 24 | import {GridItemModel} from "@vaadin/grid/src/vaadin-grid"; 25 | 26 | //import '@vaadin/icon'; 27 | //import '@vaadin/vaadin-lumo-styles'; 28 | 29 | /** */ 30 | function filterMails(mailItems: MailGridItem[], searchValue: string) { 31 | const searchTerm = (searchValue || '').trim(); 32 | const matchesTerm = (value: string) => { 33 | return value.toLowerCase().indexOf(searchTerm.toLowerCase()) >= 0; 34 | }; 35 | const filteredItems = mailItems.filter((item) => { 36 | //console.log({item}); 37 | return ( 38 | !searchTerm 39 | || matchesTerm(item.username) 40 | || matchesTerm(item.subject) 41 | || matchesTerm(item.content) 42 | ); 43 | }); 44 | return filteredItems; 45 | } 46 | 47 | 48 | /** */ 49 | @customElement("snapmail-filebox") 50 | export class SnapmailFilebox extends ZomeElement { 51 | constructor() { 52 | super(SnapmailZvm.DEFAULT_ZOME_NAME); 53 | if (HAPP_BUILD_MODE == HappBuildModeType.Debug) { 54 | this._menuItems.push({text: 'Refresh', disabled: false}); 55 | } 56 | } 57 | 58 | // @property({type: Object}) 59 | // mailItems:any; 60 | //_items:any = [] 61 | @state() private _allMailGridItems: MailGridItem[] = []; 62 | private _curFolderItems: MailGridItem[] = []; 63 | @state() private _shownItems: MailGridItem[] = []; 64 | @state() private _selectedItems: MailGridItem[] = []; 65 | //_activeItem:any = null; 66 | 67 | 68 | private readonly _systemFoldersVec = [ 69 | {label: systemFolders.ALL, value: systemFolders.ALL}, 70 | {label: systemFolders.INBOX, value: systemFolders.INBOX}, 71 | {label: systemFolders.SENT, value: systemFolders.SENT}, 72 | {label: systemFolders.TRASH, value: systemFolders.TRASH}, 73 | ]; 74 | // private readonly _systemFoldersVec: SelectItem[] = [ 75 | // {label: "bob", value:"bob"}, 76 | // {label: "marley", value:"marley"}, 77 | // ]; 78 | 79 | 80 | private _currentFolder: string = this._systemFoldersVec[1].value; 81 | 82 | @state() private _menuItems: MenuBarItem[] = 83 | [ { text: 'Move', disabled: true } 84 | , { text: 'Reply', disabled: true, children: [{ text: 'Reply to sender' }, { text: 'Reply to all' }, { text: 'Forward' }] } 85 | , { text: 'Trash', disabled: true } 86 | , { text: 'Print', disabled: true } 87 | //, { text: 'Find', disabled: true } 88 | ]; 89 | 90 | 91 | get mailGridElem(): Grid { 92 | return this.shadowRoot.getElementById("mailGrid") as Grid; 93 | } 94 | 95 | get mailSearchElem(): TextField { 96 | return this.shadowRoot.getElementById("mailSearch") as TextField; 97 | } 98 | 99 | 100 | get folderElem(): Select { 101 | return this.shadowRoot.getElementById("fileboxFolder") as Select; 102 | } 103 | 104 | 105 | /** -- Methods -- */ 106 | 107 | /** */ 108 | resetSelection() { 109 | this._selectedItems = []; 110 | } 111 | 112 | /** */ 113 | protected firstUpdated(_changedProperties: PropertyValues) { 114 | super.firstUpdated(_changedProperties); 115 | 116 | /** Display bold if mail not acknowledged */ 117 | this.mailGridElem.cellClassNameGenerator = (column, rowData: GridItemModel) => { 118 | let classes = ''; 119 | classes += determineMailCssClass(rowData.item.mailItem); 120 | const is_old = hasMailBeenOpened(rowData.item.mailItem); 121 | //console.log('hasMailBeenOpened: ', is_old, idB64); 122 | if (!is_old) { 123 | classes += ' newmail'; 124 | } 125 | return classes; 126 | }; 127 | 128 | this.mailGridElem.shadowRoot.appendChild(stylesTemplate.content.cloneNode(true)); 129 | 130 | const fileboxLayout = this.shadowRoot.getElementById('fileboxLayout') as HorizontalLayout; 131 | if (HAPP_BUILD_MODE == HappBuildModeType.Debug) { 132 | fileboxLayout.style.backgroundColor = "rgba(241,154,154,0.82)"; 133 | } 134 | 135 | this.fillMailGrid(); 136 | } 137 | 138 | 139 | /** */ 140 | onMenuItemSelected(e: MenuBarItemSelectedEvent) { 141 | console.log(" filebox.onMenuItemSelected()", e.detail.value); 142 | this.dispatchEvent(new CustomEvent('menu-item-selected', { detail: e.detail.value.text, bubbles: true, composed: true })); 143 | } 144 | 145 | 146 | /** On item select: Display in inMailArea */ 147 | onMailSelected(item: MailGridItem) { 148 | console.log(" filebox.onMailSelected()", item); 149 | this.dispatchEvent(new CustomEvent('mail-item-selected', { detail: item, bubbles: true, composed: true })); 150 | this._selectedItems = item ? [item] : []; 151 | } 152 | 153 | 154 | /** */ 155 | disableMenuButtons(isDisabled: boolean): void { 156 | console.log(" disableMenuButtons() called", isDisabled) 157 | 158 | if (this._menuItems[2].disabled === isDisabled) { 159 | return; 160 | } 161 | 162 | /** Deep-copy MenuBarItems so it can trigger a new render */ 163 | const items = JSON.parse(JSON.stringify(this._menuItems)) as MenuBarItem[]; 164 | items[1].disabled = isDisabled; 165 | items[2].disabled = isDisabled; 166 | items[3].disabled = isDisabled; 167 | 168 | this._menuItems = items; 169 | } 170 | 171 | 172 | /** On value change */ 173 | onFolderChange(folderValue: string) { 174 | this.resetSelection(); 175 | this.update_mailGrid(folderValue) 176 | this._currentFolder = folderValue; 177 | } 178 | 179 | 180 | /** */ 181 | fillMailGrid() { 182 | console.log(" fillMailGrid()") 183 | /** Get currently selected hashs */ 184 | const prevSelected = []; 185 | if (this.mailGridElem.selectedItems) { 186 | for (const mailItem of this.mailGridElem.selectedItems) { 187 | prevSelected.push(mailItem.id); 188 | } 189 | } 190 | 191 | let trashCount = 0; 192 | let inboxCount = 0; 193 | let sentCount = 0; 194 | let newCount = 0; 195 | 196 | const selected: MailGridItem[] = []; 197 | const items: MailGridItem[] = []; 198 | 199 | const selectedBox: string = this.folderElem.value//.codePointAt(0); 200 | 201 | const mailItems: MailItem[] = Object.values(this.perspective.mailMap); 202 | for (const mailItem of mailItems) { 203 | //console.log({mailItem}) 204 | const isDeleted = isMailDeleted(mailItem); 205 | const isOutMail = is_OutMail(mailItem); 206 | 207 | /** Counters */ 208 | if (isOutMail) { 209 | sentCount = sentCount + 1; 210 | } 211 | if (isDeleted) { 212 | trashCount = trashCount + 1; 213 | } 214 | if (!isDeleted && !isOutMail) { 215 | inboxCount = inboxCount + 1; 216 | } 217 | if (determineMailCssClass(mailItem) === 'newmail') { 218 | newCount = newCount + 1; 219 | } 220 | 221 | /** Determine if we should add to grid depending on current folder */ 222 | if (isDeleted && selectedBox !== systemFolders.TRASH) { 223 | continue; 224 | } 225 | if (isOutMail && selectedBox === systemFolders.INBOX) { 226 | continue; 227 | } 228 | if (!isOutMail && selectedBox === systemFolders.SENT) { 229 | continue; 230 | } 231 | const gridItem = into_gridItem(this.perspective.usernameMap, mailItem); 232 | // console.log('gridItem.id = ' + gridItem.id); 233 | items.push(gridItem); 234 | if (prevSelected.includes(gridItem.id)) { 235 | selected.push(gridItem); 236 | } 237 | } 238 | console.log(`Counters: ${newCount} / ${inboxCount} / ${sentCount} / ${trashCount} / ${mailItems.length}`); 239 | 240 | updateTray(newCount); 241 | 242 | // const systemFoldersVec = [ 243 | // systemFolders.ALL // + ' ('+ allCount +')' 244 | // , newCount === 0 ? systemFolders.INBOX : systemFolders.INBOX + ' ('+ newCount + ')' //+ inboxCount +')' 245 | // , systemFolders.SENT // + ' ('+ sentCount +')' 246 | // , systemFolders.TRASH // + ' ('+ trashCount +')' 247 | // ]; 248 | // 249 | // this.folderElem.items = systemFoldersVec; 250 | // for (const systemFolder of systemFoldersVec) { 251 | // //console.log("systemFolder.codePointAt(0) = " + systemFolder.codePointAt(0)); 252 | // if (selectedBox == systemFolder) { 253 | // this.folderElem.label = systemFolder; 254 | // break; 255 | // } 256 | // } 257 | 258 | console.log(` mailCount = ${items.length} ('${selected.length}')`); 259 | this._allMailGridItems = items; 260 | this._shownItems = filterMails(this._allMailGridItems, this.mailSearchElem.value); 261 | this._selectedItems = selected; 262 | this.mailGridElem.items = this._shownItems; 263 | this.mailGridElem.selectedItems = selected; 264 | this.mailGridElem.activeItem = selected[0]; 265 | } 266 | 267 | 268 | /** */ 269 | update_mailGrid(folderValue: string): void { 270 | console.log(' update_mailGrid:', folderValue); 271 | this._curFolderItems = []; 272 | 273 | switch(folderValue) { 274 | case systemFolders.ALL/*.codePointAt(0)*/: 275 | for (const mailItem of Object.values(this._allMailGridItems)) { 276 | //folderItems = Array.from(g_mail_map.values()); 277 | this._curFolderItems.push(mailItem); 278 | } 279 | break; 280 | case systemFolders.INBOX/*.codePointAt(0)*/: 281 | case systemFolders.SENT/*.codePointAt(0)*/: 282 | //console.log("this.perspective.mailMap", this.perspective.mailMap) 283 | for (const mailGridItem of Object.values(this._allMailGridItems)) { 284 | console.log('mailItem', mailGridItem.id); 285 | const is_out = is_OutMail(mailGridItem.mailItem); 286 | if (isMailDeleted(mailGridItem.mailItem)) { 287 | continue; 288 | } 289 | if (is_out && folderValue == systemFolders.SENT/*.codePointAt(0)*/) { 290 | this._curFolderItems.push(mailGridItem); 291 | continue; 292 | } 293 | if (!is_out && folderValue == systemFolders.INBOX/*.codePointAt(0)*/) { 294 | this._curFolderItems.push(mailGridItem); 295 | } 296 | } 297 | break; 298 | case systemFolders.TRASH/*.codePointAt(0)*/: { 299 | for (const mailGridItem of Object.values(this._allMailGridItems)) { 300 | if(isMailDeleted(mailGridItem.mailItem)) { 301 | this._curFolderItems.push(mailGridItem); 302 | } 303 | } 304 | } 305 | break; 306 | default: 307 | console.error('Unknown folder') 308 | } 309 | 310 | const span: HTMLElement = this.shadowRoot.getElementById('messageCount'); 311 | span.textContent = this._curFolderItems.length.toString(); 312 | 313 | console.log(' folderItems count: ', this._curFolderItems.length); 314 | this._shownItems = filterMails(this._curFolderItems, this.mailSearchElem.value); 315 | console.log(' update_mailGrid() _shownItems', this._shownItems); 316 | } 317 | 318 | 319 | /** */ 320 | updated() { 321 | /** Update active Item */ 322 | console.log(' .updated()'); 323 | if(this.mailGridElem.activeItem) { 324 | let newActiveItem: MailGridItem; 325 | for(const item of this.mailGridElem.items) { 326 | if(item.id === this.mailGridElem.activeItem.id) { 327 | newActiveItem = item; 328 | break; 329 | } 330 | } 331 | this.mailGridElem.selectItem(newActiveItem); 332 | } 333 | } 334 | 335 | 336 | /** */ 337 | protected willUpdate(changedProperties: PropertyValues) { 338 | super.willUpdate(changedProperties); 339 | console.log(" .willUpdate()", this._shownItems); 340 | 341 | /** Handle mails from perspective */ 342 | if (changedProperties.has('perspective')) { 343 | const allMailGridItem: MailGridItem[] = []; 344 | for (const mailItem of Object.values(this.perspective.mailMap)) { 345 | allMailGridItem.push(into_gridItem(this.perspective.usernameMap, mailItem)); 346 | } 347 | //console.log(" .willUpdate() this._allMailItems", this._allMailGridItems, allMailGridItem); 348 | this._allMailGridItems = allMailGridItem; 349 | /** Update grid if it exists */ 350 | if (!this.folderElem || !this.mailGridElem) { 351 | return; 352 | } 353 | try { 354 | this.update_mailGrid(this.folderElem.value); 355 | } catch(e) { 356 | console.error(".willUpdate() failed", e) 357 | } 358 | } 359 | 360 | /** Determine menu buttons state */ 361 | let isDisabled = true; 362 | if (this._selectedItems.length > 0) { 363 | if (!isMailDeleted(this._selectedItems[0].mailItem)) { 364 | isDisabled = false; 365 | } 366 | } 367 | this.disableMenuButtons(isDisabled); 368 | } 369 | 370 | 371 | 372 | /** 373 | */ 374 | render() { 375 | console.log(".render()", this._selectedItems.length); 376 | return html` 377 | 378 | 379 | 380 | 386 | 387 | 392 | 393 | messages: 0 394 | 399 | 400 | 401 | 402 | 403 | 404 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | `; 421 | } 422 | 423 | /** */ 424 | static get styles() { 425 | return [ 426 | css` 427 | `]; 428 | } 429 | 430 | } 431 | --------------------------------------------------------------------------------