Skip to content
Open
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
10 changes: 4 additions & 6 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ on:
workflow_dispatch:
push:
branches:
- "main"
- 'main'
pull_request:
branches:
- "main"
- 'main'
schedule:
- cron: "0 4 * * *"
- cron: '0 4 * * *'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}-${{ github.event_name }}
Expand All @@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
name: Run health checks
steps:
- name: "Fetch current Git commit history"
- name: 'Fetch current Git commit history'
uses: actions/checkout@v4
with:
fetch-depth: 2
Expand All @@ -40,7 +40,6 @@ jobs:
restore-keys: |
${{ runner.os }}-turbo-${{ github.workflow }}-
${{ runner.os }}-turbo-
save-always: true

- name: Setup tooling caches
uses: actions/cache@v4
Expand All @@ -53,7 +52,6 @@ jobs:
restore-keys: |
${{ runner.os }}-tooling-${{ github.workflow }}-
${{ runner.os }}-tooling-
save-always: true

- name: Setup PNPM
uses: pnpm/action-setup@v4
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ jobs:
restore-keys: |
${{ runner.os }}-turbo-${{ github.workflow }}-
${{ runner.os }}-turbo-
save-always: true

- name: Setup PNPM
uses: pnpm/action-setup@v4
Expand Down
Binary file added assets/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions configs/prettier/ignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ node_modules/
tsc-out/
.*cache
pnpm-lock.yaml
serve/
unpacked-extension/
bundle/
3 changes: 3 additions & 0 deletions declarations/src/constants/null-uuid.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { NullUuid } from '../types/null-uuid.type';

export const NULL_UUID: NullUuid = '00000000-0000-0000-0000-000000000000';
3 changes: 3 additions & 0 deletions declarations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ export * from './types/non-parameterizable-command.type';
export * from './types/parameterizable-command.type';
export * from './types/promise-rejector.type';
export * from './types/promise-resolver.type';
export * from './types/uuid.type';
export * from './constants/null-uuid.const';
export * from './types/null-uuid.type';
1 change: 1 addition & 0 deletions declarations/src/types/null-uuid.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type NullUuid = '00000000-0000-0000-0000-000000000000';
5 changes: 5 additions & 0 deletions declarations/src/types/uuid.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NullUuid } from './null-uuid.type';

export type Uuid =
| `${string}-${string}-${1 | 2 | 3 | 4 | 5}${string}-${8 | 9 | 'a' | 'b'}${string}-${string}`
| NullUuid;
1 change: 1 addition & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"consy": "workspace:*"
},
"devDependencies": {
"@tailwindcss/cli": "catalog:build-tools",
"tailwindcss": "catalog:build-tools",
"@consy/configs": "workspace:*",
"typescript": "catalog:codebase",
Expand Down
4 changes: 1 addition & 3 deletions examples/src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
10 changes: 7 additions & 3 deletions examples/start.script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const DEFAULT_CHARSET: 'utf8' = 'utf8';

const SERVE_PATH: string = resolve(__dirname, 'serve');

const TAILWIND_BIN_PATH: string = resolve(__dirname, 'node_modules', '.bin', 'tailwindcss');

const ENTRY_POINT_FILE_NAME: string = 'index';
const LAYOUT_ENTRY_POINT_FILE_NAME: string = `${ENTRY_POINT_FILE_NAME}.html`;
const GLOBAL_STYLES_ENTRY_POINT_FILE_NAME: string = `${ENTRY_POINT_FILE_NAME}.css`;
Expand All @@ -30,7 +32,7 @@ interface GenerateCssParams {
async function generateCss({ contentPaths, globalStylesOutput, globalStylesInput }: GenerateCssParams) {
return new Promise<void>((resolve: () => void, reject: (error: Error) => void) => {
exec(
`tailwindcss --input ${globalStylesInput} --output ${globalStylesOutput} --minify --no-autoprefixer --content ${contentPaths.join(',')}`
`${TAILWIND_BIN_PATH} --input ${globalStylesInput} --output ${globalStylesOutput} --minify --no-autoprefixer --content ${contentPaths.join(',')}`
).on('close', (code: number) => {
code === 0 ? resolve() : reject(new Error(`Tailwind CSS process exited with code ${code}`));
});
Expand Down Expand Up @@ -111,7 +113,9 @@ async function generateCss({ contentPaths, globalStylesOutput, globalStylesInput
entryNames: '[dir]/[name]'
});

const { host, port }: ServeResult = await buildContext.serve({ servedir: SERVE_PATH });
const { hosts, port }: ServeResult = await buildContext.serve({ servedir: SERVE_PATH });

console.log(`Serving at http://${host}:${port}/`);
hosts.forEach((host: string) => {
console.log(`Serving at http://${host}:${port}/`);
});
})();
4 changes: 3 additions & 1 deletion extension/build.script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { basename, join, resolve } from 'path';

const DEFAULT_CHARSET: 'utf8' = 'utf8';

const TAILWIND_BIN_PATH: string = resolve(__dirname, 'node_modules', '.bin', 'tailwindcss');

const RESULT_BUNDLE_PATH: string = resolve(__dirname, 'unpacked-extension');

const DIALOG_FILE_NAME: string = 'index';
Expand Down Expand Up @@ -75,7 +77,7 @@ interface GenerateCssParams {
async function generateCss({ contentPaths, globalStylesOutput, globalStylesInput }: GenerateCssParams) {
return new Promise<void>((resolve: () => void, reject: (error: Error) => void) => {
exec(
`tailwindcss --input ${globalStylesInput} --output ${globalStylesOutput} --minify --no-autoprefixer --content ${contentPaths.join(',')}`
`${TAILWIND_BIN_PATH} --input ${globalStylesInput} --output ${globalStylesOutput} --minify --no-autoprefixer --content ${contentPaths.join(',')}`
).on('close', (code: number) => {
code === 0 ? resolve() : reject(new Error(`Tailwind CSS process exited with code ${code}`));
});
Expand Down
1 change: 1 addition & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"react-dom": "catalog:codebase"
},
"devDependencies": {
"@tailwindcss/cli": "catalog:build-tools",
"@types/react": "catalog:codebase",
"@types/react-dom": "catalog:codebase",
"tailwindcss": "catalog:build-tools",
Expand Down
102 changes: 69 additions & 33 deletions extension/src/communication/message-bus.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,92 @@
import { NULL_UUID, Uuid } from '@consy/declarations';
import { getUuid, isUuid } from '@consy/utilities';
import { Message } from './message';
import { MessageBase } from './message-base';
import { MessageProviderListenerCallback } from './message-provider-listener-callback.type';
import { MessagingProviderBase } from './messaging-provider-base';

type SubscriptionCallback<T = unknown> = (payload: T) => void;
type MessageEventListener = (event: MessageEvent) => void;
type MessagingProviderConstructor = new () => MessagingProviderBase;

type SubscriptionCallback = (payload: Message) => void;

const MESSAGE_SOURCE_MARK: 'consy' = 'consy';
type SerializedMessagePrefix = `${typeof MESSAGE_SOURCE_MARK}/${Uuid}/`;
const SERIALIZED_MESSAGE_PREFIX_LENGTH: number = (
`${MESSAGE_SOURCE_MARK}/${NULL_UUID}/` satisfies SerializedMessagePrefix
).length;
type SerializedMessage = `${SerializedMessagePrefix}${string}`;

export class MessageBus {
#channel: BroadcastChannel = new BroadcastChannel('__consy-message-bus');
readonly #instanceId: Uuid = getUuid();
readonly #serializedMessagePrefix: SerializedMessagePrefix = `${MESSAGE_SOURCE_MARK}/${this.#instanceId}/`;

#eventListenerBySubscriptionCallback: Map<SubscriptionCallback<Message>, MessageEventListener> = new Map<
SubscriptionCallback<Message>,
MessageEventListener
>();
#eventListenerBySubscriptionCallback: Set<SubscriptionCallback> = new Set<SubscriptionCallback>();
readonly #provider: MessagingProviderBase;

readonly #messageProviderListenerCallback: MessageProviderListenerCallback = (rawData: unknown) => {
if (!MessageBus.#isSerializedMessage(rawData) || this.#isSerializedMessageFromThisInstance(rawData)) {
return;
}

const deserializedMessage: Message = this.#getDeserialized(rawData);

this.#eventListenerBySubscriptionCallback.forEach((subscriptionCallback: SubscriptionCallback) =>
subscriptionCallback(deserializedMessage)
);
};

constructor(providerConstructor: MessagingProviderConstructor) {
this.#provider = new providerConstructor();
this.#provider.listen(this.#messageProviderListenerCallback);
}

public publish(message: Message): void {
this.#channel.postMessage(message);
const serializedMessage: SerializedMessage = this.#getSerialized(message);
this.#provider.dispatch(serializedMessage);
}

public subscribe(callback: SubscriptionCallback<Message>): void {
public subscribe(callback: SubscriptionCallback): void {
if (this.#eventListenerBySubscriptionCallback.has(callback)) {
return;
}

const messageEventListener: MessageEventListener = (event: MessageEvent) => {
const messageData: unknown = event.data;
if (MessageBase.isMessageData(messageData)) {
callback(messageData);
return;
}
throw new Error('Invalid message data');
};
this.#channel.addEventListener('message', messageEventListener);
this.#eventListenerBySubscriptionCallback.set(callback, messageEventListener);
this.#eventListenerBySubscriptionCallback.add(callback);
}

public unsubscribe(callback: SubscriptionCallback<Message>): void {
const messageEventListener: MessageEventListener | undefined =
this.#eventListenerBySubscriptionCallback.get(callback);
if (messageEventListener === undefined) {
return;
public unsubscribe(callback: SubscriptionCallback): void {
this.#eventListenerBySubscriptionCallback.delete(callback);
}

static #isSerializedMessagePrefix(prefix: string): prefix is SerializedMessagePrefix {
if (prefix.length !== SERIALIZED_MESSAGE_PREFIX_LENGTH) {
return false;
}
const [sourceMark, instanceId]: string[] = prefix.split('/');
return sourceMark === MESSAGE_SOURCE_MARK && isUuid(instanceId);
}

static #isSerializedMessage(message: unknown): message is SerializedMessage {
if (typeof message !== 'string') {
return false;
}

this.#channel.removeEventListener('message', messageEventListener);
this.#eventListenerBySubscriptionCallback.delete(callback);
const messagePrefix: string = message.substring(0, SERIALIZED_MESSAGE_PREFIX_LENGTH);
return MessageBus.#isSerializedMessagePrefix(messagePrefix) && message.length > SERIALIZED_MESSAGE_PREFIX_LENGTH;
}

public close(): void {
this.#eventListenerBySubscriptionCallback.forEach(
(_listener: MessageEventListener, callback: SubscriptionCallback<Message>) => {
this.unsubscribe(callback);
}
);
#isSerializedMessageFromThisInstance(message: SerializedMessage): boolean {
return message.startsWith(this.#serializedMessagePrefix);
}

#getDeserialized(serializedMessage: SerializedMessage): Message {
const messageData: unknown = JSON.parse(serializedMessage.slice(this.#serializedMessagePrefix.length));
if (!MessageBase.isMessageData(messageData)) {
throw new Error('Invalid message data');
}
return messageData;
}

this.#channel.close();
#getSerialized(message: Message): SerializedMessage {
return `${MESSAGE_SOURCE_MARK}/${this.#instanceId}/${JSON.stringify(message)}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MessageProviderListenerCallback = (data: unknown) => void;
6 changes: 6 additions & 0 deletions extension/src/communication/messaging-provider-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { MessageProviderListenerCallback } from './message-provider-listener-callback.type';

export abstract class MessagingProviderBase {
public abstract dispatch(data: string): void;
public abstract listen(callback: MessageProviderListenerCallback): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MessageProviderListenerCallback } from '../message-provider-listener-callback.type';
import { MessagingProviderBase } from '../messaging-provider-base';

export class BrowserExtensionWorkerMessagingProvider extends MessagingProviderBase {
public dispatch(data: string): void {
throw new Error('Method not implemented.');
}
public listen(callback: MessageProviderListenerCallback): void {
throw new Error('Method not implemented.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MessageProviderListenerCallback } from '../message-provider-listener-callback.type';
import { MessagingProviderBase } from '../messaging-provider-base';

export class InjectedScriptWorkerMessagingProvider extends MessagingProviderBase {
public dispatch(data: string): void {
throw new Error('Method not implemented.');
}
public listen(callback: MessageProviderListenerCallback): void {
throw new Error('Method not implemented.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MessageProviderListenerCallback } from '../message-provider-listener-callback.type';
import { MessagingProviderBase } from '../messaging-provider-base';

export class ServiceWorkerMessagingProvider extends MessagingProviderBase {
public dispatch(data: string): void {
throw new Error('Method not implemented.');
}
public listen(callback: MessageProviderListenerCallback): void {
throw new Error('Method not implemented.');
}
}
33 changes: 21 additions & 12 deletions extension/src/dialog/RootContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,33 @@ export function RootContent(): ReactNode {
const isMounted: boolean = useMemo(() => state.mountedInstances.length !== 0, [state.mountedInstances]);

useEffect(() => {
cumulativeHandler.current.provide<MountedInstancesMessage>(
MountedInstancesMessage.type,
(mountedInstances: MountedInstance[]) => {
setState((previousState: RootContentState) => ({
...previousState,
mountedInstances
}));
}
);
const cumulativeHandlerInstance: CumulativeMessageHandler =
cumulativeHandler.current.provide<MountedInstancesMessage>(
MountedInstancesMessage.type,
(mountedInstances: MountedInstance[]) => {
setState((currentState: RootContentState) => {
return {
...currentState,
mountedInstances
};
});
}
);

chrome.runtime.onMessage.addListener((data: unknown): undefined => {
const messageListener: (data: unknown) => undefined = (data: unknown): undefined => {
if (!MessageBase.isMessageData(data)) {
return;
}
cumulativeHandler.current.handle(data);
});

cumulativeHandlerInstance.handle(data);
};
chrome.runtime.onMessage.addListener(messageListener);

chrome.runtime.sendMessage(new UpdateRequiredMessage());

return () => {
chrome.runtime.onMessage.removeListener(messageListener);
};
}, []);

if (!isMounted) {
Expand Down
4 changes: 1 addition & 3 deletions extension/src/dialog/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
2 changes: 1 addition & 1 deletion extension/src/dialog/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/>
</head>

<body>
<body class="min-w-32">
<script src="index.ts"></script>
</body>
</html>
Loading
Loading