Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

connectivity test app (without android) #54

Merged
merged 23 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f710178
add initial test app
daniellacosse Aug 23, 2023
a8dcdc4
Merge branch 'main' into outline-connectivity-app
daniellacosse Aug 30, 2023
83f9359
Update x/outline-connectivity-app/go.mod
daniellacosse Aug 30, 2023
55285bf
move folder, run go mod tidy
daniellacosse Aug 30, 2023
5886b41
Update x/examples/outline-connectivity-app/shared_frontend/index.ts
daniellacosse Aug 31, 2023
7872ea7
Update x/examples/outline-connectivity-app/package.json
daniellacosse Aug 31, 2023
fe0c58b
adds licenses and updates go.mod path
daniellacosse Sep 6, 2023
42b861e
address all feedback but the refactoring ones
daniellacosse Sep 6, 2023
be74d8c
remove pnp files
daniellacosse Sep 6, 2023
f1f4630
revert generated/wailsjs - wails generate module dosen't workj
daniellacosse Sep 6, 2023
d72e369
Update x/examples/outline-connectivity-app/shared_frontend/pages/conn…
daniellacosse Sep 7, 2023
c896bf1
wip
daniellacosse Sep 7, 2023
27f5f9b
renaming to request
daniellacosse Sep 11, 2023
ff679bd
Merge branch 'outline-connectivity-app' of https://github.com/Jigsaw-…
daniellacosse Sep 11, 2023
198433f
fix types and update docs
daniellacosse Sep 11, 2023
c1227bf
add todo
daniellacosse Sep 11, 2023
6984d5e
move ConnectivityTest check inside if statement
daniellacosse Sep 12, 2023
cd51fbb
update todos in README
daniellacosse Sep 12, 2023
7de8aec
fix diagram and add TODO for generic parser
daniellacosse Sep 13, 2023
4d43597
okay THAT should work
daniellacosse Sep 13, 2023
630c1b3
fix ipv6 resolver, and language switcher
daniellacosse Sep 13, 2023
cccf239
fix translation switcher
daniellacosse Sep 13, 2023
6623399
add TODO for test result output
daniellacosse Sep 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved
G --> H
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved

style K fill:blue;
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved
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
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved

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
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved
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
daniellacosse marked this conversation as resolved.
Show resolved Hide resolved

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 <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
Loading