Skip to content

Commit

Permalink
Merge pull request #3 from refrens/broadcast-channel-fixes
Browse files Browse the repository at this point in the history
Broadcast channel fixes
  • Loading branch information
Cypherball authored Sep 11, 2024
2 parents 4441ba1 + 1860c17 commit 5a1e4fa
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 38 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@


## [2.0.4-broadcast-channel.9](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.8...2.0.4-broadcast-channel.9) (2024-09-11)

## [2.0.4-broadcast-channel.8](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.7...2.0.4-broadcast-channel.8) (2024-09-11)

## [2.0.4-broadcast-channel.7](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.6...2.0.4-broadcast-channel.7) (2024-09-11)

## [2.0.4-broadcast-channel.6](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.5...2.0.4-broadcast-channel.6) (2024-09-11)

## [2.0.4-broadcast-channel.5](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.4...2.0.4-broadcast-channel.5) (2024-09-11)

## [2.0.4-broadcast-channel.4](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.3...2.0.4-broadcast-channel.4) (2024-09-10)

## [2.0.4-broadcast-channel.3](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.2...2.0.4-broadcast-channel.3) (2024-09-10)

## [2.0.4-broadcast-channel.2](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.1...2.0.4-broadcast-channel.2) (2024-09-10)

## [2.0.4-broadcast-channel.1](https://github.com/refrens/react-use-oauth2/compare/2.0.4-broadcast-channel.0...2.0.4-broadcast-channel.1) (2024-09-10)

## [2.0.4-broadcast-channel.0](https://github.com/refrens/react-use-oauth2/compare/2.0.3...2.0.4-broadcast-channel.0) (2024-09-10)

## [2.0.3](https://github.com/refrens/react-use-oauth2/compare/2.0.2-release-test.0...2.0.3) (2024-09-09)

## 2.0.2-release-test.0 (2024-09-09)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@refrens/react-use-oauth2",
"version": "2.0.3",
"version": "2.0.4-broadcast-channel.9",
"author": {
"name": "Tasos Kakouris",
"email": "[email protected]",
Expand Down
42 changes: 28 additions & 14 deletions src/components/OAuthPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect } from 'react';
import { OAUTH_RESPONSE } from './constants';
import { useCallback, useEffect, useRef } from 'react';
import { OAUTH_RESPONSE, OAUTH_RESPONSE_ACK } from './constants';
import {
channelPostMessage,
checkState,
isBroadcastChannel,
openerPostMessage,
queryToObject,
removeState,
} from './tools';
import { TMessageData } from './types';

type Props = {
Component?: React.ReactElement;
Expand All @@ -21,9 +22,19 @@ export const OAuthPopup = ({
</div>
),
}: Props) => {
const channel = new BroadcastChannel('oauth_channel');
const handleListener = useCallback((message: MessageEvent<TMessageData>) => {
const type = message?.data?.type;
if (type !== OAUTH_RESPONSE_ACK) {
return;
}
window.close();
removeState(sessionStorage);
}, []);

const channel = useRef(new BroadcastChannel('refrens_oauth_channel'));

useEffect(() => {
if (didInit) return;
if (didInit) return () => {};
didInit = true;

const payload = {
Expand All @@ -32,13 +43,12 @@ export const OAuthPopup = ({
};
const state = payload?.state;
const error = payload?.error;
const opener = window?.opener;

if (isBroadcastChannel(channel)) {
if (isBroadcastChannel(channel.current)) {
const stateOk = state && checkState(sessionStorage, state);

if (!error && stateOk) {
channelPostMessage(channel, { type: OAUTH_RESPONSE, payload });
channelPostMessage(channel.current, { type: OAUTH_RESPONSE, payload });
} else {
const errorMessage = error
? decodeURI(error)
Expand All @@ -47,16 +57,20 @@ export const OAuthPopup = ({
? 'OAuth error: An error has occured.'
: 'OAuth error: State mismatch.'
}`;

openerPostMessage(opener, {
type: OAUTH_RESPONSE,
error: errorMessage,
});
channelPostMessage(channel.current, { type: OAUTH_RESPONSE, error: errorMessage });
}
} else {
throw new Error('No BroadcastChannel support');
}
}, []);

// eslint-disable-next-line unicorn/prefer-add-event-listener
channel.current.addEventListener('message', handleListener);

return () => {
channel.current.close();
channel.current.removeEventListener('message', handleListener);
};
}, [handleListener, channel]);

return Component;
};
1 change: 1 addition & 0 deletions src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const OAUTH_STATE_KEY = 'react-use-oauth2-state-key';
export const OAUTH_RESPONSE = 'react-use-oauth2-response';
export const EXCHANGE_CODE_FOR_TOKEN_METHODS = ['GET', 'POST', 'PUT', 'PATCH'] as const;
export const DEFAULT_EXCHANGE_CODE_FOR_TOKEN_METHOD = 'POST';
export const OAUTH_RESPONSE_ACK = 'react-use-oauth2-response-ack';
12 changes: 12 additions & 0 deletions src/components/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ export const cleanup = (
window.removeEventListener('message', handleMessageListener);
};

export const cleanupChannel = (
intervalRef: React.MutableRefObject<string | number | NodeJS.Timeout | undefined>,
popupRef: React.MutableRefObject<Window | null | undefined>,
channel: BroadcastChannel,
handleMessageListener: any
) => {
clearInterval(intervalRef.current);
removeState(sessionStorage);
channel.close();
channel.removeEventListener('message', handleMessageListener);
};

export const formatExchangeCodeForTokenServerURL = (
serverUrl: string,
clientId: string,
Expand Down
6 changes: 5 additions & 1 deletion src/components/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OAUTH_RESPONSE, EXCHANGE_CODE_FOR_TOKEN_METHODS } from './constants';
import { OAUTH_RESPONSE, EXCHANGE_CODE_FOR_TOKEN_METHODS, OAUTH_RESPONSE_ACK } from './constants';

export type TAuthTokenPayload = {
token_type: string;
Expand Down Expand Up @@ -52,6 +52,10 @@ export type TMessageData =
| {
type: typeof OAUTH_RESPONSE;
payload: any;
}
| {
type: typeof OAUTH_RESPONSE_ACK;
payload: any;
};

type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
Expand Down
40 changes: 20 additions & 20 deletions src/components/use-oauth2.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useCallback, useRef, useState } from 'react';
import useLocalStorageState from 'use-local-storage-state';
import { DEFAULT_EXCHANGE_CODE_FOR_TOKEN_METHOD, OAUTH_RESPONSE } from './constants';
import {
cleanup,
DEFAULT_EXCHANGE_CODE_FOR_TOKEN_METHOD,
OAUTH_RESPONSE,
OAUTH_RESPONSE_ACK,
} from './constants';
import {
channelPostMessage,
cleanupChannel,
formatAuthorizeUrl,
formatExchangeCodeForTokenServerURL,
generateState,
Expand Down Expand Up @@ -45,8 +50,9 @@ export const useOAuth2 = <TData = TAuthTokenPayload>(props: TOauth2Props<TData>)
}
);

const channel = useRef(new BroadcastChannel('refrens_oauth_channel'));

const getAuth = useCallback(() => {
const channel = new BroadcastChannel('oauth_channel');
// 1. Init
setUI({
loading: true,
Expand Down Expand Up @@ -137,30 +143,23 @@ export const useOAuth2 = <TData = TAuthTokenPayload>(props: TOauth2Props<TData>)
if (onError) await onError(genericError.toString());
} finally {
// Clear stuff ...
cleanup(intervalRef, popupRef, handleBroadcastChannelMessage);
channelPostMessage(channel.current, { type: OAUTH_RESPONSE_ACK, payload: 'ack' });
cleanupChannel(
intervalRef,
popupRef,
channel.current,
handleBroadcastChannelMessage
);
}
}
// eslint-disable-next-line unicorn/prefer-add-event-listener
channel.onmessage = handleBroadcastChannelMessage;

// 4. Begin interval to check if popup was closed forcefully by the user
intervalRef.current = setInterval(() => {
const popupClosed = !popupRef.current?.window || popupRef.current?.window?.closed;
if (popupClosed) {
// Popup was closed before completing auth...
setUI((ui) => ({
...ui,
loading: false,
}));
console.warn('Warning: Popup was closed before completing authentication.');
cleanup(intervalRef, popupRef, handleBroadcastChannelMessage);
}
}, 250);
channel.current.addEventListener('message', handleBroadcastChannelMessage);

// 5. Remove listener(s) on unmount
return () => {
// eslint-disable-next-line unicorn/prefer-add-event-listener
channel.close();
channel.current.close();
channel.current.removeEventListener('message', handleBroadcastChannelMessage);
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [
Expand All @@ -173,6 +172,7 @@ export const useOAuth2 = <TData = TAuthTokenPayload>(props: TOauth2Props<TData>)
onError,
setUI,
setData,
channel,
]);

const logout = useCallback(() => {
Expand Down

0 comments on commit 5a1e4fa

Please sign in to comment.