Skip to content

Commit

Permalink
Merge pull request #54 from Jigsaw-Code/outline-connectivity-app
Browse files Browse the repository at this point in the history
connectivity test app (without android)
  • Loading branch information
daniellacosse authored Sep 14, 2023
2 parents 817a454 + 6623399 commit 278ec10
Show file tree
Hide file tree
Showing 72 changed files with 8,127 additions and 0 deletions.
17 changes: 17 additions & 0 deletions x/examples/outline-connectivity-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
output

# vscode
.vscode

# node
node_modules

# yarn
.yarn/cache
.yarn/sdks
.yarn/unplugged
.yarn/install-state.gz
.pnp.*

# apple
.DS_Store

Large diffs are not rendered by default.

Large diffs are not rendered by default.

874 changes: 874 additions & 0 deletions x/examples/outline-connectivity-app/.yarn/releases/yarn-3.6.1.cjs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions x/examples/outline-connectivity-app/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"

yarnPath: .yarn/releases/yarn-3.6.1.cjs
134 changes: 134 additions & 0 deletions x/examples/outline-connectivity-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Outline Connectivity App

## Overview

This is a simple cross-platform app to test connectivity to Outline servers, using the Outline SDK. It is built with [Wails](https://wails.app/) and [Capacitor](https://capacitorjs.com/).

### Architecture

The overarching goal of this application is to demonstrate how the Outline SDK enables you to write each line of business logic only once across all platforms.

We achieve this by first writing a [`shared_backend`](./shared_backend) package in Go - which contains the UDP/TCP connectivity test implemented with the Outline SDK - and a [`shared_frontend`](./shared_frontend/) GUI built with TypeScript and Lit which contains an HTML form for entering the required connectivity test parameters.

Each platform used - [Wails](https://wails.app/) for desktop and [Capacitor](https://capacitorjs.com/) for mobile - then has a thin wrapper around the shared code that handles the platform-specific details. The following diagram illustrates how the shared code is built and used across platforms:

```mermaid
graph LR
subgraph Shared
A["shared_backend"]
B["shared_frontend"]
end
subgraph Build
C["SharedBackend.xcframework"]
D["SharedBackend.aar"]
end
subgraph app_desktop
H["index.html"]
G["DesktopBackend.Request()"]
end
subgraph app_mobile
subgraph ios
I["MobileBackendPlugin.swift"]
end
subgraph android
J["MobileBackendPlugin.kt"]
end
K["index.html"]
L["MobileBackend.Request()"]
end
A -.-> |gomobile| C
A -.-> |gomobile| D
A --> G
B --> H
B --> K
C --> I
D --> J
I --> L
J --> L
L --> K
G --> H
style K fill:blue;
style K color:white;
style H fill:blue;
style H color:white;
```

For Mobile, we use `gomobile` to build the `shared_backend` package into a `xcframework` for iOS and an `aar` for Android. You can see this for yourself by running `yarn shared_backend build`. For Desktop, Wails simply refers to the `shared_backend` package directly.

Then we implement a small piece of middleware that enables the frontend to make requests to the backend via the given platform.

```ts
interface Backend {
Request<T, K>(resourceName: string, parameters: T): Promise<K>
}
```

In a `Request` call, the frontend passes a `resourceName` and `parameters` to the backend, and the backend returns a promise either containing the result or which throws an error. The `resource` is the name of a function in the `shared_backend` package, and the `parameters` are are passed to that function.

With this middleware implemented, we can now use the shared code in the frontend. For example, in the mobile app, we can use the shared code like so:

```ts
@customElement("app-main")
export class AppMain extends LitElement {
render() {
return html`<connectivity-test-page
.onSubmit=${
(parameters: SharedFrontend.ConnectivityTestRequest) =>
MobileBackend.Request<SharedFrontend.ConnectivityTestRequest, SharedFrontend.ConnectivityTestResponse>("ConnectivityTest", parameters)
} />`;
}
}
```

## Development

### Prerequisites

- [Node.js](https://nodejs.org/)
- [Yarn](https://yarnpkg.com/)
- [Go](https://golang.org/)
- [Wails](https://wails.app/)
- [Capacitor](https://capacitorjs.com/)
- [CocoaPods](https://cocoapods.org/)
- [Xcode](https://developer.apple.com/xcode/)
- [Android SDK](https://developer.android.com/studio)
- [Android NDK](https://developer.android.com/ndk)

### Setup

1. Clone this repo
1. `cd` into the repo
1. `yarn`

If at any point you run into issues during development, try `yarn reset`.

### Development Server

`yarn watch`

### Build

> TODO: how to generate credentials
`yarn build`

### Needed Improvements

1. **\[P1\]** android (in progress)
1. **\[P1\]** read browser language on load, centralize language list, and only localize once
1. **\[P1\]** documentation on how to generate mobile app build credentials
1. **\[P1\]** add individual test result errors to the test result output UI
1. **\[P2\]** use x/config to parse the access key and showcase the different transports (see: https://github.com/Jigsaw-Code/outline-sdk/blob/main/x/examples/outline-connectivity/main.go)
1. **\[P2\]** generalize request handler via generics/reflection
1. **\[P2\]** Create a logo for the app
1. **\[P2\]** Make backend request calls non-blocking
1. **\[P2\]** Introducing some kind of tracing into the test

### Current Issues

1. <span style="color:red">**\[P0\]** add server url to an ENV var somehow... pretty dumb capacitor...</span>
1. **\[P1\]** Results dialog isn't rendering as intended (likely because of the `{ all: initial }`)
1. **\[P2\]** `cap ___ run` breaks (have workaround and [issue filed](https://github.com/ionic-team/capacitor/issues/6791))
1. <span style="color:gray">**\[P3\]** spurious lit localize TS error</span>
60 changes: 60 additions & 0 deletions x/examples/outline-connectivity-app/app_desktop/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2023 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"encoding/json"
"errors"

"github.com/Jigsaw-Code/outline-sdk/x/examples/outline-connectivity-app/shared_backend"
)

// App struct
type App struct {
ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}

func (a *App) Request(resourceName string, parameters string) (shared_backend.Response, error) {
var response shared_backend.Response

request := shared_backend.Request{ResourceName: resourceName, Parameters: parameters}

rawRequest, requestSerializeError := json.Marshal(request)

if requestSerializeError != nil {
return response, errors.New("DesktopBackend.Request: failed to serialize request")
}

// TODO: make this non-blocking with goroutines/channels
responseParseError := json.Unmarshal(shared_backend.HandleRequest(rawRequest), &response)

if responseParseError != nil {
return response, errors.New("DesktopBackend.Request: failed to parse response")
}

return response, nil
}
47 changes: 47 additions & 0 deletions x/examples/outline-connectivity-app/app_desktop/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2023 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// backend
import * as DesktopBackend from "./generated/wailsjs/go/main/App";

async function requestBackend<T, K>(resourceName: string, parameters: T): Promise<K> {
const response = await DesktopBackend.Request(resourceName, JSON.stringify(parameters));

if (response.error) {
throw new Error(response.error);
}

return JSON.parse(response.body);
}

// frontend
import * as SharedFrontend from "shared_frontend";
import type { ConnectivityTestRequest, ConnectivityTestResponse } from "shared_frontend";
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";

SharedFrontend.registerAllElements();

// main
@customElement("app-main")
export class AppMain extends LitElement {
render() {
return html`<connectivity-test-page .onSubmit=${
(parameters: ConnectivityTestRequest) =>
requestBackend<ConnectivityTestRequest, ConnectivityTestResponse>(
"ConnectivityTest", parameters
)
} />`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {shared_backend} from '../models';

export function Request(arg1:string,arg2:string):Promise<shared_backend.Response>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

export function Request(arg1, arg2) {
return window['go']['main']['App']['Request'](arg1, arg2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export namespace shared_backend {

export class Response {
body: string;
error: string;

static createFrom(source: any = {}) {
return new Response(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.body = source["body"];
this.error = source["error"];
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
Loading

0 comments on commit 278ec10

Please sign in to comment.