Compare commits

...

26 Commits

Author SHA1 Message Date
EthanShoeDev
5ca24b539e lock update 2025-10-10 02:58:00 -04:00
EthanShoeDev
f3c6992b0a website update 2025-10-10 02:30:15 -04:00
EthanShoeDev
247a67aa60 Add privacy policy 2025-10-09 16:52:00 -04:00
EthanShoeDev
0f468b9f2d rm patched pkg 2025-10-08 19:59:44 -04:00
EthanShoeDev
856be66449 chore(@fressh/react-native-uniffi-russh): release v0.0.5 2025-10-07 21:50:54 -04:00
EthanShoeDev
47f7844aea adjust config again 2025-10-07 21:38:53 -04:00
EthanShoeDev
3f48e96a4f chore(@fressh/react-native-uniffi-russh): release v0.0.4 2025-10-07 21:28:55 -04:00
EthanShoeDev
e8808bddfb publish configs 2025-10-07 21:20:37 -04:00
EthanShoeDev
242f26b383 chore(@fressh/react-native-xtermjs-webview): release v0.0.8 2025-10-07 21:05:35 -04:00
EthanShoeDev
de963a9d64 try again to remove .turbo from npm 2025-10-07 21:04:47 -04:00
EthanShoeDev
ec3876ac3a chore(@fressh/react-native-uniffi-russh): release v0.0.3 2025-10-07 20:54:33 -04:00
EthanShoeDev
64d385038c chore(@fressh/react-native-xtermjs-webview): release v0.0.7 2025-10-07 20:52:01 -04:00
EthanShoeDev
57765da5e8 publish configs 2025-10-07 20:48:24 -04:00
EthanShoeDev
26c5bb8332 chore(@fressh/react-native-xtermjs-webview): release v0.0.6 2025-10-07 17:07:08 -04:00
EthanShoeDev
3a1893e08c chore(@fressh/react-native-uniffi-russh): release v0.0.2 2025-10-07 15:53:18 -04:00
EthanShoeDev
49d4a667b9 small change 2025-10-07 15:40:00 -04:00
EthanShoeDev
fffc3b103c flake fmt 2025-10-07 14:51:08 -04:00
EthanShoeDev
a3c2b67513 flake change 2025-10-07 14:46:28 -04:00
EthanShoeDev
3acde79485 config 2025-10-07 13:46:19 -04:00
EthanShoeDev
711335367e add src 2025-10-07 13:10:04 -04:00
EthanShoeDev
e4139c6f7b small tweaks 2025-10-07 10:19:45 -04:00
EthanShoeDev
8e7ec01e28 small bug fix 2025-10-07 01:24:15 -04:00
EthanShoeDev
0fc4481e82 chore(@fressh/react-native-xtermjs-webview): release v0.0.5 2025-10-07 00:46:30 -04:00
EthanShoeDev
fa2ad2708e changelog 2025-10-07 00:45:42 -04:00
EthanShoeDev
dc1a19baad release config 2025-10-07 00:22:39 -04:00
EthanShoeDev
2b33da8e20 Better docs 2025-10-07 00:21:46 -04:00
52 changed files with 1403 additions and 1261 deletions

View File

@@ -9,6 +9,7 @@
"yoavbls.pretty-ts-errors",
"ctf0.duplicated-code-new",
"github.vscode-github-actions",
"mkhl.direnv"
"mkhl.direnv",
"jnoortheen.nix-ide"
]
}

View File

@@ -1,3 +1,71 @@
nix develop .#default
## Contributing
### Monorepo layout
- `apps/mobile`: Expo app (serves as the example for both packages)
- `apps/web`: Static site (Astro)
- `packages/react-native-uniffi-russh`: React Native native module exposing
russh via UniFFI
- `packages/react-native-xtermjs-webview`: React Native WebView-based xterm.js
renderer
### Prerequisites
- Node and pnpm installed
- Optional: Nix for dev shells (recommended)
- For native module work: Rust toolchain (rustup, cargo), Android/iOS build
tools
With Nix:
```
nix develop .#default
```
Dev shell with android emulator included:
```
nix develop .#android-emulator
```
### Setup
1. Clone the repo
2. Install dependencies at the root:
```
pnpm install
```
3. Run the lint command:
```
pnpm exec turbo lint
```
### Develop
- Mobile app:
```
cd apps/mobile
pnpm run android
```
### Releasing
Each publishable package uses release-it. From the package directory:
```
pnpm run release
```
See the package CHANGELOGs for release notes:
- `packages/react-native-uniffi-russh/CHANGELOG.md`
- `packages/react-native-xtermjs-webview/CHANGELOG.md`
### CI
Pull requests run the workflow in `.github/workflows/check.yml`. Please ensure
lint/typecheck/tests pass.

View File

@@ -1,11 +1,80 @@
Fressh is a mobile SSH client that remains clean and simple while supporting
powerful features:
## Fressh
- Securely storing previous connections
- Configurable preset command buttons
- Configurable theme
[Fressh](https://fressh.dev/) is a mobile SSH client that remains clean and
simple while supporting powerful features.
Coming soon
[![ci](https://github.com/EthanShoeDev/fressh/actions/workflows/check.yml/badge.svg)](https://github.com/EthanShoeDev/fressh/actions/workflows/check.yml)
[![npm: @fressh/react-native-uniffi-russh](https://img.shields.io/npm/v/%40fressh%2Freact-native-uniffi-russh)](https://www.npmjs.com/package/@fressh/react-native-uniffi-russh)
[![npm: @fressh/react-native-xtermjs-webview](https://img.shields.io/npm/v/%40fressh%2Freact-native-xtermjs-webview)](https://www.npmjs.com/package/@fressh/react-native-xtermjs-webview)
- Fully accurate xterm emulation
- On-device LLM for command completion and output summarization
### Features
- **Secure connection history**: Securely store previous connections
- **Theming**: Configurable theme
- **xterm fidelity**: Fully accurate xterm emulation
### Coming soon
- **Command presets**: Configurable preset command buttons
- **On-device AI**: On-device LLM for command completion and output
summarization
### Screenshots
![Hosts tab](./packages/assets/mobile-screenshots/hosts-tab.png)
![Shell detail](./packages/assets/mobile-screenshots/shell-detail.png)
### Architecture
The app is a monorepo with three main parts:
- **`apps/mobile`**: The actual React Native Expo app.
- **`packages/react-native-uniffi-russh`**: A
[uniffi react native](https://github.com/jhugman/uniffi-bindgen-react-native)
binding package that exposes a native Rust module for
[russh](https://github.com/Eugeny/russh).
- **`packages/react-native-xtermjs-webview`**: A small library that instantiates
an Expo WebView preloaded with [xterm.js](https://xtermjs.org/).
Both packages are published on npm if you want to use them in your own project:
- [`@fressh/react-native-uniffi-russh`](https://www.npmjs.com/package/@fressh/react-native-uniffi-russh)
- [`@fressh/react-native-xtermjs-webview`](https://www.npmjs.com/package/@fressh/react-native-xtermjs-webview)
### Why
Mostly to practice with React Native, Expo, and Rust. There are a few more
developed SSH clients on the Google Play and iOS App Stores.
Some of those try to lock features like one-off commands behind a paywall, so
this aims to be a free alternative.
Another notable feature of the app is the WebView xterm.js renderer. Using this
as the render layer has a few benefits:
- **Parity with VS Code**: We match the render behavior of VS Code
- **Consistent visuals**: The render layer visually matches on both iOS and
Android
With that said, it is probably less performant than a native renderer, so it may
be replaced in the future. Implementing a
[Nitro view](https://nitro.margelo.com/docs/view-components) seems very
promising.
### Changelogs
- `apps/mobile`: [`apps/mobile/CHANGELOG.md`](./apps/mobile/CHANGELOG.md)
- `@fressh/react-native-uniffi-russh`:
[`packages/react-native-uniffi-russh/CHANGELOG.md`](./packages/react-native-uniffi-russh/CHANGELOG.md)
- `@fressh/react-native-xtermjs-webview`:
[`packages/react-native-xtermjs-webview/CHANGELOG.md`](./packages/react-native-xtermjs-webview/CHANGELOG.md)
### Contributing
We provide a Nix flake devshell to help get a working environment quickly. See
[`CONTRIBUTING.md`](./CONTRIBUTING.md) for details.
### License
MIT

View File

@@ -1,60 +1,28 @@
# Welcome to your Expo app 👋
## Fressh Mobile (Expo)
This is an [Expo](https://expo.dev) project created with
[`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
This is the Fressh mobile app built with Expo. It provides a clean SSH client
experience. packages:
## Get started
- `@fressh/react-native-uniffi-russh`
- `@fressh/react-native-xtermjs-webview`
1. Install dependencies
```bash
pnpm install
```
2. Start the app
```bash
pnpm exec expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app
development with Expo
You can start developing by editing the files inside the **app** directory. This
project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
### Setup
```bash
pnpm run reset-project
pnpm install
pnpm exec expo start
```
This command will move the starter code to the **app-example** directory and
create a blank **app** directory where you can start developing.
Open using Expo Go, an emulator, or a simulator.
## Learn more
For a high-level feature overview, see the root [`README.md`](../../README.md).
To learn more about developing your project with Expo, look at the following
resources:
### Development notes
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into
advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a
step-by-step tutorial where you'll create a project that runs on Android, iOS,
and the web.
- Edit files under `app/` (file-based routing)
- Ensure Android/iOS tooling is installed for native builds
## Join the community
### Links
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform
and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask
questions.
- Main README: [`../../README.md`](../../README.md)
- Changelog: [`./CHANGELOG.md`](./CHANGELOG.md)

View File

@@ -15,7 +15,7 @@ const config: ExpoConfig = {
slug: 'fressh',
version: packageJson.version,
orientation: 'portrait',
icon: '../../packages/assets/ios-dark-2.png',
icon: '../../packages/assets/mobile-app-icon-dark.png',
scheme: 'fressh',
userInterfaceStyle: 'automatic',
newArchEnabled: true,
@@ -24,34 +24,36 @@ const config: ExpoConfig = {
config: { usesNonExemptEncryption: false },
bundleIdentifier: 'dev.fressh.app',
buildNumber: String(versionCode),
// TODO: Add ios specific icons
// icon: {
// dark: '',
// light: '',
// tinted: '',
// }
},
android: {
package: 'dev.fressh.app',
versionCode,
adaptiveIcon: {
foregroundImage: '../../packages/assets/adaptive-icon.png',
foregroundImage: '../../packages/assets/android-adaptive-icon.png',
backgroundColor: '#151718',
},
edgeToEdgeEnabled: true,
predictiveBackGestureEnabled: false,
softwareKeyboardLayoutMode: 'pan',
},
web: {
output: 'static',
favicon: '../../packages/assets/favicon.png',
},
plugins: [
'expo-router',
[
'expo-splash-screen',
{
image: '../../packages/assets/splash-icon-light.png',
backgroundColor: '#ECEDEE',
dark: {
image: '../../packages/assets/splash-icon-dark.png',
backgroundColor: '#151718',
},
imageWidth: 200,
backgroundColor: '#ECEDEE',
},
],
'expo-secure-store',

View File

@@ -21,7 +21,7 @@
"prebuild": "expo prebuild",
"prebuild:clean": "expo prebuild --clean",
"expo:dep:check": "expo install --fix",
"expo:doctor": "pnpm dlx expo-doctor@latest",
"expo:doctor": "pnpm dlx expo-doctor@latest || true",
"test:e2e": "maestro test test/e2e/",
"adb:logs": "while ! adb logcat --pid=$(adb shell pidof -s dev.fressh.app); do sleep 1; done",
"release": "GITHUB_TOKEN=$(gh auth token) release-it",
@@ -40,19 +40,19 @@
"@tanstack/react-form": "^1.20.0",
"@tanstack/react-query": "^5.89.0",
"date-fns": "^4.1.0",
"expo": "54.0.12",
"expo": "54.0.13",
"expo-clipboard": "~8.0.7",
"expo-constants": "~18.0.9",
"expo-crypto": "~15.0.7",
"expo-dev-client": "~6.0.13",
"expo-dev-client": "~6.0.14",
"expo-document-picker": "~14.0.7",
"expo-file-system": "~19.0.16",
"expo-font": "~14.0.8",
"expo-file-system": "~19.0.17",
"expo-font": "~14.0.9",
"expo-glass-effect": "^0.1.4",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.8",
"expo-image": "~3.0.9",
"expo-linking": "~8.0.8",
"expo-router": "6.0.10",
"expo-router": "6.0.11",
"expo-secure-store": "~15.0.7",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8",

View File

@@ -467,6 +467,9 @@ type KeyboardToolbarButtonProps =
| KeyboardToolbarModifierButtonProps
| KeyboardToolbarInstantButtonProps;
const propsToKey = (props: KeyboardToolbarButtonProps) =>
'label' in props ? props.label : props.iconName;
function KeyboardToolbarButton({
style,
...props
@@ -487,7 +490,8 @@ function KeyboardToolbarButton({
);
const modifierActive =
props.type === 'modifier' && modifierKeysActive.includes(props);
props.type === 'modifier' &&
!!modifierKeysActive.find((m) => propsToKey(m) === propsToKey(props));
return (
<Pressable
@@ -505,8 +509,10 @@ function KeyboardToolbarButton({
onPress={() => {
if (props.type === 'modifier') {
setModifierKeysActive((modifierKeysActive) =>
modifierKeysActive.includes(props)
? modifierKeysActive.filter((m) => m !== props)
modifierKeysActive.find((m) => propsToKey(m) === propsToKey(props))
? modifierKeysActive.filter(
(m) => propsToKey(m) !== propsToKey(props),
)
: [...modifierKeysActive, props],
);
return;

View File

@@ -2,10 +2,6 @@
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
// https://github.com/jhugman/uniffi-bindgen-react-native/pull/198
// https://github.com/microsoft/TypeScript/issues/41883#issuecomment-1758692340
// TODO: Get this merged
// https://github.com/jhugman/uniffi-bindgen-react-native/pull/297
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

View File

@@ -1,48 +1,17 @@
# Astro Starter Kit: Basics
## Fressh Web
```sh
pnpm create astro@latest -- --template basics
This is an Astro site used for the project website/docs. For project details,
see the root README.
### Commands
```bash
pnpm install
pnpm dev
pnpm build
pnpm preview
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
### Links
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── Welcome.astro
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│   └── index.astro
└── package.json
```
To learn more about the folder structure of an Astro project, refer to
[our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `pnpm install` | Installs dependencies |
| `pnpm dev` | Starts local dev server at `localhost:4321` |
| `pnpm build` | Build your production site to `./dist/` |
| `pnpm preview` | Preview your build locally, before deploying |
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
| `pnpm astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into
our [Discord server](https://astro.build/chat).
- Main README: [`../../README.md`](../../README.md)

View File

@@ -14,11 +14,12 @@
"preview": "astro preview"
},
"dependencies": {
"@astrojs/vercel": "^8.2.7",
"@astrojs/vercel": "^8.2.9",
"@fressh/assets": "workspace:*",
"@tailwindcss/vite": "4.1.9",
"@vercel/analytics": "^1.5.0",
"astro": "^5.13.7",
"astro": "^5.14.3",
"sharp": "^0.34.4",
"tailwindcss": "4.1.10"
},
"devDependencies": {

View File

@@ -1,11 +1,17 @@
---
import Layout from '../layouts/Layout.astro';
import iosDarkAppIcon from '@fressh/assets/ios-dark-2.png';
import mobileAppIconDark from '@fressh/assets/mobile-app-icon-dark.png';
import hostsTabAndroidScreenshot from '@fressh/assets/mobile-screenshots/hosts-tab.png';
import shellDetailAndroidScreenshot from '@fressh/assets/mobile-screenshots/shell-detail.png';
import GithubMark from '@fressh/assets/third-party-brands/github-mark/github-mark.svg';
import GooglePlayBadge from '@fressh/assets/third-party-brands/google-play/GetItOnGooglePlay_Badge_Web_color_English.svg';
import AppStoreBadge from '@fressh/assets/third-party-brands/apple-app-store/Black_lockup/SVG/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg';
import npmLogoRed from '@fressh/assets/third-party-brands/npm-js/npm-logo-red.png';
const title = 'Fressh — Mobile SSH Client';
const description =
'A clean, powerful mobile SSH client. Built with React Native, powered by Russh (Rust-based SSH), and planned to be open source.';
const image = iosDarkAppIcon.src;
'A clean, powerful open-source mobile SSH client. Built with React Native and powered by Russh (Rust-based SSH).';
const image = mobileAppIconDark.src;
---
<Layout>
@@ -21,104 +27,285 @@ const image = iosDarkAppIcon.src;
<meta name="twitter:image" content={image} />
</Fragment>
<section
class="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-gray-50 to-white px-6 dark:from-gray-900 dark:to-black"
class="bg-gradient-to-b from-gray-50 via-white to-white dark:from-gray-950 dark:via-gray-950 dark:to-black"
>
<img
src={iosDarkAppIcon.src}
alt="Fressh app icon"
class="mb-6 h-20 w-20 rounded-2xl shadow-md"
/>
<h1
class="text-5xl font-extrabold tracking-tight text-gray-900 sm:text-6xl dark:text-white"
>
Fressh
</h1>
<p class="mt-3 text-lg text-gray-600 dark:text-gray-300">
A clean, powerful mobile SSH client.
</p>
<span
class="mt-4 inline-flex items-center gap-2 rounded-full border border-dashed border-gray-300 px-3 py-1 text-sm text-gray-600 dark:border-gray-700 dark:text-gray-300"
>Coming soon</span
>
<div
class="mt-10 grid w-full max-w-3xl gap-6 text-left sm:grid-cols-2 lg:grid-cols-3"
class="mx-auto flex min-h-screen w-full max-w-6xl flex-col justify-center gap-16 px-6 py-24"
>
<div class="rounded-xl border border-gray-200 p-6 dark:border-gray-800">
<h2
class="text-base font-semibold tracking-wide text-gray-900 dark:text-gray-100"
>
Features
</h2>
<ul class="mt-3 space-y-2 text-gray-700 dark:text-gray-300">
<li class="flex items-start gap-3">
<span class="text-emerald-500">✓</span><span
>Securely storing previous connections</span
>
</li>
<li class="flex items-start gap-3">
<span class="text-emerald-500">✓</span><span
>Configurable preset command buttons</span
>
</li>
<li class="flex items-start gap-3">
<span class="text-emerald-500">✓</span><span
>Configurable theme</span
>
</li>
<ul></ul>
</ul>
</div>
<div class="rounded-xl border border-gray-200 p-6 dark:border-gray-800">
<h2
class="text-base font-semibold tracking-wide text-gray-900 dark:text-gray-100"
>
Coming soon
</h2>
<ul class="mt-3 space-y-2 text-gray-700 dark:text-gray-300">
<li class="flex items-start gap-3">
<span class="text-amber-500">•</span><span
>Fully accurate xterm emulation</span
>
</li>
<li class="flex items-start gap-3">
<span class="text-amber-500">•</span><span
>On-device LLM for command completion and output summarization</span
>
</li>
<ul></ul>
</ul>
</div>
<div class="rounded-xl border border-gray-200 p-6 dark:border-gray-800">
<h2
class="text-base font-semibold tracking-wide text-gray-900 dark:text-gray-100"
>
Technical specs
</h2>
<ul class="mt-3 space-y-2 text-gray-700 dark:text-gray-300">
<li class="flex items-start gap-3">
<span class="text-blue-500">•</span>
<span>UI built with React Native</span>
</li>
<li class="flex items-start gap-3">
<span class="text-blue-500">•</span>
<span>
SSH core powered by
<a
href="https://github.com/Eugeny/russh"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline decoration-dotted hover:decoration-solid dark:text-blue-400"
<div class="grid gap-16 lg:max-w-4xl">
<div class="space-y-8">
<div class="flex items-start gap-4">
<img
src={mobileAppIconDark.src}
alt="Fressh app icon"
class="h-20 w-20 shrink-0 rounded-3xl border border-white/30 shadow-xl shadow-emerald-500/10 dark:border-white/10"
loading="eager"
/>
<div class="flex flex-col items-start gap-2">
<span
class="inline-flex items-center gap-2 rounded-full border border-emerald-300/70 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-900 dark:border-emerald-500/50 dark:bg-emerald-500/15 dark:text-emerald-200"
>
Russh
</a>
(Rust-based SSH library)
</span>
</li>
<li class="flex items-start gap-3">
<span class="text-blue-500">•</span>
<span>Planned to be open source</span>
</li>
</ul>
<span class="inline-block h-2 w-2 rounded-full bg-emerald-500"
></span>
Coming soon
</span>
<span
class="text-sm font-semibold tracking-[0.3em] text-gray-500 uppercase dark:text-gray-400"
>
Mobile SSH Client
</span>
</div>
</div>
<h1
class="text-4xl font-black tracking-tight text-gray-900 sm:text-5xl lg:text-6xl dark:text-white"
>
Fressh — Mobile SSH Client
</h1>
<p
class="max-w-xl text-lg leading-relaxed text-gray-600 dark:text-gray-300"
>
A clean, powerful open-source mobile SSH client. Built with React
Native and powered by Russh (Rust-based SSH).
</p>
<div class="flex flex-wrap items-center gap-4">
<a
href="https://github.com/EthanShoeDev/fressh"
target="_blank"
rel="noopener noreferrer"
class="group inline-flex items-center gap-3 rounded-full border border-gray-200 bg-white/70 px-5 py-3 text-sm font-medium text-gray-900 shadow-sm backdrop-blur transition hover:border-gray-300 hover:bg-white dark:border-white/10 dark:bg-white/5 dark:text-white dark:hover:border-white/20 dark:hover:bg-white/10"
>
<GithubMark
class="h-5 w-5"
viewBox="0 0 98 96"
preserveAspectRatio="xMidYMid meet"
aria-hidden="true"
/>
<span>View the source on GitHub</span>
</a>
<div
class="flex flex-col gap-2 text-xs text-gray-500 dark:text-gray-400"
>
<div class="flex items-center gap-6">
<div class="flex flex-col items-start gap-2">
<GooglePlayBadge
class="h-12 w-auto select-none"
role="img"
aria-label="Get it on Google Play badge"
/>
<span
class="inline-flex items-center gap-2 rounded-full border border-dashed border-gray-300 px-2 py-0.5 text-[11px] font-medium tracking-wider text-gray-500 uppercase dark:border-gray-700 dark:text-gray-400"
>Coming soon</span
>
</div>
<div class="flex flex-col items-start gap-2">
<div class="flex h-12 items-center rounded-lg">
<AppStoreBadge
class="h-9 w-auto select-none"
role="img"
aria-label="Download on the App Store badge"
/>
</div>
<span
class="inline-flex items-center gap-2 rounded-full border border-dashed border-gray-300 px-2 py-0.5 text-[11px] font-medium tracking-wider text-gray-500 uppercase dark:border-gray-700 dark:text-gray-400"
>Coming soon</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="grid gap-6 lg:grid-cols-3">
<article
class="rounded-3xl border border-gray-200/80 bg-white/80 p-8 backdrop-blur-sm transition hover:border-emerald-200 dark:border-white/10 dark:bg-white/5 dark:hover:border-emerald-500/30"
>
<h2
class="text-sm font-semibold tracking-wider text-emerald-600 uppercase dark:text-emerald-300"
>
Features
</h2>
<ul
class="mt-4 space-y-3 text-sm leading-relaxed text-gray-700 dark:text-gray-300"
>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-semibold text-emerald-600 dark:text-emerald-300"
>✓</span
>
<span>Securely store previous connections</span>
</li>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-semibold text-emerald-600 dark:text-emerald-300"
>✓</span
>
<span>Configurable theme for approachable ergonomics</span>
</li>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-semibold text-emerald-600 dark:text-emerald-300"
>✓</span
>
<span>Fully accurate xterm emulation</span>
</li>
</ul>
</article>
<article
class="rounded-3xl border border-gray-200/80 bg-white/80 p-8 backdrop-blur-sm transition hover:border-amber-200 dark:border-white/10 dark:bg-white/5 dark:hover:border-amber-400/30"
>
<h2
class="text-sm font-semibold tracking-wider text-amber-600 uppercase dark:text-amber-300"
>
Coming soon
</h2>
<ul
class="mt-4 space-y-3 text-sm leading-relaxed text-gray-700 dark:text-gray-300"
>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-amber-500/10 text-sm font-semibold text-amber-600 dark:text-amber-300"
>•</span
>
<span
>On-device LLM for command completion and output summarization</span
>
</li>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-amber-500/10 text-sm font-semibold text-amber-600 dark:text-amber-300"
>•</span
>
<span>Configurable preset command buttons</span>
</li>
</ul>
</article>
<article
class="rounded-3xl border border-gray-200/80 bg-white/80 p-8 backdrop-blur-sm transition hover:border-blue-200 dark:border-white/10 dark:bg-white/5 dark:hover:border-blue-400/30"
>
<h2
class="text-sm font-semibold tracking-wider text-blue-600 uppercase dark:text-blue-300"
>
Technical specs
</h2>
<ul
class="mt-4 space-y-3 text-sm leading-relaxed text-gray-700 dark:text-gray-300"
>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-blue-500/10 text-sm font-semibold text-blue-600 dark:text-blue-300"
>•</span
>
<span>UI built with React Native</span>
</li>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-blue-500/10 text-sm font-semibold text-blue-600 dark:text-blue-300"
>•</span
>
<span>
SSH core powered by
<a
href="https://github.com/Eugeny/russh"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline decoration-dotted hover:decoration-solid dark:text-blue-400"
>
Russh
</a>
(Rust-based SSH library)
</span>
</li>
<li class="flex items-start gap-3">
<span
class="mt-1 inline-flex h-5 w-5 items-center justify-center rounded-full bg-blue-500/10 text-sm font-semibold text-blue-600 dark:text-blue-300"
>•</span
>
<span>
Open source on
<a
href="https://github.com/EthanShoeDev/fressh"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline decoration-dotted hover:decoration-solid dark:text-blue-400"
>
GitHub
</a>
</span>
</li>
</ul>
</article>
</div>
<div class="grid gap-6 lg:grid-cols-2">
<a
href="https://www.npmjs.com/package/@fressh/react-native-uniffi-russh"
target="_blank"
rel="noopener noreferrer"
class="group relative overflow-hidden rounded-3xl border border-gray-200 bg-white/80 p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-emerald-200 hover:shadow-xl dark:border-white/10 dark:bg-white/5 dark:hover:border-emerald-500/30"
>
<img
src={npmLogoRed.src}
alt="npm"
class="absolute top-5 right-5 h-4 w-auto opacity-80"
loading="lazy"
/>
<h3
class="pr-12 text-lg font-semibold text-gray-900 transition group-hover:text-emerald-600 dark:text-gray-100 dark:group-hover:text-emerald-300"
>
@fressh/react-native-uniffi-russh
</h3>
<p
class="mt-3 text-sm leading-relaxed text-gray-600 dark:text-gray-300"
>
React Native bindings (via uniffi) exposing a native Rust module for
the Russh SSH library.
</p>
</a>
<a
href="https://www.npmjs.com/package/@fressh/react-native-xtermjs-webview"
target="_blank"
rel="noopener noreferrer"
class="group relative overflow-hidden rounded-3xl border border-gray-200 bg-white/80 p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-blue-200 hover:shadow-xl dark:border-white/10 dark:bg-white/5 dark:hover:border-blue-400/30"
>
<img
src={npmLogoRed.src}
alt="npm"
class="absolute top-5 right-5 h-4 w-auto opacity-80"
loading="lazy"
/>
<h3
class="pr-12 text-lg font-semibold text-gray-900 transition group-hover:text-blue-600 dark:text-gray-100 dark:group-hover:text-blue-300"
>
@fressh/react-native-xtermjs-webview
</h3>
<p
class="mt-3 text-sm leading-relaxed text-gray-600 dark:text-gray-300"
>
An Expo WebView preloaded with xterm.js for terminal rendering on
iOS and Android.
</p>
</a>
</div>
<div class="mt-16">
<div
class="mx-auto max-w-5xl rounded-[2.5rem] border border-gray-200/70 bg-white/80 px-8 py-12 shadow-xl shadow-emerald-500/5 backdrop-blur dark:border-white/10 dark:bg-white/5 dark:shadow-emerald-500/10"
>
<div
class="flex flex-col items-center gap-8 lg:flex-row lg:justify-center"
>
<img
src={hostsTabAndroidScreenshot.src}
alt="Hosts tab screenshot"
class="w-full max-w-xs rounded-3xl border border-white/60 shadow-xl ring-1 shadow-emerald-500/15 ring-emerald-500/10 dark:border-white/10 dark:ring-white/10"
loading="lazy"
/>
<img
src={shellDetailAndroidScreenshot.src}
alt="Shell detail screenshot"
class="w-full max-w-xs rounded-3xl border border-white/60 shadow-xl ring-1 shadow-slate-900/10 ring-slate-900/10 dark:border-white/10 dark:ring-white/10"
loading="lazy"
/>
</div>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,95 @@
<head>
<title>Privacy Policy | Fressh</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<main>
<h1>Privacy Policy</h1>
<p>Effective date: October 9, 2025</p>
<p>
Fressh ("the App") is provided by an individual developer ("we", "us"). This
policy explains how the App handles information.
</p>
<h2>Summary</h2>
<ul>
<li>We do not collect any personal information or analytics.</li>
<li>The App has no backend servers; your data remains on your device.</li>
<li>No ads and no thirdparty tracking SDKs.</li>
</ul>
<h2>Data the App handles</h2>
<p>
The App allows you to store SSH connection details (e.g., hostnames,
usernames, ports, and optional private keys). This information is stored
locally on your device and is only transmitted to the servers you choose
when you initiate an SSH session.
</p>
<h3>Sensitive credentials</h3>
<p>
Private keys, passwords, and session data never leave your device except as
required to establish and maintain the SSH connection that you request. We
do not upload this information to any server we control.
</p>
<h2>Permissions</h2>
<ul>
<li>
<strong>Network access</strong>: required to connect to SSH servers you
choose.
</li>
<li>
<strong>Local storage</strong>: used to save SSH profiles/keys if you opt
to store them.
</li>
<li>
<strong>Clipboard</strong> (optional): used only when you copy/paste text during
a session.
</li>
</ul>
<h2>Collection, sharing, and retention</h2>
<ul>
<li>
<strong>Collection</strong>: We do not collect or process personal data.
</li>
<li>
<strong>Sharing</strong>: We do not sell or share data with third parties.
</li>
<li>
<strong>Retention</strong>: Data you save remains on your device until you
delete it or uninstall the App.
</li>
</ul>
<h2>Childrens privacy</h2>
<p>
The App is not directed to children. We do not knowingly collect personal
information from children under 13. If you believe a child has provided
information, please contact us so we can delete it.
</p>
<h2>Security</h2>
<p>
We rely on the security features of your devices operating system and the
SSH protocol. Protect your device with a strong passcode and keep your
operating system up to date.
</p>
<h2>Thirdparty services</h2>
<p>The App does not use advertising, analytics, or social media SDKs.</p>
<h2>Changes to this policy</h2>
<p>
We may update this policy from time to time. Changes will be posted on this
page with an updated effective date.
</p>
<h2>Contact</h2>
<p>
If you have questions about this policy, contact us at
<a href="mailto:ethanshoedev@gmail.com">ethanshoedev@gmail.com</a>.
</p>
</main>

View File

@@ -63,6 +63,8 @@
fen.targets.armv7-linux-androideabi.stable.rust-std
fen.targets.x86_64-linux-android.stable.rust-std
fen.targets.i686-linux-android.stable.rust-std
fen.targets.aarch64-apple-ios.stable.rust-std
fen.targets.aarch64-apple-ios-sim.stable.rust-std
];
defaultPkgs = with pkgs; [
@@ -93,6 +95,11 @@
clang-tools
];
mkShellFn =
if pkgs.stdenv.isDarwin
then pkgs.mkShellNoCC
else pkgs.mkShell;
ndkId = "27-1-12297006"; # nix flake show github:tadfisher/android-nixpkgs | grep ndk
ndkAttr = "ndk-${ndkId}";
ndkVer = builtins.replaceStrings ["-"] ["."] ndkId;
@@ -218,13 +225,13 @@
export PROMPT_COMMAND=". \"$FRESSH_STARSHIP_PREINIT\""
'';
in {
default = pkgs.mkShell {
default = mkShellFn {
packages = defaultPkgs ++ [remoteAndroidSdk.androidSdk];
shellHook =
commonAndroidInit remoteAndroidSdk.sdkRoot;
};
android-emulator = pkgs.mkShell {
android-emulator = mkShellFn {
packages = defaultPkgs ++ [fullAndroidSdk.androidSdk];
shellHook =
commonAndroidInit fullAndroidSdk.sdkRoot;

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

View File

Before

Width:  |  Height:  |  Size: 827 KiB

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z" style="fill: #fff"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.99671,19.88935a5.14625,5.14625,0,0,1,2.45058-4.31771,5.26776,5.26776,0,0,0-4.15039-2.24376c-1.74624-.1833-3.43913,1.04492-4.329,1.04492-.90707,0-2.27713-1.02672-3.75247-.99637a5.52735,5.52735,0,0,0-4.65137,2.8367c-2.01111,3.482-.511,8.59939,1.41551,11.414.96388,1.37823,2.09037,2.91774,3.56438,2.86315,1.4424-.05983,1.98111-.91977,3.7222-.91977,1.72494,0,2.23035.91977,3.73427.88506,1.54777-.02512,2.52292-1.38435,3.453-2.77563a11.39931,11.39931,0,0,0,1.579-3.21589A4.97284,4.97284,0,0,1,24.99671,19.88935Z"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.15611,11.47681a5.06687,5.06687,0,0,0,1.159-3.62989,5.15524,5.15524,0,0,0-3.33555,1.72582,4.82131,4.82131,0,0,0-1.18934,3.4955A4.26259,4.26259,0,0,0,22.15611,11.47681Z"/>
</g>
</g>
<g>
<path d="M42.30178,27.13965h-4.7334l-1.13672,3.35645H34.42678l4.4834-12.418h2.083l4.4834,12.418H43.43752Zm-4.24316-1.54883h3.752L39.961,20.14355H39.9092Z"/>
<path d="M55.1592,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.64455,21.34766,55.1592,23.16406,55.1592,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30178,29.01563,53.249,27.81934,53.249,25.96973Z"/>
<path d="M65.12453,25.96973c0,2.81348-1.50635,4.62109-3.77881,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C63.6094,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91064,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26662,29.01563,63.21389,27.81934,63.21389,25.96973Z"/>
<path d="M71.70949,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51758-3.61426,2.625,0,4.42383,1.47168,4.48438,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60547,1.626,3.60547,3.44238,0,2.32324-1.84961,3.77832-4.793,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z"/>
<path d="M86.064,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72512,30.6084,86.064,28.82617,86.064,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S92.7593,27.93164,92.7593,25.96973Z"/>
<path d="M96.18508,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z"/>
<path d="M109.38332,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10109,25.13477Z"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="artwork" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 155 60">
<!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
<defs>
<style>
.st0 {
fill: #4285f4;
}
.st1 {
fill: #a6a6a6;
}
.st2 {
stroke: #fff;
stroke-miterlimit: 10;
stroke-width: .2px;
}
.st2, .st3, .st4 {
fill: #fff;
}
.st5 {
fill: #34a853;
}
.st6 {
fill: #fbbc04;
}
.st4 {
fill-opacity: 0;
}
.st7 {
fill: #ea4335;
}
</style>
</defs>
<rect class="st4" width="155" height="60"/>
<rect x="10" y="10" width="135" height="40" rx="5" ry="5"/>
<path class="st1" d="M140,10.8c2.316,0,4.2,1.884,4.2,4.2v30c0,2.316-1.884,4.2-4.2,4.2H15c-2.316,0-4.2-1.884-4.2-4.2V15c0-2.316,1.884-4.2,4.2-4.2h125M140,10H15c-2.75,0-5,2.25-5,5v30c0,2.75,2.25,5,5,5h125c2.75,0,5-2.25,5-5V15c0-2.75-2.25-5-5-5h0Z"/>
<g>
<path class="st2" d="M57.418,20.243c0,.838-.248,1.505-.745,2.003-.564.592-1.3.888-2.204.888-.866,0-1.602-.3-2.208-.9-.606-.601-.909-1.345-.909-2.233s.303-1.633.909-2.233c.605-.601,1.342-.901,2.208-.901.43,0,.841.084,1.232.251.391.168.704.391.938.67l-.527.528c-.397-.475-.944-.712-1.643-.712-.632,0-1.178.222-1.639.666-.461.444-.691,1.021-.691,1.73s.23,1.286.691,1.73c.461.444,1.007.666,1.639.666.67,0,1.229-.223,1.676-.67.29-.291.458-.696.503-1.215h-2.179v-.721h2.907c.028.157.042.307.042.453Z"/>
<path class="st2" d="M62.028,17.737h-2.732v1.902h2.464v.721h-2.464v1.902h2.732v.737h-3.503v-6h3.503v.737Z"/>
<path class="st2" d="M65.279,23h-.771v-5.263h-1.676v-.737h4.123v.737h-1.676v5.263Z"/>
<path class="st2" d="M69.938,23v-6h.77v6h-.77Z"/>
<path class="st2" d="M74.128,23h-.771v-5.263h-1.676v-.737h4.123v.737h-1.676v5.263Z"/>
<path class="st2" d="M83.609,22.225c-.59.607-1.323.909-2.2.909s-1.61-.303-2.199-.909c-.59-.606-.884-1.348-.884-2.225s.294-1.619.884-2.225c.589-.607,1.322-.91,2.199-.91.872,0,1.603.305,2.196.914.592.609.888,1.349.888,2.221,0,.877-.295,1.619-.884,2.225ZM79.779,21.722c.444.45.987.674,1.63.674s1.186-.225,1.63-.674c.444-.45.667-1.024.667-1.722s-.223-1.273-.667-1.722c-.443-.45-.987-.674-1.63-.674s-1.186.225-1.63.674c-.443.45-.666,1.024-.666,1.722s.223,1.273.666,1.722Z"/>
<path class="st2" d="M85.575,23v-6h.939l2.916,4.667h.033l-.033-1.156v-3.511h.771v6h-.805l-3.051-4.894h-.033l.033,1.156v3.737h-.771Z"/>
</g>
<path class="st3" d="M78.136,31.752c-2.352,0-4.269,1.788-4.269,4.253,0,2.449,1.917,4.253,4.269,4.253s4.269-1.804,4.269-4.253c0-2.465-1.917-4.253-4.269-4.253ZM78.136,38.582c-1.289,0-2.4-1.063-2.4-2.578,0-1.531,1.112-2.578,2.4-2.578s2.4,1.047,2.4,2.578c0,1.514-1.112,2.578-2.4,2.578ZM68.823,31.752c-2.352,0-4.269,1.788-4.269,4.253,0,2.449,1.917,4.253,4.269,4.253s4.269-1.804,4.269-4.253c0-2.465-1.917-4.253-4.269-4.253ZM68.823,38.582c-1.289,0-2.401-1.063-2.401-2.578,0-1.531,1.112-2.578,2.401-2.578s2.4,1.047,2.4,2.578c0,1.514-1.112,2.578-2.4,2.578ZM57.744,33.057v1.804h4.318c-.129,1.015-.467,1.756-.983,2.272-.628.628-1.611,1.321-3.335,1.321-2.658,0-4.736-2.143-4.736-4.801s2.078-4.801,4.736-4.801c1.434,0,2.481.564,3.254,1.289l1.273-1.273c-1.079-1.031-2.513-1.82-4.527-1.82-3.641,0-6.702,2.964-6.702,6.605s3.061,6.605,6.702,6.605c1.966,0,3.448-.644,4.608-1.853,1.192-1.192,1.563-2.868,1.563-4.221,0-.419-.032-.805-.097-1.128h-6.074ZM103.052,34.458c-.354-.95-1.434-2.707-3.641-2.707-2.191,0-4.011,1.724-4.011,4.253,0,2.384,1.804,4.253,4.221,4.253,1.949,0,3.077-1.192,3.544-1.885l-1.45-.967c-.483.709-1.144,1.176-2.094,1.176s-1.627-.435-2.062-1.289l5.687-2.352-.193-.483ZM97.252,35.876c-.048-1.643,1.273-2.481,2.223-2.481.741,0,1.369.37,1.579.902l-3.802,1.579ZM92.629,40h1.869v-12.502h-1.869v12.502ZM89.567,32.702h-.064c-.419-.499-1.224-.951-2.239-.951-2.127,0-4.076,1.869-4.076,4.269,0,2.384,1.949,4.237,4.076,4.237,1.015,0,1.82-.451,2.239-.967h.064v.612c0,1.627-.87,2.497-2.272,2.497-1.144,0-1.853-.822-2.143-1.514l-1.627.677c.467,1.128,1.708,2.513,3.77,2.513,2.191,0,4.044-1.289,4.044-4.43v-7.636h-1.772v.693ZM87.425,38.582c-1.289,0-2.368-1.079-2.368-2.562,0-1.498,1.079-2.594,2.368-2.594,1.273,0,2.272,1.096,2.272,2.594,0,1.482-.999,2.562-2.272,2.562ZM111.806,27.499h-4.471v12.501h1.866v-4.736h2.605c2.068,0,4.101-1.497,4.101-3.883s-2.033-3.882-4.101-3.882ZM111.854,33.524h-2.654v-4.285h2.654c1.395,0,2.187,1.155,2.187,2.143,0,.969-.792,2.143-2.187,2.143ZM123.386,31.729c-1.351,0-2.75.595-3.329,1.914l1.657.692c.354-.692,1.013-.917,1.705-.917.965,0,1.946.579,1.962,1.608v.129c-.338-.193-1.061-.483-1.946-.483-1.785,0-3.603.981-3.603,2.815,0,1.673,1.463,2.75,3.104,2.75,1.254,0,1.946-.563,2.38-1.222h.064v.965h1.801v-4.793c0-2.22-1.657-3.458-3.796-3.458ZM123.161,38.58c-.611,0-1.464-.305-1.464-1.061,0-.965,1.061-1.335,1.978-1.335.82,0,1.206.177,1.705.418-.145,1.158-1.142,1.978-2.219,1.978ZM133.743,32.002l-2.139,5.42h-.064l-2.219-5.42h-2.01l3.329,7.575-1.898,4.214h1.946l5.131-11.789h-2.075ZM116.936,40h1.866v-12.501h-1.866v12.501Z"/>
<g>
<path class="st7" d="M30.717,29.424l-10.647,11.3s.001.005.002.007c.327,1.227,1.447,2.13,2.777,2.13.531,0,1.031-.144,1.459-.396l.034-.02,11.984-6.915-5.609-6.106Z"/>
<path class="st6" d="M41.488,27.5l-.01-.007-5.174-3-5.829,5.187,5.849,5.848,5.146-2.969c.902-.487,1.515-1.438,1.515-2.535,0-1.09-.604-2.036-1.498-2.525Z"/>
<path class="st0" d="M20.07,19.277c-.064.236-.098.484-.098.74v19.968c0,.256.033.504.098.739l11.013-11.011-11.013-10.436Z"/>
<path class="st5" d="M30.796,30.001l5.51-5.509-11.97-6.94c-.435-.261-.943-.411-1.486-.411-1.33,0-2.452.905-2.779,2.134,0,0,0,.002,0,.003l10.726,10.724Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

View File

Before

Width:  |  Height:  |  Size: 828 KiB

After

Width:  |  Height:  |  Size: 828 KiB

View File

Before

Width:  |  Height:  |  Size: 886 KiB

After

Width:  |  Height:  |  Size: 886 KiB

View File

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 882 KiB

View File

@@ -0,0 +1,33 @@
# Workspace caches
.turbo/
# Node modules (npm excludes by default)
node_modules/
# OS junk
**/.DS_Store
# Platform build outputs and local config
ios/build/
android/build/
android/gradle/
android/gradlew
android/gradlew.bat
android/local.properties
# Tests and mocks
**/__tests__/
**/__fixtures__/
**/__mocks__/
# Dotfiles and editors
**/.*
.vscode/
.idea/
# Coverage and logs
coverage/
*.log
*.local
rust/target/

View File

@@ -1,6 +1,10 @@
import type { Config } from 'release-it';
import { type Config } from 'release-it';
export default {
npm: {
publish: true,
publishArgs: ['--access', 'public'],
},
git: {
requireCleanWorkingDir: true,
tagName: '${npm.name}-v${version}',
@@ -10,20 +14,9 @@ export default {
push: true,
},
// This one *does* publish to npm
npm: {
publish: true,
// pass flags youd give to `npm publish`
publishArgs: ['--access', 'public'],
// (optional) skip npms own prepublish checks:
// skipChecks: true
},
github: {
release: true,
releaseName: '${npm.name} v${version}',
// optional: attach build artifacts
// assets: ['dist/**']
},
plugins: {
@@ -36,7 +29,7 @@ export default {
hooks: {
'before:init': ['turbo run lint:check'],
'before:npm:release': 'turbo run build:android build:ios',
'after:bump': ['turbo run build:android build:ios'],
'after:release': 'echo "Published ${npm.name} v${version} to npm"',
},
} satisfies Config;

View File

@@ -0,0 +1,11 @@
# Changelog
## [0.0.5](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-uniffi-russh-v0.0.4...${npm.name}-v0.0.5) (2025-10-08)
## [0.0.4](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-uniffi-russh-v0.0.3...${npm.name}-v0.0.4) (2025-10-08)
## [0.0.3](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-uniffi-russh-v0.0.2...${npm.name}-v0.0.3) (2025-10-08)
## 0.0.2 (2025-10-07)
## [0.0.1](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.1...@fressh/react-native-xtermjs-webview-v0.0.4) (2025-10-07)

View File

@@ -1,162 +0,0 @@
# Contributing
Contributions are always welcome, no matter how large or small!
We want this community to be friendly and respectful to each other. Please
follow it in all your interactions with the project. Before contributing, please
read the [code of conduct](./CODE_OF_CONDUCT.md).
## Development workflow
This project is a monorepo managed using
[Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the
following packages:
- The library package in the root directory.
- An example app in the `example/` directory.
To get started with the project, make sure you have the correct version of
[Node.js](https://nodejs.org/) installed. See the [`.nvmrc`](./.nvmrc) file for
the version used in this project.
Run `yarn` in the root directory to install the required dependencies for each
package:
```sh
yarn
```
> Since the project relies on Yarn workspaces, you cannot use
> [`npm`](https://github.com/npm/cli) for development without manually
> migrating.
The [example app](/example/) demonstrates usage of the library. You need to run
it to test any changes you make.
It is configured to use the local version of the library, so any changes you
make to the library's source code will be reflected in the example app. Changes
to the library's JavaScript code will be reflected in the example app without a
rebuild, but native code changes will require a rebuild of the example app.
If you want to use Android Studio or XCode to edit the native code, you can open
the `example/android` or `example/ios` directories respectively in those
editors. To edit the Objective-C or Swift files, open
`example/ios/UniffiRusshExample.xcworkspace` in XCode and find the source files
at `Pods > Development Pods > react-native-uniffi-russh`.
To edit the Java or Kotlin files, open `example/android` in Android studio and
find the source files at `react-native-uniffi-russh` under `Android`.
You can use various commands from the root directory to work with the project.
To start the packager:
```sh
yarn example start
```
To run the example app on Android:
```sh
yarn example android
```
To run the example app on iOS:
```sh
yarn example ios
```
To confirm that the app is running with the new architecture, you can check the
Metro logs for a message like this:
```sh
Running "UniffiRusshExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
```
Note the `"fabric":true` and `"concurrentRoot":true` properties.
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typecheck
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
### Commit message convention
We follow the
[conventional commits specification](https://www.conventionalcommits.org/en) for
our commit messages:
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
- `feat`: new features, e.g. add new method to the module.
- `refactor`: code refactor, e.g. migrate from class components to hooks.
- `docs`: changes into documentation, e.g. add usage example for the module.
- `test`: adding or updating tests, e.g. add integration tests using detox.
- `chore`: tooling changes, e.g. change CI config.
Our pre-commit hooks verify that your commit message matches this format when
committing.
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/),
[TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking,
[ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting
and formatting the code, and [Jest](https://jestjs.io/) for testing.
Our pre-commit hooks verify that the linter and tests pass when committing.
### Publishing to npm
We use [release-it](https://github.com/release-it/release-it) to make it easier
to publish new versions. It handles common tasks like bumping version based on
semver, creating tags and releases etc.
To publish new versions, run the following:
```sh
yarn release
```
### Scripts
The `package.json` file contains various scripts for common tasks:
- `yarn`: setup project by installing dependencies.
- `yarn typecheck`: type-check files with TypeScript.
- `yarn lint`: lint files with ESLint.
- `yarn test`: run unit tests with Jest.
- `yarn example start`: start the Metro server for the example app.
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_
> series:
> [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Verify that linters and tests are passing.
- Review the documentation to make sure it looks good.
- Follow the pull request template when opening a pull request.
- For pull requests that change the API or implementation, discuss with
maintainers first by opening an issue.

View File

@@ -1,34 +1,57 @@
# react-native-uniffi-russh
## @fressh/react-native-uniffi-russh
Uniffi bindings for russh
React Native bindings (via UniFFI) for the Rust SSH library
[russh](https://github.com/Eugeny/russh).
## Installation
[![npm version](https://img.shields.io/npm/v/%40fressh%2Freact-native-uniffi-russh)](https://www.npmjs.com/package/@fressh/react-native-uniffi-russh)
```sh
npm install react-native-uniffi-russh
### Install
```bash
pnpm add @fressh/react-native-uniffi-russh
```
## Usage
Peer dependencies (you manage): `react`, `react-native`.
```js
import { multiply } from 'react-native-uniffi-russh';
### Usage
// ...
This package exposes a native Rust module for SSH transport. For a complete,
working integration, see the example app:
const result = multiply(3, 7);
- https://github.com/EthanShoeDev/fressh/tree/main/apps/mobile
### API overview
High-level API surface (see code for full types):
```ts
import { RnRussh } from '@fressh/react-native-uniffi-russh';
await RnRussh.uniffiInitAsync();
const conn = await RnRussh.connect({
host: 'example.com',
port: 22,
username: 'me',
security: { type: 'password', password: '...' },
onServerKey: async () => true,
});
const shell = await conn.startShell({ term: 'Xterm' });
shell.addListener(
(ev) => {
// handle TerminalChunk or DropNotice
},
{ cursor: { mode: 'live' } },
);
```
## Contributing
### Links
- [Development workflow](CONTRIBUTING.md#development-workflow)
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
- [Code of conduct](CODE_OF_CONDUCT.md)
## License
MIT
---
Made with
[create-react-native-library](https://github.com/callstack/react-native-builder-bob)
- Changelog:
[`CHANGELOG.md`](https://github.com/EthanShoeDev/fressh/blob/main/packages/react-native-uniffi-russh/CHANGELOG.md)
- Contributing:
[`CONTRIBUTING.md`](https://github.com/EthanShoeDev/fressh/blob/main/CONTRIBUTING.md)
- API source:
[`src/api.ts`](https://github.com/EthanShoeDev/fressh/blob/main/packages/react-native-uniffi-russh/src/api.ts)
- License: MIT

View File

@@ -1,9 +1,9 @@
{
"name": "@fressh/react-native-uniffi-russh",
"homepage": "https://github.com/EthanShoeDev/fressh",
"homepage": "https://github.com/EthanShoeDev/fressh#readme",
"license": "MIT",
"description": "Uniffi bindings for russh",
"version": "0.0.1",
"version": "0.0.5",
"type": "module",
"main": "./lib/module/api.js",
"types": "./lib/typescript/src/api.d.ts",
@@ -18,20 +18,16 @@
"lib",
"android",
"ios",
"src",
"cpp",
"rust",
"*.podspec",
"*.xcframework/**",
"react-native.config.js",
"LICENSE",
"!ios/build",
"!android/build",
"!android/gradle",
"!android/gradlew",
"!android/gradlew.bat",
"!android/local.properties",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__",
"!**/.*"
"*.md",
"*.config.*",
"tsconfig*.json",
"LICENSE"
],
"scripts": {
"fmt": "cross-env SORT_IMPORTS=true prettier --write .",
@@ -52,15 +48,23 @@
"release": "GITHUB_TOKEN=$(gh auth token) release-it",
"release:dry": "release-it --dry-run"
},
"keywords": [
"react-native",
"ios",
"android"
],
"repository": {
"type": "git",
"url": "git+https://github.com/EthanShoeDev/fressh.git"
},
"bugs": {
"url": "https://github.com/EthanShoeDev/fressh/issues"
},
"keywords": [
"react-native",
"ssh",
"russh",
"uniffi",
"rust",
"expo",
"android",
"ios"
],
"author": "EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> (https://github.com/EthanShoeDev)",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
@@ -88,7 +92,7 @@
"typescript": "~5.9.2"
},
"dependencies": {
"uniffi-bindgen-react-native": "github:EthanShoeDev/uniffi-bindgen-react-native#build-ts"
"uniffi-bindgen-react-native": "github:jhugman/uniffi-bindgen-react-native#main"
},
"peerDependencies": {
"react": "19.1.0",

View File

@@ -24,7 +24,7 @@
"dependsOn": ["build:bob"],
},
"typecheck": {
"dependsOn": ["build:native"],
"dependsOn": ["build:native", "build:bob"],
},
// Special tasks

View File

@@ -0,0 +1,20 @@
# Exclude build and workspace caches not meant for publication
.turbo/
# Node modules should never be packed (npm ignores it by default, but explicit is fine)
node_modules/
# OS cruft
**/.DS_Store
# Keep dist outputs and sources; do not exclude them here
# dist/
# dist-internal/
# src/
# Common ignores that shouldn't ship
coverage/
.vscode/
.idea/
*.log
*.local

View File

@@ -1,7 +1,6 @@
import { type Config } from 'release-it';
export default {
// Avoid double-publish from the built-in npm plugin
npm: {
publish: true,
publishArgs: ['--access', 'public'],
@@ -18,8 +17,6 @@ export default {
github: {
release: true,
releaseName: '${npm.name} v${version}',
// optional: attach build artifacts
// assets: ['dist/**']
},
plugins: {
@@ -32,7 +29,7 @@ export default {
hooks: {
'before:init': ['turbo run lint:check'],
'before:github:release': 'turbo run build',
'after:bump': 'turbo run build',
'after:release': 'echo "Published ${npm.name} v${version} to npm"',
},
} satisfies Config;

View File

@@ -1,5 +1,13 @@
# Changelog
## [0.0.8](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.7...${npm.name}-v0.0.8) (2025-10-08)
## [0.0.7](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.6...${npm.name}-v0.0.7) (2025-10-08)
## [0.0.6](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.5...${npm.name}-v0.0.6) (2025-10-07)
## [0.0.5](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.4...${npm.name}-v0.0.5) (2025-10-07)
## [0.0.4](https://github.com/EthanShoeDev/fressh/compare/@fressh/react-native-xtermjs-webview-v0.0.1...${npm.name}-v0.0.4) (2025-10-07)
## 0.0.1 (2025-10-07)

View File

@@ -1,77 +1,78 @@
# React + TypeScript + Vite
## @fressh/react-native-xtermjs-webview
This template provides a minimal setup to get React working in Vite with HMR and
some ESLint rules.
React Native WebView that embeds [xterm.js](https://xtermjs.org/) with sensible
defaults and a bridge for input and output.
Currently, two official plugins are available:
[![npm version](https://img.shields.io/npm/v/%40fressh%2Freact-native-xtermjs-webview)](https://www.npmjs.com/package/@fressh/react-native-xtermjs-webview)
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react)
uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc)
uses [SWC](https://swc.rs/) for Fast Refresh
### Install
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the
configuration to enable type-aware lint rules:
```js
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
```bash
pnpm add @fressh/react-native-xtermjs-webview react-native-webview
```
You can also install
[eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x)
and
[eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom)
for React-specific lint rules:
Peer dependencies: `react`, `react-native-webview`.
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';
### Usage
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
For a complete production example, see the mobile app:
https://github.com/EthanShoeDev/fressh/tree/main/apps/mobile
Basic usage:
```tsx
import React, { useRef } from 'react';
import type { XtermWebViewHandle } from '@fressh/react-native-xtermjs-webview';
import { XtermJsWebView } from '@fressh/react-native-xtermjs-webview';
export function Terminal() {
const termRef = useRef<XtermWebViewHandle | null>(null);
return (
<XtermJsWebView
ref={termRef}
onInitialized={() => {
const hello = new TextEncoder().encode('hello');
termRef.current?.write(hello);
}}
onData={(input) => {
console.log('user input:', input);
}}
/>
);
}
```
#### Props
- `webViewOptions`: subset of `react-native-webview` props (sane defaults
applied)
- `xtermOptions`: partial `@xterm/xterm` options (theme, font, scrollback, etc.)
- `onInitialized`: called when the terminal is ready
- `onData(str)`: emits user keystrokes
- `size`: `{ cols, rows }` to set terminal size
- `autoFit`: auto-fit after important changes (default: true)
#### Ref API
- `write(bytes)`, `writeMany([bytes...])`, `flush()`
- `clear()`, `focus()`, `fit()`, `resize({ cols, rows })`
### Publishing contents
This package intentionally publishes both `src/` and built `dist/` artifacts for
transparency and debugging.
### Links
- Changelog:
[`CHANGELOG.md`](https://github.com/EthanShoeDev/fressh/blob/main/packages/react-native-xtermjs-webview/CHANGELOG.md)
- Contributing:
[`CONTRIBUTING.md`](https://github.com/EthanShoeDev/fressh/blob/main/CONTRIBUTING.md)
- Example app:
[`apps/mobile`](https://github.com/EthanShoeDev/fressh/tree/main/apps/mobile)
and source usage:
[`apps/mobile/src/app/(tabs)/shell/detail.tsx`](<https://github.com/EthanShoeDev/fressh/blob/main/apps/mobile/src/app/(tabs)/shell/detail.tsx>)
- API source:
[`src/index.tsx`](https://github.com/EthanShoeDev/fressh/blob/main/packages/react-native-xtermjs-webview/src/index.tsx)
- License: MIT

View File

@@ -1,20 +1,39 @@
{
"name": "@fressh/react-native-xtermjs-webview",
"private": false,
"version": "0.0.4",
"version": "0.0.8",
"license": "MIT",
"type": "module",
"files": [
"src",
"dist",
"dist-internal",
"!node_modules",
"!.turbo",
"*"
"*.config.*",
"tsconfig*.json",
"*.md",
"*.html"
],
"exports": {
".": "./dist/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/EthanShoeDev/fressh.git"
},
"bugs": {
"url": "https://github.com/EthanShoeDev/fressh/issues"
},
"homepage": "https://github.com/EthanShoeDev/fressh#readme",
"keywords": [
"react-native",
"xterm",
"webview",
"terminal",
"ssh",
"expo",
"android",
"ios"
],
"scripts": {
"fmt": "cross-env SORT_IMPORTS=true prettier --write .",
"fmt:check": "cross-env SORT_IMPORTS=true prettier --check .",
@@ -33,7 +52,6 @@
},
"peerDependencies": {
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native-webview": "13.15.0"
},
"devDependencies": {

View File

@@ -1,69 +0,0 @@
diff --git a/crates/ubrn_cli/templates/jsi/android/cpp-adapter.cpp b/crates/ubrn_cli/templates/jsi/android/cpp-adapter.cpp
index d8d1c1a1c1269ecee9467fd0ef809dcb1e375923..a031428d1eaecec2df94e7148e9d45d15642158d 100644
--- a/crates/ubrn_cli/templates/jsi/android/cpp-adapter.cpp
+++ b/crates/ubrn_cli/templates/jsi/android/cpp-adapter.cpp
@@ -2,6 +2,7 @@
#include <jni.h>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvokerHolder.h>
+#include <fbjni/fbjni.h>
#include "{{ self.config.project.cpp_filename() }}.h"
{%- let package_name = self.config.project.android.package_name().replace(".", "_") %}
{%- let name = self.config.project.module_cpp() %}
@@ -29,35 +30,27 @@ JNIEXPORT jboolean JNICALL
jlong rtPtr,
jobject callInvokerHolderJavaObj
) {
- // https://github.com/realm/realm-js/blob/main/packages/realm/binding/android/src/main/cpp/io_realm_react_RealmReactModule.cpp#L122-L145
- // React Native uses the fbjni library for handling JNI, which has the concept of "hybrid objects",
- // which are Java objects containing a pointer to a C++ object. The CallInvokerHolder, which has the
- // invokeAsync method we want access to, is one such hybrid object.
- // Rather than reworking our code to use fbjni throughout, this code unpacks the C++ object from the Java
- // object `callInvokerHolderJavaObj` manually, based on reverse engineering the fbjni code.
-
- // 1. Get the Java object referred to by the mHybridData field of the Java holder object
- auto callInvokerHolderClass = env->GetObjectClass(callInvokerHolderJavaObj);
- auto hybridDataField = env->GetFieldID(callInvokerHolderClass, "mHybridData", "Lcom/facebook/jni/HybridData;");
- auto hybridDataObj = env->GetObjectField(callInvokerHolderJavaObj, hybridDataField);
-
- // 2. Get the destructor Java object referred to by the mDestructor field from the myHybridData Java object
- auto hybridDataClass = env->FindClass("com/facebook/jni/HybridData");
- auto destructorField =
- env->GetFieldID(hybridDataClass, "mDestructor", "Lcom/facebook/jni/HybridData$Destructor;");
- auto destructorObj = env->GetObjectField(hybridDataObj, destructorField);
-
- // 3. Get the mNativePointer field from the mDestructor Java object
- auto destructorClass = env->FindClass("com/facebook/jni/HybridData$Destructor");
- auto nativePointerField = env->GetFieldID(destructorClass, "mNativePointer", "J");
- auto nativePointerValue = env->GetLongField(destructorObj, nativePointerField);
-
- // 4. Cast the mNativePointer back to its C++ type
- auto nativePointer = reinterpret_cast<facebook::react::CallInvokerHolder*>(nativePointerValue);
- auto jsCallInvoker = nativePointer->getCallInvoker();
-
- auto runtime = reinterpret_cast<jsi::Runtime *>(rtPtr);
- return {{ ns }}::installRustCrate(*runtime, jsCallInvoker);
+ try {
+ if (callInvokerHolderJavaObj == nullptr) {
+ return false;
+ }
+
+ auto alias = facebook::jni::alias_ref<jobject>(callInvokerHolderJavaObj);
+ auto holder = facebook::jni::static_ref_cast<facebook::react::CallInvokerHolder::javaobject>(alias);
+ if (!holder) {
+ return false;
+ }
+
+ auto jsCallInvoker = holder->cthis()->getCallInvoker();
+ if (!jsCallInvoker) {
+ return false;
+ }
+
+ auto runtime = reinterpret_cast<jsi::Runtime *>(rtPtr);
+ return {{ ns }}::installRustCrate(*runtime, jsCallInvoker);
+ } catch (...) {
+ return false;
+ }
}
extern "C"

1085
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,3 @@ onlyBuiltDependencies:
- oxc-resolver
- sharp
- unrs-resolver
patchedDependencies:
uniffi-bindgen-react-native: patches/uniffi-bindgen-react-native.patch