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

Add logic back to try to reconnect to the app websocket if socket is closed #279

Merged
merged 15 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ target
node_modules
/lib
.tsbuildinfo
.hc
.hc*
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## \[Unreleased\]

### Added
- Bring back a websocket reconnection automation for Admin and App websockets. When either of them is closed and a new request made, it will attempt to reconnect using the same app authentication token that was used to initially authenticate the websocket. A specific `InvalidTokenError` is returned if that fails.
### Fixed
### Changed
### Removed
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ You need `holochain` and `hc` on your path, best to get them from nix with `nix-

To perform the pre-requisite DNA compilation steps, and run the Nodejs test, run:
```bash
nix-shell
./run-test.sh
nix develop
./build-fixture.sh
npm run test
```

## Contribute
Expand Down
2 changes: 1 addition & 1 deletion docs/client.wsclient.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Description

</td><td>

[WsClientOptions](./client.wsclientoptions.md)
[WsClientOptions](./client.wsclientoptions.md) \| undefined


</td><td>
Expand Down
2 changes: 1 addition & 1 deletion docs/client.wsclient.options.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
**Signature:**

```typescript
options: WsClientOptions;
options: WsClientOptions | undefined;
```
53 changes: 19 additions & 34 deletions src/api/app/websocket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import Emittery, { UnsubscribeFunction } from "emittery";
import { omit } from "lodash-es";
import { AgentPubKey, CellId, InstalledAppId, RoleName } from "../../types.js";
import { AppInfo, CellType, MemproofMap } from "../admin/index.js";
import {
AppAuthenticationToken,
AppInfo,
CellType,
MemproofMap,
} from "../admin/index.js";
import {
catchError,
DEFAULT_TIMEOUT,
Expand Down Expand Up @@ -77,6 +82,7 @@ export class AppWebsocket implements AppClient {
CallZomeResponseGeneric<Uint8Array>,
CallZomeResponse
>;
private readonly appAuthenticationToken: AppAuthenticationToken;
Copy link
Member

Choose a reason for hiding this comment

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

I don't believe this is very high risk, but I don't feel comfortable not pointing this out. Storing this token may expose it to an injected script. If it is exfiltrated then it is re-usable elsewhere.

I much prefer the single-use tokens but I understand that this is being done for UX.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is, and it's similar to the zome call signing credentials. They're equally stored in JavaScript memory for convenience, which compromises security to a degree.


cachedAppInfo?: AppInfo | null;

Expand Down Expand Up @@ -110,6 +116,7 @@ export class AppWebsocket implements AppClient {
private constructor(
client: WsClient,
appInfo: AppInfo,
token: AppAuthenticationToken,
callZomeTransform?: CallZomeTransform,
defaultTimeout?: number
) {
Expand All @@ -118,6 +125,7 @@ export class AppWebsocket implements AppClient {
this.installedAppId = appInfo.installed_app_id;
this.defaultTimeout = defaultTimeout ?? DEFAULT_TIMEOUT;
this.callZomeTransform = callZomeTransform ?? defaultCallZomeTransform;
this.appAuthenticationToken = token;
this.emitter = new Emittery<AppEvents>();
this.cachedAppInfo = appInfo;

Expand Down Expand Up @@ -204,18 +212,15 @@ export class AppWebsocket implements AppClient {

const client = await WsClient.connect(options.url, options.wsClientOptions);

if (env?.APP_INTERFACE_TOKEN) {
// Note: This will only work for multiple connections if a single_use = false token is provided
await client.authenticate({ token: env.APP_INTERFACE_TOKEN });
} else {
if (!options.token) {
throw new HolochainError(
"AppAuthenticationTokenMissing",
`unable to connect to Conductor API - no app authentication token provided.`
);
}
await client.authenticate({ token: options.token });
}
const token = options.token ?? env?.APP_INTERFACE_TOKEN;

if (!token)
throw new HolochainError(
"AppAuthenticationTokenMissing",
`unable to connect to Conductor API - no app authentication token provided.`
);

await client.authenticate({ token });

const appInfo = await (
AppWebsocket.requester(client, "app_info", DEFAULT_TIMEOUT) as Requester<
Expand All @@ -233,6 +238,7 @@ export class AppWebsocket implements AppClient {
return new AppWebsocket(
client,
appInfo,
token,
options.callZomeTransform,
options.defaultTimeout
);
Expand Down Expand Up @@ -443,27 +449,6 @@ export class AppWebsocket implements AppClient {
transformer
);
}

private containsCell(cellId: CellId) {
const appInfo = this.cachedAppInfo;
if (!appInfo) {
return false;
}
for (const roleName of Object.keys(appInfo.cell_info)) {
for (const cellInfo of appInfo.cell_info[roleName]) {
const currentCellId =
CellType.Provisioned in cellInfo
? cellInfo[CellType.Provisioned].cell_id
: CellType.Cloned in cellInfo
? cellInfo[CellType.Cloned].cell_id
: undefined;
if (currentCellId && isSameCell(currentCellId, cellId)) {
return true;
}
}
}
return false;
}
}

const defaultCallZomeTransform: Transformer<
Expand Down
Loading
Loading