Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
df76b99
use local xxdk wasm and set localstorage wrapper for chrome extension…
thisisommore Feb 2, 2025
2b7fda6
add .gitpod.yml
thisisommore Feb 2, 2025
7a79e90
setup code workspace and fix gitpod confiog
thisisommore Feb 2, 2025
573ddf9
fix gitpod config - run npm link as sudo
thisisommore Feb 2, 2025
b638668
update dev config
thisisommore May 29, 2025
36bff9c
Update Gitpod configuration to change repository URLs and add new rep…
thisisommore May 29, 2025
f68146d
Revert "Update Gitpod configuration to change repository URLs and add…
thisisommore May 29, 2025
c066743
Merge remote-tracking branch 'upstream/main' into 1-mvp---for-experiment
thisisommore May 29, 2025
35e9072
fix stuck [1/x]
thisisommore Jun 25, 2025
122363f
Refactor WebAssemblyRunner to use havenStorageMemory instead of haven…
thisisommore Jun 28, 2025
99f722f
use back remote kv and listeners and use promise wrapper to fix block…
thisisommore Jul 4, 2025
3cd14b8
Refactor loadCmix function to remove unnecessary timeout and streamli…
thisisommore Jul 4, 2025
a70ed67
remove unnessary cmix init check
thisisommore Jul 4, 2025
656e1b5
remove unnessary await for getShareURL and getPrivacyLevel
thisisommore Jul 4, 2025
e0af1bf
remove unused storage objects
thisisommore Jul 4, 2025
8eca256
remove unused tick debugging
thisisommore Jul 4, 2025
fedb8d5
revert Window interface to main
thisisommore Jul 4, 2025
726258a
useNotification: remove unnessary new line
thisisommore Jul 4, 2025
bddea9e
useChannel hooks: remove unused useState import
thisisommore Jul 4, 2025
5151dd9
WebAssemblyRunner: remove unused console.log
thisisommore Jul 4, 2025
61b93e3
haven storage: use strict types
thisisommore Jul 4, 2025
871d0ee
Rename `extSPromise.ts` to `haven-storage.ts`
thisisommore Jul 4, 2025
159d1cb
Update .gitpod.yml and gitpod.Dockerfile to specify exact versions fo…
thisisommore Jul 6, 2025
45d186c
Add initialization tasks for xxdk-wasm, client, and ekv in .gitpod.yml
thisisommore Jul 6, 2025
6083c35
Update gitpod.Dockerfile to install specific versions of wasmbrowsert…
thisisommore Jul 6, 2025
c73d8b7
Remove installation of specific versions of wasmbrowsertest and clean…
thisisommore Jul 6, 2025
087957b
gitpod docker - install wasmbrowsertest in first stage
thisisommore Jul 6, 2025
f8e5b46
remove duplicate xxdk-wasm git clone
thisisommore Jul 6, 2025
00bf499
Update gitpod.Dockerfile to change ownership of Go module cache for g…
thisisommore Jul 6, 2025
fa9c176
Refactor .gitpod.yml and gitpod.Dockerfile to streamline setup tasks …
thisisommore Jul 6, 2025
5068f47
Add GOPRIVATE environment variable to gitpod.Dockerfile for private G…
thisisommore Jul 6, 2025
8bb62de
gitpod: open terminal in clone directory
thisisommore Jul 6, 2025
4b80536
Update gitpod.Dockerfile to change ownership of Go directory and add …
thisisommore Jul 6, 2025
380b872
Add PATH export for Go binaries in .gitpod.yml before setup tasks
thisisommore Jul 6, 2025
cd26c1f
Create SETUP.md
thisisommore Jul 22, 2025
6b001f8
Update SETUP.md
thisisommore Jul 22, 2025
e1a48cb
Create setup.sh
thisisommore Jul 22, 2025
797f0cd
setup.sh: add checks for required files
thisisommore Jul 22, 2025
c7d3e68
use localstorage if there are existing keys or if externsion is not a…
thisisommore Aug 4, 2025
212dcc3
remove gitpod files
thisisommore Aug 4, 2025
51ac16f
remove global.gitconfig
thisisommore Aug 4, 2025
d439c9f
remove setup.sh
thisisommore Aug 4, 2025
c5d5d4a
remove bun.lock
thisisommore Aug 4, 2025
b1e6350
remove SETUP.md
thisisommore Aug 4, 2025
15a1c46
haven storage extension: ad comment for hello request
thisisommore Aug 4, 2025
8b576e3
revert vite.config.ts to main
thisisommore Aug 4, 2025
b487df0
network-client-context: remove unnessary logging
thisisommore Aug 4, 2025
212597d
remove main.code-workspace
thisisommore Aug 4, 2025
3448785
update extension id
thisisommore Aug 5, 2025
246fb43
WebAssemblyRunner: revert unnecessary changes
thisisommore Aug 5, 2025
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@juggle/resize-observer": "^3.4.0",
"@react-spring/web": "^9.5.5",
"@reduxjs/toolkit": "^1.9.1",
"@types/chrome": "^0.0.326",
"@vitejs/plugin-react": "^4.3.4",
"@windmillcode/quill-emoji": "^2.0.3000",
"body-scroll-lock": "^4.0.0-beta.0",
Expand Down
29 changes: 25 additions & 4 deletions src/components/common/WebAssemblyRunner/WebAssemblyRunner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { FC, useEffect } from 'react';
import { InitXXDK, setXXDKBasePath } from 'xxdk-wasm';

import { useUtils } from 'src/contexts/utils-context';
import { havenStorageExtension } from './haven-storage-extension';
import { havenStorageLocal } from './local-storage';
import { HavenStorage } from './type';

type Logger = {
StopLogging: () => void;
Expand All @@ -23,6 +26,7 @@ declare global {
GetLogger: () => Logger;
logger?: Logger;
getCrashedLogFile: () => Promise<string>;
havenStorage: HavenStorage;
}
}

Expand All @@ -41,15 +45,32 @@ const WebAssemblyRunner: FC<WithChildren> = ({ children }) => {
// symlinking your public directory:
// cd public && ln -s ../node_modules/xxdk-wasm xxdk-wasm && cd ..
// Then override with this function here:
//setXXDKBasePath(window!.location.href + 'xxdk-wasm');
// setXXDKBasePath(window!.location.href + 'xxdk-wasm');

// NOTE: NextJS hackery, since they can't seem to provide a helper to get a proper origin...
setXXDKBasePath(basePath);

InitXXDK().then(async (result: any) => {
setUtils(result);
const initXXdk = async () => {
if (localStorage.getItem('🞮🞮speakeasyapp') === null) {
const isAvailable = await havenStorageExtension.init();
if (isAvailable) {
console.log('[HavenStorage] Using extension storage since extension is available');
window.havenStorage = havenStorageExtension;
} else {
console.log('[HavenStorage] Using localStorage since extension is not available');
window.havenStorage = havenStorageLocal;
}
} else {
console.log('[HavenStorage] Using localStorage due to existing keys');
window.havenStorage = havenStorageLocal;
}

const xxdkUtils = await InitXXDK();
setUtils(xxdkUtils);
setUtilsLoaded(true);
});
};

initXXdk();
}
}, [basePath, setUtils, setUtilsLoaded, utilsLoaded]);
return <>{children}</>;
Expand Down
180 changes: 180 additions & 0 deletions src/components/common/WebAssemblyRunner/haven-storage-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { HavenStorage } from './type';

// type from Extension
type BaseMessage<Api extends string> = {
api: Api;
requestId: string;
};

export type TRequest = BaseMessage<'LocalStorage:Request'> &
(
| {
action: 'clear' | 'keys';
}
| {
action: 'getItem' | 'removeItem';
key: string;
}
| {
action: 'setItem';
key: string;
value: string;
}
);

export type TResponse = BaseMessage<'LocalStorage:Response'> &
(
| {
action: 'getItem';
result: unknown;
}
| {
action: 'keys';
result: string[];
}
| {
action: 'removeItem' | 'clear' | 'setItem';
}
);

function generateRequestId(): string {
return Math.random().toString(36).slice(2, 11);
}

const promiseHandlers: Record<
string,
{ resolve: (result: any) => void; reject: (error: any) => void }
> = {};

// establish a long-lived Port to extension
const EXT_ID = 'ihilcflljgogpbkopcmkoaoildehaehc';
let port: chrome.runtime.Port;

function isValidResponse(msg: any): msg is TResponse {
return msg?.requestId && msg.api === 'LocalStorage:Response';
}

let status: 'disconnected' | 'not-installed' | 'ok' = 'disconnected';
function setupPort(): Promise<boolean> {
return new Promise((resolve, reject) => {
port = chrome.runtime.connect(EXT_ID, { name: 'LocalStorageChannel' });

let settled = false;
// incoming responses on that port
port.onMessage.addListener((msg) => {
// we want to settle only once
if (!settled) {
settled = true;
resolve(true);
}

if (msg?.requestId && msg.api === 'LocalStorage:Response') {
const handler = promiseHandlers[msg.requestId];

if (handler && isValidResponse(msg)) {
handler.resolve('result' in msg ? msg.result : undefined);
delete promiseHandlers[msg.requestId];
}
}
});

port.onDisconnect.addListener(() => {
if (chrome.runtime.lastError) {
if (
chrome.runtime.lastError.message ===
'Could not establish connection. Receiving end does not exist.'
) {
status = 'not-installed';
resolve(false);
} else {
status = 'disconnected';
reject(chrome.runtime.lastError);
}
}
});

// send message to check if extension is responding to request
port.postMessage({
api: 'LocalStorage:Request',
action: 'getItem',
key: 'hello',
requestId: 'init'
} satisfies TRequest);
});
}

function sendViaPort<T>(action: 'clear' | 'keys'): Promise<T>;
function sendViaPort<T>(action: 'getItem' | 'removeItem', key: string): Promise<T>;
function sendViaPort<T>(action: 'setItem', key: string, value: any): Promise<T>;
function sendViaPort<T>(
action: 'getItem' | 'setItem' | 'removeItem' | 'clear' | 'keys',
key?: string,
value?: any
): Promise<T> {
const requestId = generateRequestId();

return new Promise<T>(async (resolve, reject) => {
promiseHandlers[requestId] = { resolve, reject };
let request: TRequest;

if (action === 'clear' || action === 'keys') {
// no key/value
request = { api: 'LocalStorage:Request', action, requestId };
} else if (action === 'getItem' || action === 'removeItem') {
// key is required here
request = { api: 'LocalStorage:Request', action, key: key!, requestId };
} else if (action === 'setItem') {
// setItem: both key and value are required
request = { api: 'LocalStorage:Request', action, key: key!, value: value!, requestId };
} else {
throw new Error(`Unknown action: ${action}`);
}

// ports get disconnected after inactivity
if (status === 'disconnected') {
await setupPort();
}

try {
port.postMessage(request);
} catch (err) {
console.error('postMessage failed, reconnecting port', err);
setupPort();
// retry once after reconnect
try {
port.postMessage({ api: 'LocalStorage:Request', action, key, value, requestId });
} catch (retryErr) {
delete promiseHandlers[requestId];
reject(retryErr);
}
}
});
}

export const havenStorageExtension = {
getItem(key) {
return sendViaPort('getItem', key);
},
setItem(key, value) {
return sendViaPort('setItem', key, value);
},
delete(key) {
return sendViaPort('removeItem', key);
},
clear() {
return sendViaPort('clear');
},
keys() {
return sendViaPort('keys');
},
key(_idx) {
// not implemented on SW side
return Promise.reject('not implemented');
},
isAvailable() {
return true;
},
init() {
return setupPort();
}
} satisfies HavenStorage;
43 changes: 43 additions & 0 deletions src/components/common/WebAssemblyRunner/local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { HavenStorage } from './type';

export const havenStorageLocal: HavenStorage = {
async getItem(key) {
return localStorage.getItem(key);
},

async setItem(key, value) {
localStorage.setItem(key, value);
},

async delete(key) {
localStorage.removeItem(key);
},

async clear() {
localStorage.clear();
},

async keys() {
const keys: string[] = [];

for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key !== null) {
keys.push(key);
}
}

return keys;
},

key(index) {
return new Promise((resolve, reject) => {
try {
const key = localStorage.key(index);
resolve(key);
} catch (err) {
reject(err);
}
});
}
};
12 changes: 12 additions & 0 deletions src/components/common/WebAssemblyRunner/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type HavenStorage = {
getItem: (key: string) => Promise<string | null>;
setItem: (key: string, value: any) => Promise<void>;
delete: (key: string) => Promise<void>;
clear: () => Promise<void>;
keys: () => Promise<string[]>;
key: (index: number) => Promise<string | null>;
isAvailable?: () => boolean;

// boolean indicating if the storage is available
init?: () => Promise<boolean>;
};
15 changes: 9 additions & 6 deletions src/contexts/dm-client-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ export const DMContextProvider: FC<WithChildren> = ({ children }) => {
useEffect(() => {
if (client) {
try {
dispatch(dms.actions.setUserNickname(client.GetNickname()));
client.GetNickname().then((nickname) => {
dispatch(dms.actions.setUserNickname(nickname));
});
} catch (e) {
// no nickname found
}
Expand All @@ -149,8 +151,8 @@ export const DMContextProvider: FC<WithChildren> = ({ children }) => {
}, [client, dmsDatabaseName, setDmsDatabaseName]);

const createDatabaseCipher = useCallback(
(cmix: CMix, decryptedPassword: Uint8Array) => {
const cipher = utils.NewDatabaseCipher(
async (cmix: CMix, decryptedPassword: Uint8Array) => {
const cipher = await utils.NewDatabaseCipher(
cmix.GetID(),
decryptedPassword,
MAXIMUM_PAYLOAD_BLOCK_SIZE
Expand All @@ -177,7 +179,7 @@ export const DMContextProvider: FC<WithChildren> = ({ children }) => {
try {
const workerPath = (await dmIndexedDbWorkerPath()).toString();
//console.log('DMWORKERPATH: ' + workerPath);
const notifications = utils.LoadNotificationsDummy(cmix.GetID());
const notifications = await utils.LoadNotificationsDummy(cmix.GetID());
NewDMClientWithIndexedDb(
cmix.GetID(),
notifications.GetID(),
Expand Down Expand Up @@ -206,8 +208,9 @@ export const DMContextProvider: FC<WithChildren> = ({ children }) => {
rawPassword as string,
channelManager.ExportPrivateIdentity(rawPassword as string)
);
const cipher = createDatabaseCipher(cmix as CMix, decryptedPassword as Uint8Array);
createDMClient(cmix as CMix, cipher, privateIdentity);
createDatabaseCipher(cmix as CMix, decryptedPassword as Uint8Array).then((cipher) => {
createDMClient(cmix as CMix, cipher, privateIdentity);
});
}
}, [
channelManager,
Expand Down
Loading