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

Implement SyncStorageService using broadcast-channel #1197

Closed
wants to merge 35 commits into from

Conversation

denysoblohin-okta
Copy link
Contributor

@denysoblohin-okta denysoblohin-okta commented Apr 25, 2022

  • Reimplement SyncStorageService (service to sync authState cross multiple tabs) with using broadcast-channel package instead of using StorageEvent
  • Added LeaderElectionService (leader election code was a part of ServiceManager)

Internal ref: OKTA-476153

Implementation notes:

Notes on broadcast-channel issues:

  1. Error Failed to execute 'postMessage' on 'BroadcastChannel': Channel is closed .
    See Auth JS 6.4.0 causes issues in Angular apps when testing #1174, Error on redirect from login okta-react#221, "Failed to execute 'postMessage' on 'BroadcastChannel'" error - React 18 okta-react#232
    Cause: broadcast-channel's leader election tries to post internal message after closing the channel.
    Workaround:
    (this.channel as any).postInternal = () => Promise.resolve();
  2. Detection of Node environment in broadcast-channel is naive:
    https://github.com/pubkey/broadcast-channel/blob/773fbc3477d246e96f7566b7ac5ff3164042204d/src/util.js#L61
    This leads to wrongly selecting 'node' method (based on fs sockets) for broadcast-channel during unit tests.
    Causes tests flakiness.
    For downstream SDKs causes error No useable method found in ["native","idb","localstorage"], see Please provide unit test example okta-react#224
    Workaround:
    // broadcast-channel should not detect node environment
    // https://github.com/pubkey/broadcast-channel/blob/master/src/util.js#L61
    process[Symbol.toStringTag] = 'Process';

Notes on IE11

  1. Test app was not working on IE11. Need to add polyfills:
    https://github.com/okta/okta-auth-js/pull/1197/files#diff-a861a360629a870e92aba3e9458e109f96ad1c9512f8ff339b0a4072f7ce7d4c
    (maybe replace multiple features with 'core-js/stable' ?)
    And polyfill FormData: https://github.com/okta/okta-auth-js/pull/1197/files#diff-41fbe6af6c0cd49ffacdf69a8831d1a7327ea9d90849bd4bf95c27e6f6033ffcR210
  2. IE11 has known issue with synchronization of localStorage across tabs.
    Partial workaround is to set empty event handler for window.onstorage: https://github.com/okta/okta-auth-js/pull/1197/files#diff-4190d70999a6d0023de331545bbec8ba4167f9d6d71faa196061946d5f8822d9R129
    But it's not 100% working, sometimes you still get old value from LocalStorage.
    So to fix this issue I've added a new type of event EVENT_SET_STORAGE in TokenManager (only for IE11) to update storage explicitly in other tabs.
    https://github.com/okta/okta-auth-js/pull/1197/files#diff-f18cf5684cf053b7ffbc2723f4983f1a26432ef9eade50cbfa00a74a7793dedbR280
    https://github.com/okta/okta-auth-js/pull/1197/files#diff-b3d0b7dbba2f28b35e943fe28c40cc6b832b43b2986d5d14dd1ce9b7d164f3deR134

Other notes

  1. SyncStorageService works with localStorage and cookie storage.
    https://github.com/okta/okta-auth-js/pull/1197/files#diff-6d3aacb7a946c6ca690509a0cc75e9880e5ca6942803b7cdd4059a9ba9a31b2dR356
    https://github.com/okta/okta-auth-js/pull/1197/files#diff-2128a2510aeabd863495fcf012a579ef877ad61544805295fff687cfce0728c4R55

@denysoblohin-okta denysoblohin-okta force-pushed the od-sync-service-broadcast-OKTA-476153 branch from 2bcc941 to 6fa57a4 Compare April 25, 2022 12:49
@codecov-commenter
Copy link

codecov-commenter commented Apr 26, 2022

Codecov Report

Merging #1197 (d943b27) into master (cceda24) will increase coverage by 0.06%.
The diff coverage is 95.33%.

@@            Coverage Diff             @@
##           master    #1197      +/-   ##
==========================================
+ Coverage   93.41%   93.48%   +0.06%     
==========================================
  Files         157      159       +2     
  Lines        4586     4681      +95     
  Branches     1058     1081      +23     
==========================================
+ Hits         4284     4376      +92     
- Misses        283      287       +4     
+ Partials       19       18       -1     
Impacted Files Coverage Δ
lib/browser/browserStorage.ts 92.80% <66.66%> (-1.18%) ⬇️
lib/services/LeaderElectionService.ts 87.50% <87.50%> (ø)
lib/services/SyncStorageService.ts 97.33% <96.61%> (+0.55%) ⬆️
lib/AuthStateManager.ts 94.00% <100.00%> (ø)
lib/OktaAuth.ts 89.45% <100.00%> (+0.06%) ⬆️
lib/SavedObject.ts 90.69% <100.00%> (+0.69%) ⬆️
lib/ServiceManager.ts 95.45% <100.00%> (+3.04%) ⬆️
lib/TokenManager.ts 95.75% <100.00%> (+0.89%) ⬆️
lib/services/AutoRenewService.ts 100.00% <100.00%> (ø)
lib/services/index.ts 100.00% <100.00%> (ø)
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cceda24...d943b27. Read the comment docs.

@denysoblohin-okta denysoblohin-okta marked this pull request as ready for review May 4, 2022 12:51
onLeader?: OnLeaderHandler;
};

export class LeaderElectionService implements ServiceInterface {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the motivation for moving the tab elector logic to it's own service?

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 isolated election-related code from ServiceManager into a separate class for single responsibility, then saw it looks like a service.

@denysoblohin-okta denysoblohin-okta force-pushed the od-sync-service-broadcast-OKTA-476153 branch from fa79c0b to b4d565c Compare May 5, 2022 19:03
{
broadcastChannelName?: string;
};
SyncStorageServiceOptions & LeaderElectionServiceOptions;
Copy link
Contributor

Choose a reason for hiding this comment

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

changing the name of broadcastChannelName to electionChannelName is a breaking change. Can you have the config respect both options until the next major release

@@ -43,14 +44,15 @@ export class ServiceManager implements ServiceManagerInterface {

// TODO: backwards compatibility, remove in next major version - OKTA-473815
const { autoRenew, autoRemove, syncStorage } = sdk.tokenManager.getOptions();
options.electionChannelName = options.broadcastChannelName;
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be:

options.electionChannelName = options.electionChannelName || options.broadcastChannelName

to support passing electionChannelName (as is being done in the test app)

const client = createAuth(options);
const serviceManagerOptions = (client.serviceManager as any).options;
expect(serviceManagerOptions.electionChannelName).toEqual('test-channel');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a test verifying that electionChannelName option is supported

{ autoRenew, autoRemove, syncStorage },
{
electionChannelName: `${sdk.options.clientId}-election`,
syncChannelName: `${sdk.options.clientId}-sync`,
Copy link
Contributor

Choose a reason for hiding this comment

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

please add a test for these default values in test/spec/ServiceManager

Copy link
Contributor

Choose a reason for hiding this comment

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

also please add a test verifying syncChannelName can be set


export declare type AccessTokenCallback = (key: string, token: AccessToken) => void;
export declare type IDTokenCallback = (key: string, token: IDToken) => void;
export declare type RefreshTokenCallback = (key: string, token: RefreshToken) => void;

// only add methods needed internally
export interface TokenManagerInterface {
on: (event: string, handler: TokenManagerErrorEventHandler | TokenManagerEventHandler, context?: object) => void;
off: (event: string, handler?: TokenManagerErrorEventHandler | TokenManagerEventHandler) => void;
on: (event: string, handler: TokenManagerAnyEventHandler, context?: object) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we could handle this better with function overloads, like we are doing on SIW: https://github.com/okta/okta-signin-widget/blob/okta-signin-widget-6.3.3/src/types/api.ts#L10
Then it will be clear which type of event has which type of callback. As is, I'm not sure as a developer how to implement my handler function - it seems to just have one of 3 shapes, so I have to be prepared for any of the variations. Would be better I think to know which type of callback I should implement for a specific event.

Copy link
Contributor

@aarongranick-okta aarongranick-okta left a comment

Choose a reason for hiding this comment

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

I have tested the functionality and it seems to work great! Just a few comments

Copy link
Contributor

@aarongranick-okta aarongranick-okta left a comment

Choose a reason for hiding this comment

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

please add missing tests and fix logic with electionChannelName

@denysoblohin-okta denysoblohin-okta force-pushed the od-sync-service-broadcast-OKTA-476153 branch 2 times, most recently from 07b4484 to c609644 Compare June 1, 2022 21:53
@denysoblohin-okta denysoblohin-okta force-pushed the od-sync-service-broadcast-OKTA-476153 branch from 498382d to d943b27 Compare June 2, 2022 20:38
@denysoblohin-okta denysoblohin-okta force-pushed the od-sync-service-broadcast-OKTA-476153 branch from d943b27 to 45fc202 Compare June 3, 2022 08:03
@pzi
Copy link

pzi commented Jun 4, 2022

what happened here?

@denysoblohin-okta
Copy link
Contributor Author

@pzi This PR has been merged to master

@pzi
Copy link

pzi commented Jun 7, 2022

@denysoblohin-okta oh ok, I kinda expected the PR to be merged instead of closed :D thanks for that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants