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

[TP-179336] Use postmessage instead of storage event for embedded case #2

Merged
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
29 changes: 29 additions & 0 deletions addon/mixins/ui-service-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var ServicesMixin = Mixin.create({
let service = this;
let lastRemote = this.remote;
let storageToriiEventHandler;
let messageToriiEventHandler;

return new EmberPromise(function (resolve, reject) {
if (lastRemote) {
Expand All @@ -55,7 +56,34 @@ var ServicesMixin = Mixin.create({
});
}
};

// Using postMessage as an alternative to localStorage/storageEvent
AndrewHYi marked this conversation as resolved.
Show resolved Hide resolved
// for case of web site embedded in iframe
messageToriiEventHandler = function (messageEvent) {
if (messageEvent.data === 'getPendingRequestKey') {

Choose a reason for hiding this comment

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

suggestion: this fix should be merged alongside the first commit. Also, the commit about feature flags is confusing here, but that only makes sense in the context of the SCF application (not here in Torii fork land).

messageEvent.source.postMessage(
JSON.stringify({ pendingRequestKey: service.pendingRequestKey }),
window.location.origin
);
} else {
const msg = JSON.parse(messageEvent.data);
const key = Object.keys(msg)[0];
var remoteIdFromEvent = PopupIdSerializer.deserialize(
decodeURIComponent(key)
);
if (remoteId === remoteIdFromEvent) {
var data = parseMessage(msg[key], keys);
localStorage.removeItem(key);
run(function () {
resolve(data);
});
}
}
};
window.addEventListener('message', messageToriiEventHandler);

var pendingRequestKey = PopupIdSerializer.serialize(remoteId);
service.pendingRequestKey = pendingRequestKey;
localStorage.setItem(CURRENT_REQUEST_KEY, pendingRequestKey);
localStorage.removeItem(WARNING_KEY);

Expand Down Expand Up @@ -115,6 +143,7 @@ var ServicesMixin = Mixin.create({
}).finally(function () {
// didClose will reject this same promise, but it has already resolved.
service.close();
window.removeEventListener('message', messageToriiEventHandler);
window.removeEventListener('storage', storageToriiEventHandler);
});
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "torii",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",

Choose a reason for hiding this comment

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

suggestion: the version bump should be the last commit - also, do we need to bump the version up again?

"description": "A set of clean abstractions for authentication in Ember.js",
"keywords": [
"authentication",
Expand Down
43 changes: 38 additions & 5 deletions public/redirect.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,49 @@
<meta charset="UTF-8">
<title>Torii OAuth Redirect</title>
<script>
var CURRENT_REQUEST_KEY = '__torii_request';
var pendingRequestKey = window.localStorage.getItem(CURRENT_REQUEST_KEY);
const CURRENT_REQUEST_KEY = '__torii_request';
let pendingRequestKey = window.localStorage.getItem(CURRENT_REQUEST_KEY);
const origin = window.location.origin;
let opener = window.opener;
try {
if (opener && opener.parent && opener.parent.origin === origin) {
opener = opener.parent;
}
} catch {}

if (pendingRequestKey) {
window.localStorage.removeItem(CURRENT_REQUEST_KEY);
var url = window.location.toString();
const url = window.location.toString();
window.localStorage.setItem(pendingRequestKey, url);
}
const obj = {};
obj[pendingRequestKey] = url;
opener?.postMessage(
JSON.stringify(obj),
origin
);

window.close();
} else {
// localStorage not shared with opener due to browser restrictions
opener?.postMessage('getPendingRequestKey', origin);
window.addEventListener('message', function(messageEvent) {
if (messageEvent.source === opener) {
const msg = JSON.parse(messageEvent.data);
if (msg.pendingRequestKey) {
pendingRequestKey = msg.pendingRequestKey;
url = window.location.toString();
const obj = {};
obj[pendingRequestKey] = url;
opener.postMessage(
JSON.stringify(obj),
origin
);

window.close();
window.close();
}
}
});
}
</script>
</head>
</html>
129 changes: 129 additions & 0 deletions tests/unit/mixins/ui-service-mixin-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* eslint-disable ember/no-mixins, ember/no-new-mixins, ember/no-classic-classes */

import EmberObject from '@ember/object';
Copy link

@AndrewHYi AndrewHYi Sep 23, 2024

Choose a reason for hiding this comment

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

suggestion: let's add the test to the first commit.

thought: I need some help walking through the test code - I'll schedule something for this afternoon.

Update: tests seem to be failing - can reschedule the group review we rescheduled if you need more time.

import Evented from '@ember/object/evented';
import UiServiceMixin, {
CURRENT_REQUEST_KEY,
} from 'torii/mixins/ui-service-mixin';
import PopupIdSerializer from 'torii/lib/popup-id-serializer';
import { module, test } from 'qunit';

module('Unit | Mixin | ui-service-mixin', function (hooks) {
const originalWindowOpen = window.open;

const popupId = '09123-asdf';
const expectedUrl = 'http://authServer';
const expectedRedirectUrl = 'http://localserver?code=fr';
const expectedMessage = 'getPendingRequestKey';

const mockWindowListener = (event) => {
let msg;
try {
msg = JSON.parse(event.data);
} catch {
// allow
}
if (msg && Object.keys(msg)[0] === 'pendingRequestKey') {
const obj = {};
const key = PopupIdSerializer.serialize(encodeURIComponent(popupId));
obj[key] = `${expectedUrl}?redirect_url=${expectedRedirectUrl}`;
window.dispatchEvent(
new MessageEvent('message', {
data: JSON.stringify(obj),
source: window,
})
);
}
};

const buildMockWindow = function (windowName) {
windowName = windowName || '';
window.addEventListener('message', mockWindowListener);
return {
name: windowName,
focus() {},
close() {},
open() {
this.postMessage(expectedMessage);
},
postMessage(msg) {
window.dispatchEvent(
new MessageEvent('message', { data: msg, source: window })
);
},
};
};

const buildPopupIdGenerator = function (popupId) {
return {
generate() {
return popupId;
},
};
};

let Popup = EmberObject.extend(Evented, UiServiceMixin, {
// Open a popup window.
openRemote(_url, pendingRequestKey) {
this.remote = buildMockWindow(pendingRequestKey);
this.remote.open();
},

closeRemote() {
this.remote.closed = true;
},

pollRemote() {
if (!this.remote) {
return;
}
},
});

let popup;

hooks.beforeEach(function () {
popup = Popup.create({ remoteIdGenerator: buildPopupIdGenerator(popupId) });
localStorage.removeItem(CURRENT_REQUEST_KEY);
});

hooks.afterEach(async function () {
localStorage.removeItem(CURRENT_REQUEST_KEY);
window.open = originalWindowOpen;
window.removeEventListener('message', mockWindowListener);
popup.destroy();
});

test('requests pending request key', function (assert) {
assert.expect(1);

let resultMessage;
const resultMessageListener = (event) => {
resultMessage = event.data;
};
try {
window.addEventListener('message', resultMessageListener);

popup.openRemote(expectedUrl, CURRENT_REQUEST_KEY);

assert.strictEqual(
resultMessage,
expectedMessage,
'requests pendingRequestKey'
);
} finally {
window.removeEventListener('message', resultMessageListener);
}
});

test('returns data after receiving key', async function (assert) {
const keys = ['redirect_url'];
const result = await popup.open(expectedUrl, keys);

assert.strictEqual(
result.redirect_url,
expectedRedirectUrl,
'returns data successfully'
);
});
});
Loading