-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat: e2e api #102
feat: e2e api #102
Changes from 4 commits
878d93b
191950b
309a0f8
f2b5109
748a2e0
f0ee084
96c2402
a753124
cbb5f4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,17 +5,48 @@ type LocalStorage = { | |||||
get: (storeValues: string[], callback: (result: { [key: string]: unknown }) => void) => void | ||||||
} | ||||||
|
||||||
class DappSecurityContext { | ||||||
private readonly storage: LocalStorage | ||||||
private readonly storePrefix: string | ||||||
abstract class DappSecurityContext { | ||||||
protected readonly storage: LocalStorage | ||||||
protected readonly storePrefix: string | ||||||
|
||||||
constructor(storePrefix: string) { | ||||||
this.storage = chrome.storage.local | ||||||
this.storePrefix = storePrefix | ||||||
} | ||||||
|
||||||
public abstract isValid(sender: chrome.runtime.MessageSender): boolean | ||||||
|
||||||
/** STORAGE FUNCTIONS */ | ||||||
|
||||||
public async setStorageItem(keyName: string, keyValue: unknown): Promise<void> { | ||||||
const key = this.enrichStorageKey(keyName) | ||||||
|
||||||
await new Promise(resolve => this.storage.set({ [key]: keyValue }, () => resolve(true))) | ||||||
} | ||||||
|
||||||
public getStorageItem(keyName: string): Promise<unknown> { | ||||||
const key = this.enrichStorageKey(keyName) | ||||||
|
||||||
return new Promise(resolve => | ||||||
this.storage.get([key], result => { | ||||||
resolve(result[key]) | ||||||
}), | ||||||
) | ||||||
} | ||||||
|
||||||
private enrichStorageKey(keyName: string): string { | ||||||
return `${this.storePrefix}-${keyName}` | ||||||
} | ||||||
} | ||||||
|
||||||
class TabDappSecurityContext extends DappSecurityContext { | ||||||
public constructor( | ||||||
private tabId: number, | ||||||
private frameId: number, | ||||||
private frameContentRoot: string, | ||||||
private originContentRoot: string, | ||||||
) { | ||||||
this.storage = chrome.storage.local | ||||||
this.storePrefix = this.frameContentRoot || this.originContentRoot | ||||||
super(frameContentRoot || originContentRoot) | ||||||
} | ||||||
|
||||||
public isValidTabId(tabId: number): boolean { | ||||||
|
@@ -33,26 +64,30 @@ class DappSecurityContext { | |||||
return originContentRoot === this.originContentRoot | ||||||
} | ||||||
|
||||||
/** STORAGE FUNCTIONS */ | ||||||
public isValid(sender: chrome.runtime.MessageSender): boolean { | ||||||
const tabId = senderTabId(sender) | ||||||
const frameId = senderFrameId(sender) | ||||||
const frameContentRoot = senderFrameOrigin(sender) | ||||||
const originContentRoot = senderContentOrigin(sender) | ||||||
|
||||||
public async setStorageItem(keyName: string, keyValue: unknown): Promise<void> { | ||||||
const key = this.enrichStorageKey(keyName) | ||||||
console.log(`tabid ${tabId} frameId ${frameId} frameconte ${frameContentRoot} originCon ${originContentRoot}`) | ||||||
|
||||||
await new Promise(resolve => this.storage.set({ [key]: keyValue }, () => resolve(true))) | ||||||
return ( | ||||||
this.isValidTabId(tabId) && | ||||||
this.isValidFrameId(frameId) && | ||||||
this.isFrameContentRoot(frameContentRoot) && | ||||||
this.isValidOriginContentRoot(originContentRoot) | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
public getStorageItem(keyName: string): Promise<unknown> { | ||||||
const key = this.enrichStorageKey(keyName) | ||||||
|
||||||
return new Promise(resolve => | ||||||
this.storage.get([key], result => { | ||||||
resolve(result[key]) | ||||||
}), | ||||||
) | ||||||
class ExtensionDappSecurityContext extends DappSecurityContext { | ||||||
constructor(private extensionId: string) { | ||||||
super(extensionId) | ||||||
} | ||||||
|
||||||
private enrichStorageKey(keyName: string): string { | ||||||
return `${this.storePrefix}-${keyName}` | ||||||
public isValid(sender: chrome.runtime.MessageSender): boolean { | ||||||
return sender.id === this.extensionId | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -63,12 +98,20 @@ export class DappSessionManager { | |||||
this.securityContexts = {} | ||||||
} | ||||||
public register(sessionId: string, sender: chrome.runtime.MessageSender): void { | ||||||
const tabId = senderTabId(sender) | ||||||
const frameId = senderFrameId(sender) | ||||||
const frameContentRoot = senderFrameOrigin(sender) | ||||||
const originContentRoot = senderContentOrigin(sender) | ||||||
let context: DappSecurityContext | null = null | ||||||
|
||||||
this.securityContexts[sessionId] = new DappSecurityContext(tabId, frameId, frameContentRoot, originContentRoot) | ||||||
if (this.isSenderExtension(sender)) { | ||||||
context = new ExtensionDappSecurityContext(String(sessionId)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm telling you because the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, this sessionId/extensionId is not needed at all. |
||||||
} else { | ||||||
const tabId = senderTabId(sender) | ||||||
const frameId = senderFrameId(sender) | ||||||
const frameContentRoot = senderFrameOrigin(sender) | ||||||
const originContentRoot = senderContentOrigin(sender) | ||||||
|
||||||
context = new TabDappSecurityContext(tabId, frameId, frameContentRoot, originContentRoot) | ||||||
} | ||||||
|
||||||
this.securityContexts[sessionId] = context | ||||||
console.log(`dApp session "${sessionId}" has been initialized`, this.securityContexts[sessionId]) | ||||||
} | ||||||
|
||||||
|
@@ -80,19 +123,7 @@ export class DappSessionManager { | |||||
|
||||||
if (!context) return false | ||||||
|
||||||
const tabId = senderTabId(sender) | ||||||
const frameId = senderFrameId(sender) | ||||||
const frameContentRoot = senderFrameOrigin(sender) | ||||||
const originContentRoot = senderContentOrigin(sender) | ||||||
|
||||||
console.log(`tabid ${tabId} frameId ${frameId} frameconte ${frameContentRoot} originCon ${originContentRoot}`) | ||||||
|
||||||
return ( | ||||||
context.isValidTabId(tabId) && | ||||||
context.isValidFrameId(frameId) && | ||||||
context.isFrameContentRoot(frameContentRoot) && | ||||||
context.isValidOriginContentRoot(originContentRoot) | ||||||
) | ||||||
return context.isValid(sender) | ||||||
} | ||||||
|
||||||
public getStorageItem( | ||||||
|
@@ -122,4 +153,9 @@ export class DappSessionManager { | |||||
|
||||||
return securityContext | ||||||
} | ||||||
|
||||||
private isSenderExtension(sender: chrome.runtime.MessageSender): boolean { | ||||||
// TODO If support for other browsers is needed, then this function should be extended | ||||||
return Boolean(sender.url?.startsWith('chrome-extension://')) | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { nanoid } from 'nanoid' | ||
import { bzzProtocolToFakeUrl } from '../../contentscript/swarm-library/bzz-link' | ||
import { fakeUrl } from '../../utils/fake-url' | ||
import { appendSwarmSessionIdToUrl } from '../../utils/swarm-session-id' | ||
import { DappSessionManager } from '../dapp-session.manager' | ||
|
||
enum Action { | ||
REGISTER = 'register', | ||
LOCAL_STORAGE_GET = 'localStorage.getItem', | ||
LOCAL_STORAGE_SET = 'localStorage.setItem', | ||
BZZ_LINK_PROTOCOL_TO_FAKE_URL = 'bzzLink.bzzProtocolToFakeUrl', | ||
BZZ_LINK_LINK_URL_TO_FAKE_URL = 'bzzLink.bzzLinkUrlToFakeUrl', | ||
BZZ_LINK_URL_TO_FAKE_URL = 'bzzLink.urlToFakeUrl', | ||
WEB2_HELPER_FAKE_BEE_API_ADDRESS = 'web2Helper.fakeBeeApiAddress', | ||
WEB2_HELPER_FAKE_BZZ_ADDRESS = 'web2Helper.fakeBzzAddress', | ||
} | ||
|
||
interface Request<A extends Action, P> { | ||
action: A | ||
sessionId: string | ||
parameters: P | ||
} | ||
nugaon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
interface Response { | ||
data?: any | ||
error?: string | ||
} | ||
|
||
type RegisterRequest = Request<Action.REGISTER, void> | ||
type LocalStorageGetRequest = Request<Action.LOCAL_STORAGE_GET, { name: string }> | ||
type LocalStorageSetRequest = Request<Action.LOCAL_STORAGE_SET, { name: string; value: unknown }> | ||
type BzzLinkProtocolToFakeUrlRequest = Request<Action.BZZ_LINK_PROTOCOL_TO_FAKE_URL, { url: string; newPage: boolean }> | ||
type BzzLinkLinkUrlToFakeUrlRequest = Request< | ||
Action.BZZ_LINK_LINK_URL_TO_FAKE_URL, | ||
{ bzzLinkUrl: string; newPage: boolean } | ||
> | ||
type BzzLinkUrlToFakeUrlRequest = Request<Action.BZZ_LINK_URL_TO_FAKE_URL, { url: string; newPage: boolean }> | ||
type Web2HelperFakeBeeApiAddressRequesst = Request<Action.WEB2_HELPER_FAKE_BEE_API_ADDRESS, void> | ||
type Web2HelprFakeBzzAddressRequest = Request<Action.WEB2_HELPER_FAKE_BZZ_ADDRESS, { reference: string }> | ||
|
||
type RequestType = | ||
| RegisterRequest | ||
| LocalStorageGetRequest | ||
| LocalStorageSetRequest | ||
| BzzLinkProtocolToFakeUrlRequest | ||
| BzzLinkLinkUrlToFakeUrlRequest | ||
| BzzLinkUrlToFakeUrlRequest | ||
| Web2HelperFakeBeeApiAddressRequesst | ||
| Web2HelprFakeBzzAddressRequest | ||
|
||
export class E2ESessionFeeder { | ||
constructor(private manager: DappSessionManager) { | ||
this.serveEvents() | ||
} | ||
|
||
serveEvents(): void { | ||
console.log('Register E2ESessionFeeder event listeners...') | ||
|
||
// register dapp session id | ||
chrome.runtime.onMessageExternal.addListener( | ||
async ( | ||
request: RequestType, | ||
sender: chrome.runtime.MessageSender, | ||
sendResponse: (response?: Response) => void, | ||
) => { | ||
const { action } = request || {} | ||
const senderId = sender.id as string | ||
const response: Response = {} | ||
|
||
try { | ||
if (!senderId) { | ||
throw new Error('Invalid extension ID') | ||
} | ||
nugaon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (action === Action.REGISTER) { | ||
response.data = this.handleRegistration(sender) | ||
} else if (action === Action.LOCAL_STORAGE_GET) { | ||
response.data = await this.handleLocalStorageGet(request) | ||
} else if (action === Action.LOCAL_STORAGE_SET) { | ||
await this.handleLocalStorageSet(request) | ||
} else if (action === Action.WEB2_HELPER_FAKE_BEE_API_ADDRESS) { | ||
response.data = this.handleWeb2FakeBeeApiAddress(request) | ||
} else if (action === Action.WEB2_HELPER_FAKE_BZZ_ADDRESS) { | ||
response.data = this.handleWeb2FakeBzzAddress(request) | ||
} else if (action === Action.BZZ_LINK_PROTOCOL_TO_FAKE_URL) { | ||
response.data = this.handleBzzLinkProtocolToFakeUrl(request) | ||
} else if (action === Action.BZZ_LINK_LINK_URL_TO_FAKE_URL) { | ||
response.data = this.handleBzzLinkUrlToFakeUrl(request) | ||
} else if (action === Action.BZZ_LINK_URL_TO_FAKE_URL) { | ||
response.data = this.handleBzzUrlToFakeUrl(request) | ||
} else { | ||
throw new Error(`Unknown action ${action}`) | ||
nugaon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} catch (error) { | ||
response.error = String(error) | ||
} | ||
|
||
this.sendResponse(response, action, senderId, sendResponse) | ||
}, | ||
) | ||
} | ||
|
||
private sendResponse( | ||
response: Response | null, | ||
action: Action, | ||
senderId: string, | ||
sendResponse: (response?: Response) => void, | ||
nugaon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
if (response?.error) { | ||
console.warn( | ||
`Sending error response for action '${action}' to the extension with ID '${senderId}'.`, | ||
response.error, | ||
) | ||
} else { | ||
console.log(`The extension with ID '${senderId}' successfully invoked action '${action}'.`) | ||
} | ||
sendResponse(response || {}) | ||
} | ||
|
||
private handleRegistration(sender: chrome.runtime.MessageSender): string { | ||
const sessionId = nanoid() | ||
this.manager.register(sessionId, sender) | ||
|
||
return sessionId | ||
} | ||
|
||
private handleLocalStorageGet(request: RequestType): Promise<unknown> { | ||
const { | ||
sessionId, | ||
parameters: { name }, | ||
} = request as LocalStorageGetRequest | ||
|
||
if (!name) { | ||
throw new Error('Cannot get item from localStorage. Item name required') | ||
} | ||
|
||
return this.manager.getStorageItem(sessionId, name) | ||
} | ||
|
||
private handleLocalStorageSet(request: RequestType): Promise<void> { | ||
const { | ||
sessionId, | ||
parameters: { name, value }, | ||
} = request as LocalStorageSetRequest | ||
|
||
if (!name) { | ||
throw new Error('Cannot get item from localStorage. Item name required') | ||
} | ||
|
||
return this.manager.setStorageItem(sessionId, name, value) | ||
} | ||
nugaon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private handleBzzLinkProtocolToFakeUrl(request: RequestType): string | null { | ||
const { | ||
sessionId, | ||
parameters: { url, newPage }, | ||
} = request as BzzLinkProtocolToFakeUrlRequest | ||
|
||
return bzzProtocolToFakeUrl(url, sessionId, newPage) | ||
} | ||
|
||
private handleBzzLinkUrlToFakeUrl(request: RequestType): string | null { | ||
const { | ||
sessionId, | ||
parameters: { bzzLinkUrl, newPage }, | ||
} = request as BzzLinkLinkUrlToFakeUrlRequest | ||
|
||
return bzzProtocolToFakeUrl(bzzLinkUrl, sessionId, newPage) | ||
} | ||
|
||
private handleBzzUrlToFakeUrl(request: RequestType): string | null { | ||
const { | ||
sessionId, | ||
parameters: { url, newPage }, | ||
} = request as BzzLinkUrlToFakeUrlRequest | ||
|
||
return bzzProtocolToFakeUrl(url, sessionId, newPage) | ||
} | ||
|
||
private handleWeb2FakeBeeApiAddress(request: RequestType): string { | ||
const { sessionId } = request | ||
|
||
return appendSwarmSessionIdToUrl(fakeUrl.beeApiAddress, sessionId) | ||
} | ||
|
||
private handleWeb2FakeBzzAddress(request: RequestType): string { | ||
const { | ||
sessionId, | ||
parameters: { reference }, | ||
} = request as Web2HelprFakeBzzAddressRequest | ||
|
||
return appendSwarmSessionIdToUrl(`${fakeUrl.bzzProtocol}/${reference}`, sessionId) | ||
} | ||
Comment on lines
+125
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic could be completely on the other extension's side and you could save the async calls. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { | ||
bzzProtocolToFakeUrl as bzzProtocolToFakeUrlFn, | ||
bzzLinkUrlToFakeUrl as bzzLinkUrlToFakeUrlFn, | ||
urlToFakeUrl as urlToFakeUrlFn, | ||
} from './bzz-link' | ||
|
||
/** gives back the fake URL of the BZZ protocol reference or null if the first parameter is not a valid BZZ protocol reference */ | ||
export function bzzProtocolToFakeUrl(url: string, newPage = false): string | null { | ||
return bzzProtocolToFakeUrlFn(url, window.swarm.sessionId, newPage) | ||
} | ||
|
||
/** gives back the fake URL of the bzz.link or null if the first parameter is not a valid bzz.link reference */ | ||
export function bzzLinkUrlToFakeUrl(bzzLinkUrl: string, newPage = false): string | null { | ||
return bzzLinkUrlToFakeUrlFn(bzzLinkUrl, window.swarm.sessionId, newPage) | ||
} | ||
|
||
/** transform the given URL to FakeURL or return null if it is not possible */ | ||
export function urlToFakeUrl(url: string, newPage = false): string | null { | ||
return urlToFakeUrlFn(url, window.swarm.sessionId, newPage) | ||
} | ||
export * from '../../utils/bzz-link' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this will cause problem for allowed the storage key length. can you check what is the limit for that? Maybe would be better, if we transform the ID here to some prefixed and trimmed ID