Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added public/img/appdata.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources/views/docs/1/digging-deeper/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: Digging Deeper
order: 3
---
41 changes: 41 additions & 0 deletions resources/views/docs/1/digging-deeper/broadcasting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: Broadcasting
order: 100
---

# Broadcasting

NativePHP fires various events during its operations. You may listen for these events using any event listener in your
application as you normally would.

NativePHP also broadcasts these events over a websocket connection to the `nativephp` broadcast channel. This allows
you to listen for these events in real-time using Laravel Echo and react to these events on your application's
front-end.

A full list of all events fired and broadcast by NativePHP can be found in the
[src/Events/](https://github.com/nativephp/laravel/tree/main/src/Events) folder.

## Broadcasting custom events

You can also broadcast your own custom events. Simply instruct your event to implement the `ShouldBroadcast` contract
and define the `broadcastsOn` method in your event, returning `nativephp` as one of the channels it broadcasts to:

```php
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class JobFinished implements ShouldBroadcastNow
{
public function broadcastOn(): array
{
return [
new Channel('nativephp'),
];
}
}
```

This is great for times when you want to offload an intensive task to a background queue and await its completion
without constantly polling your application for its status.

Your fired event will be broadcast and your application can listen for it and just as your normally would.
59 changes: 59 additions & 0 deletions resources/views/docs/1/digging-deeper/databases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Databases
order: 200
---

# Working with Databases

Almost every application needs a database, especially if your app is working with complex user data or communicating
with an API. A database is an efficient and reliable way to persist structured data across multiple versions of
your application.

When building a _server-side_ application, you are free to choose the database engine you prefer. But in the context of
a self-contained native application, your choices are limited to:
- what you can reasonably bundle with your app; or
- what you can expect the user's system to have installed.

**To keep the footprint of your application small, NativePHP currently only supports SQLite out of the box.**

You can interact with SQLite via PDO or an ORM, such as Eloquent, in exactly the way you're used to.

## SQLite

[SQLite](https://sqlite.org/) is a feature-rich, portable, lightweight, file-based database. It's perfect for native
applications that need persistent storage of complex data structures with the speed and tooling of SQL.

Its small footprint and minimal dependencies make it ideal for cross-platform, native applications. Your users
don't need to install anything else besides your app, and it doesn't add hundreds of MBs to your bundle,
keeping download & install size small.

### Configuration

You do not need to do anything special to configure your application to use SQLite. NativePHP will automatically:
- switch to using SQLite when building your application,
- create a database file for you in the `storage` directory on the user's system,
- configure your application to use that database file, and
- run your migrations each time your app starts.

## Migrations

When writing migrations, you need to consider any special recommendations for working with SQLite. For example,
SQLite disables foreign key constraints by default. If your application relies upon foreign key constraints,
[you need to enable SQLite support for them](https://laravel.com/docs/10.x/database#configuration) before
writing your migrations.

**It's important to test your migrations before releasing updates!** You don't want to accidentally delete your user's
data when they update your app.

## When not to use a database

If you're only storing small amounts of very simple metadata or working files, you may not need a database at all.
Consider storing files instead. These could be JSON, CSV, plain text or any other format that makes sense for
your application.

Consider also using file storage for very critical metadata about the state of your application on a user's device.
If you rely on the same database you store the user's data to store this information, if the database becomes
corrupted for any reason, your application may not be able to start at all.

If you store this information in a file, you can at least instruct your users to delete the file and restart the
application lowering the risk of deleting their data.
84 changes: 84 additions & 0 deletions resources/views/docs/1/digging-deeper/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Files
order: 300
---

# Files & Paths

Working with files in NativePHP is just like working with files in a regular Laravel application. To achieve this,
NativePHP rewrites the `Application::$storagePath()` (and thus `app()->storagePath()` and the `storage_path()` helper)
to the [Electron `app.getPath('appData')` path](https://www.electronjs.org/docs/latest/api/app#appgetpathname),
which is different for each operating system.

This means that you can continue to use Laravel's `Storage` facade to store and retrieve files on your user's file
system just as you would on your server.

If you use the default Storage configuration for the `local` filesystem, your `local` disk will also point to this
`appdata` directory, followed by `storage/app`.

![](/img/appdata.png)

Here you may see some folders you recognise, namely `database` and `storage`. The other folders are managed by Electron.
The `storage` folder is exactly the same `storage` directory you are used to seeing in your Laravel application. It
stores various caches and also application logs.

You should use this `Application::storagePath()` when storing files on your user's computer that need to remain
available even when your application is updated or removed from the system, e.g. your application's configuration,
settings and any user data that the user doesn't need direct access to.

It's also the location where your SQLite database will be stored.

## Storing files elsewhere

NativePHP doesn't interfere with any of your _existing_ filesystem configuration, so you may continue to configure
[Filesystems](https://laravel.com/docs/filesystems) as you normally would, however you should be aware that it does
_add_ some new default filesystems for your convenience.

Consider that your users want to store their files in locations other than the obscure `appdata` directories on their
preferred OS. To that end, NativePHP provides a variety of convenient `filesystems` which are configured at runtime to
point to the respective, platform-specific directories for the current user.

[warning]
If your application also defines any of these filesystems, NativePHP will override their configuration with its own.
[/warning]

You can use these filesystem simply using the `Storage` facade like this:

```php
Storage::disk('user.home')->get('file.txt');
Storage::disk('user.desktop')->get('file.txt');
Storage::disk('user.documents')->get('file.txt');
Storage::disk('user.downloads')->get('file.txt');
Storage::disk('user.music')->get('file.txt');
Storage::disk('user.pictures')->get('file.txt');
Storage::disk('user.videos')->get('file.txt');
Storage::disk('user.recent')->get('file.txt');
```

Note that the PHP process which runs your application operates with the same privileges as the logged-in user, this
means your application is able to read and write files wherever your user is authorised to.

Generally, you should only read and write files to the user's `home` directory or your app's `appdata` directory. Be
aware that some operating systems now actively prompt the user to grant permissions to apps when they first attempt to
access directories in the user's home directory.

See [Security](/docs/digging-deeper/security) for more considerations.

[aside]
You can also continue to use cloud storage providers if you wish.

However, be mindful that an application installed on a user's device is even more likely to experience network
disruption than one operating on a server in the cloud, as your users may be without an internet connection at any
time.

You should prepare more carefully for such scenarios when interacting with any APIs that require network connectivity
by checking for a connection _before_ making a request and/or handling exceptions gracefully should a request fail.

This will help maintain a smooth user experience
[/aside]

NativePHP uses the `local` disk by default. If you would like to use a different disk, you may configure this in your
`config/filesystems.php` file.

Remember, you can set the filesystem disk your application uses by default in your `config/filesystems.php` file or by
adding a `FILESYSTEM_DISK` variable to your `.env` file.
155 changes: 155 additions & 0 deletions resources/views/docs/1/digging-deeper/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Security
order: 400
---
# Security

When building desktop applications it's essential to take your application's security to the next level, both to
protect your application and infrastructure, but also to protect your users, their system and their data. This is a
complex and wide-reaching topic. Please take time to thoroughly understand everything discussed in this chapter.

Remember that we can't cover everything here either, so please use good judgement when implementing features of your
application that allows users to manipulate data on their filesystem or other sources.

## Protecting your application and infrastructure

A major consideration for NativePHP is how it can protect _your_ application.

### Secrets and .env
As your application is being installed on systems outside of your/your organisation's control, it is important to think
of the environment that it's in as _potentially_ hostile, which is to say that any secrets, passwords or keys are
could fall into the hands of someone who might try to abuse them.

This means you should, where possible, use unique keys for each installation, preferring to generate these at first-run
or on every run rather than sharing the same key for every user across many installations.

Especially if your application is communicating with any private APIs over the network, we highly recommend that your
application and any API use a robust and secure authentication protocol, such as OAuth2, that enables you to create and
distribute unique and expiring tokens (an expiration date less than 48 hours in the future is recommended) with a high
level of entropy, as this makes them hard to guess and hard to abuse.

[hypothetical]
When your application runs for the first time on a user's device, NativePHP generates a new `APP_KEY`. This means that
when your application uses encryption features, by default each user's encryption key will be different. This means
that an attacker won't be able to acquire an `APP_KEY` from one user to decrypt content encrypted by another user.

This also presents a challenge for you if you wish to centralise or backup any user data by sending it to the cloud.
If your wider infrastructure relies on having access to unencrypted data, make sure you are clear with your users about
what data you are collecting and how it is used.

Depending on the laws applicable where you live or intend to distribute your software, you may have to write detailed
privacy policies which your users must agree to before they can use your application. In some cases, you may not be
allowed to use encryption features.

NativePHP leaves it up to you to determine your obligations in this regard. If you are unsure, please seek appropriate
legal advice.
[/hypothetical]

If your application allows users to connect _their own_ API keys for a service, you should treat these keys with great
care. If you choose to store them anywhere (either in a [File](/docs/digging-deeper/files) or
[Database](/docs/digging-deeper/databases)), make sure you store them encrypted and decrypt them only when in use.

### Files and privileges

Your application runs in a privileged state thanks to the PHP runtime being executed as the user who is currently
operating the system. This is convenient, but it also comes with risks. Your application has access to everything that
the user is authorized to access on the system.

You should limit where you are reading and writing files to the locations your user expects. These are the `appdata`
folder for the combination of your application and this user and the user's `home` directory (and the other user
subdirectories).

All of these can be done simply by using the provided Storage filesystems detailed in
[Files](/docs/digging-deeper/files).

### The web servers

NativePHP works by spinning up web servers on each side of the runtime environment: one on the PHP side to execute your
application and another on the Electron side, to interact with Electron's native environment hooks for the operating
system. It then bridges the gap between the two by making _authenticated and encrypted_ HTTP calls between the two
using a pre-shared, dynamic key that is regenerated every time your application starts.

This prevents any third-party software from snooping/sniffing the connection and 'tinkering' with either your
application or the Electron environment. This means that your application's front-end will only be accessible through
your Electron application shell and the Electron APIs will only respond to your application.

**You MUST NOT bypass this security measure!** If you do, your application will be open to attack from very basic HTTP
calls, which it is trivial for any installed application to make, or even for your user to be coerced into making via a
web browser (e.g. from a phishing attack).

By default, Laravel's built-in CSRF and CORS protections will go some way to preventing many of these kinds of attacks
but you should do all you can to prevent unwanted attack vectors from being made available.

## Protecting your users and their data

Equally important is how your app protects users. NativePHP is a complex combination of powerful software and so there
are a number of risks associated with its use.

### When sending data over the network

**Always use HTTPS to communicate with web services.** This ensures that any data sent between your user's device and
the service is encrypted in transit.

### The PHP executable

Currently, the bundled PHP executable can be used by any user or application that knows where to find it and has
privileges to execute binaries in that location.

This is a potential attack vector that your users ought to be aware of when they are installing other applications. If
a user installs an application that they don't trust, it may attempt to use the PHP binary bundled with your application
to execute arbitrary code on your user's device. This is known as a Remote Code Execution attack (or RCE).

While this may not directly affect your application (unless it's the target of such an attack), you can still help users
to secure their device by reminding them of their responsibility to only install trusted software from reputable
vendors.

There's very little that can be done to mitigate this kind of attack in practice, just the same as any application you
install now on your device could use any other application installed.

### Interpreted code

[hypothetical]

As your application is just a well-organized bundle of plain-text PHP scripts which is not compiled to machine-code
until runtime, it is trivial for anyone to change the execution of your application by diving into one of these files
and altering the code.

The approach we've taken to mitigate this is to verify that the code is as expected using a signature of all the files.
This signature is computed every time your application is booted, but by default it is done in the background so it
doesn't noticeably slow down your application's boot sequence, which could result in a poor user experience.

Once complete, this signature is then compared to the signature that Electron expects to see for your application, which
is computed and stored in your application bundle at build time. If the signatures match, the user will not notice
anything, however if they do not match, Electron will interrupt the user with a warning to indicate that the application
has been tampered with and may not be safe to use.

This warning can be dismissed and the user may continue, but it will re-appear again when they quit your app and
re-open it.

If your application is not particularly large, you may choose to have this signature calculation run to completion
_before_ your application is booted to prevent any alterations from executing as your app boots up. This is, of course,
safer for your user, but initially slower each time your app boots up. However, you could use a splash screen and a
progress bar to give your user some visual feedback while this is happening.

In future versions of NativePHP, we hope to be able to offer a more robust solution to this problem.

#### How it works

When you run `php artisan native:build`, we perform the following hashing function:

We take an `md5` hash of each static file in your application - not just PHP files! - and this includes all of your
Composer dependencies too. We then compute an `md5` hash of each of these hashes.

We store the final computed hash as a hardcoded value inside the Electron bundle. This is a signature or checksum of
your entire codebase which can be verified at runtime when a user opens your application.

We run the exact same hashing mechanic on each run of your application in a background thread. Once complete, the
newly-computed hash is compared to the hardcoded value.

Be aware that any changes you make to the source code included in a production build _after_ the build has been created
will cause the hashes to differ and thus trigger the tamper warning for your users. If there is some intentionally
dynamic aspect to your code, such as personalised/customisable builds, you will need to exclude the relevant files from
the hashing mechanic.

You can do this by adding the relevant paths to the `tamper_proofing.exclude_files` array in your `config/nativephp.php`
config file.
5 changes: 5 additions & 0 deletions resources/views/docs/1/digging-deeper/updater.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Updating Your App
order: 500
---
# The Updater
Loading