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

Remix example #443

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c7804c6
feat(remix): add initial files for example
IgnisDa Dec 8, 2022
e5384fb
ci(remix): add deployment configs
IgnisDa Dec 8, 2022
936d88b
feat(remix): add basic login example
IgnisDa Dec 8, 2022
ee4dc4b
feat(remix-eg): add redirect after auth
IgnisDa Dec 8, 2022
931a872
feat(remix-example): add basic todo page
IgnisDa Dec 8, 2022
a26de9e
docs(remix-eg): add comment explaining func
IgnisDa Dec 8, 2022
2fb22cb
feat(remix-eg): allow deleting and updating todos
IgnisDa Dec 8, 2022
e3c9d2a
refactor(remix-eg): use remix-utils methods
IgnisDa Dec 8, 2022
7d82abc
style(remix-eg): remove useless braces
IgnisDa Dec 8, 2022
fab4452
fix(remix-eg): set correct key for todo list
IgnisDa Dec 8, 2022
64d7ddd
ci: run built app in docker container
IgnisDa Dec 17, 2022
d162580
fix(example/remix): use correct env vars
IgnisDa Dec 17, 2022
0bbe644
Merge branch 'main' of https://github.com/teamhanko/hanko into remix
IgnisDa Dec 17, 2022
e89ae54
Merge branch 'main' into remix
Dec 23, 2022
559c595
build: update remix versions
IgnisDa Jan 26, 2023
413e7b9
style(remix-example): fix eslint error
IgnisDa Jan 26, 2023
73cea86
Merge branch 'main' into remix
IgnisDa Jan 26, 2023
6e4d8bb
build(remix-example): update elements version
IgnisDa Jan 26, 2023
8ba200b
Merge branch 'main' into remix
like-a-bause Feb 1, 2023
7c1561f
Merge branch 'main' into remix
IgnisDa Feb 4, 2023
d086400
docs(examples): add correct instructions for running server.
IgnisDa Feb 5, 2023
b552ce5
Workaround for facebook/react/issues/24430
cayblood Feb 10, 2023
b54f399
Merge branch 'main' into remix
IgnisDa Feb 10, 2023
aae5471
Workaround for React 18 hydration issues
cayblood Feb 11, 2023
c3efbbd
Merge branch 'IgnisDa:remix' into remix
cayblood Feb 11, 2023
279de09
build: revert packages to a version that works
IgnisDa Feb 14, 2023
f4e7054
build(examples/remix): revert frontend sdk to latest version
IgnisDa Feb 15, 2023
dd26cae
Merge branch 'main' into remix
IgnisDa Feb 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"cSpell.words": [
"hanko",
"teamhanko",
"Todos"
]
}
19 changes: 19 additions & 0 deletions deploy/docker-compose/todo-remix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
todo-backend:
build: ../../examples/express
environment:
- HANKO_API_URL=http://hanko:8000
networks:
- intranet

todo-frontend:
build: ../../examples/remix
environment:
- PORT=8888
- REMIX_APP_HANKO_API=http://localhost:8000
- REMIX_APP_TODO_API=http://todo-backend:8002
ports:
- "8888:8888"
- "8008:8008"
networks:
- intranet
40 changes: 31 additions & 9 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,66 @@

This directory contains examples that show

- integration of web component(s) provided through the`@teamhanko/hanko-elements` package (see [elements](../frontend/elements)).
- how to validate JSON Web Tokens (JWT) issued by the Hanko [API](../backend) in a custom backend
- integration of web component(s) provided through the`@teamhanko/hanko-elements` package
(see [elements](../frontend/elements)).
- how to validate JSON Web Tokens (JWT) issued by the Hanko [API](../backend) in a custom
backend

It contains:

- an example [express](express) backend - this is a simple version of the well-known todo app
- an example [express](express) backend - this is a simple version of the well-known todo
app
- example frontend applications using the following frameworks:
- [Angular](angular)
- [Next.js](nextjs)
- [React](react)
- [Vue](vue)
- [Svelte](svelte)
- [Remix](remix)

## How to run

### Manual
1. Start the Hanko API (see the instructions on how to run the API [in Docker](../backend/README.md#Docker) or [from Source](../backend/README.md#from-source))

1. Start the Hanko API (see the instructions on how to run the API [in
Docker](../backend/README.md#Docker) or [from Source](../backend/README.md#from-source))
2. Start the express backend (see the [README](express) for the express backend)
3. Start one of the frontend applications (see the README for the app of your choice)

### Docker Compose

#### React
```

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-react.yaml -p "hanko-todo-react" up --build
```

#### Angular
```

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-angular.yaml -p "hanko-todo-angular" up --build
```

#### Next.js
```

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-nextjs.yaml -p "hanko-todo-nextjs" up --build
```

#### Vue
```

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-vue.yaml -p "hanko-todo-vue" up --build
```

#### Svelte
```

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-svelte.yaml -p "hanko-todo-svelte" up --build
```

#### Remix

```bash
docker compose -f deploy/docker-compose/base.yaml -f deploy/docker-compose/todo-remix.yaml -p "hanko-todo-remix" up --build
```
2 changes: 2 additions & 0 deletions examples/remix/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
REMIX_APP_HANKO_API=http://localhost:8000
REMIX_APP_TODO_API=http://localhost:8002
4 changes: 4 additions & 0 deletions examples/remix/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};
5 changes: 5 additions & 0 deletions examples/remix/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules

/.cache
/build
/public/build
21 changes: 21 additions & 0 deletions examples/remix/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# pull official base image
FROM node:16-alpine

# set working directory
WORKDIR /app

# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH

# install app dependencies
COPY package.json ./
COPY package-lock.json ./
RUN npm install

# add app
COPY . ./

RUN npm run build

# start app
CMD ["npm", "run", "start"]
27 changes: 27 additions & 0 deletions examples/remix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Hanko Remix example

This is a [Remix](https://remix.run) project bootstrapped with [Create Remix
App](https://www.npmjs.com/package/create-remix).

## Starting the app

### Prerequisites

- a running Hanko API (see the instructions on how to run the API [in
Docker](../backend/README.md#Docker) or [from Source](../backend/README.md#from-source))
- a running express backend (see the [README](../express) for the express backend)

### Set up environment variables

In the `.env` file set up the correct environment variables:

- `REMIX_APP_HANKO_API`: this is the URL of the Hanko API (default:
`http://localhost:8000`, can be customized using the `server.public.address` option in
the [configuration file](../../backend/docs/Config.md))
- `REMIX_APP_TODO_API`: this is the URL of the [express](../express) backend (default:
`http://localhost:8002`)

### Run development server

The entire process is automated with docker. You can find the instructions in the
[examples/README.md](../README.md#remix) file.
Binary file added examples/remix/app/assets/bg.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions examples/remix/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

function hydrate() {
startTransition(() => {
hydrateRoot(
document.getElementById('root')!,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
}

if (window.requestIdleCallback) {
window.requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
window.setTimeout(hydrate, 1);
}
118 changes: 118 additions & 0 deletions examples/remix/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { PassThrough } from "stream";
import type { EntryContext } from "@remix-run/node";
import { Response } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import isbot from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import { renderHeadToString } from 'remix-island';
import { Head } from './root';

const ABORT_DELAY = 5000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return isbot(request.headers.get("user-agent"))
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
}

function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let didError = false;

const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onAllReady() {
const head = renderHeadToString({request, remixContext, Head});
const body = new PassThrough();

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);
body.write(`<!DOCTYPE html><html><head>${head}</head><body><div id="root">`);
pipe(body);
body.write(`</div></body></html>`);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
didError = true;

console.error(error);
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}

function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let didError = false;

const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const head = renderHeadToString({ request, remixContext, Head });
const body = new PassThrough();

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
})
);

body.write(`<!DOCTYPE html><html><head>${head}</head><body><div id="root">`);
pipe(body);
body.write(`</div></body></html>`);
},
onShellError(err: unknown) {
reject(err);
},
onError(error: unknown) {
didError = true;

console.error(error);
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}
20 changes: 20 additions & 0 deletions examples/remix/app/lib/auth.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { redirect } from '@remix-run/server-runtime';
import { parse } from 'cookie';
import type { JwtPayload } from 'jsonwebtoken';
import { decode } from 'jsonwebtoken';

export const extractHankoCookie = (request: Request) => {
const cookies = parse(request.headers.get('Cookie') || '');
return cookies.hanko;
};

// ensures the user has a hanko cookie but does not check if it is valid
export async function requireValidJwt(request: Request) {
const hankoCookie = extractHankoCookie(request);
const decoded = decode(hankoCookie) as JwtPayload;
const hankoId = decoded?.sub;
const exp = (decoded?.exp || 0) * 1000;
if (!hankoId || exp < Date.now())
throw redirect(`/`);
return decoded
}
6 changes: 6 additions & 0 deletions examples/remix/app/lib/hanko.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// `.client.ts` files are only supposed to run on the client. I could not find this
// documented on the remix docs but someone on the remix discord told me this is how it is
// supposed to work.
import { register } from '@teamhanko/hanko-elements';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this, the remix build command currently results in: "ERROR: Could not resolve "@teamhanko/hanko-elements"

Reverting to @teamhanko/hanko-elements@0.0.17-alpha requires the "old" import path for the register function, i.e. import { register } from '@teamhanko/hanko-elements/hanko-auth'.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IgnisDa I just submitted a PR for this fix plus another compatibility issue I found with the ClientOnly component.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IgnisDa @lfleischmann replied below before my fix was merged into this PR. I think their concerns would go away and we could finally get this merged with that fix. That said, we should probably update to 0.5.0, now that it has been released.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added you as a collaborator in my repository in case you need to make any changes.


export { register as registerHankoAuth };