Skip to content

Instantly share code, notes, and snippets.

@eoger
Created April 13, 2020 19:54
Show Gist options
  • Save eoger/73d2b24eedf4ec85e23e17a16e792d33 to your computer and use it in GitHub Desktop.
Save eoger/73d2b24eedf4ec85e23e17a16e792d33 to your computer and use it in GitHub Desktop.
class RustFxAccount {
/**
* Create a new unauthenticated instance of FxA from scratch.
* @param {String} fxaServer Content URL of the remote Firefox Accounts server.
* @param {string} clientId OAuth client_id of the application.
* @param {string} redirectUri Redirection URL to be navigated to at the end of the OAuth login flow.
* @param {string} [tokenServerUrlOverride] Override the token server URL: used by self-hosters of Sync.
*/
constructor({
fxaServer,
clientId,
redirectUri,
tokenServerUrlOverride = null
}) {
// Cool
}
/**
* Restore a previous instance of `RustFxAccount` from a
* serialized state (obtained with `toJSON(...)`).
* @returns {Promise<RustFxAccount>}
*/
static fromJSON(json) {
// Beans
}
/**
* Serialize the state of a `RustFxAccount` instance. It can be restored
* later with `fromJSON(...)`. It is the responsability of the caller to
* persist that serialized state regularly (after operations that mutate
* `RustFxAccount`) in a **secure** location.
* @returns {Promise<string>} The JSON representation of the state.
*/
async toJSON() {
return "foobar"
}
/**
* Request a OAuth token by starting a new OAuth flow.
*
* Once the user has confirmed the authorization grant, they will get redirected to `redirect_url`:
* the caller must intercept that redirection, extract the `code` and `state` query parameters and call
* `completeOAuthFlow(...)` to complete the flow.
*
* @param {[string]} scopes
* @returns {Promise<string>} a URL string that the caller should navigate to.
*/
async beginOAuthFlow(scopes) {
return "https://mozilla.com"
}
/**
* Start a supp-side pairing flow by providing the
* URL displayed by the Auth side.
*
* @param {string} pairingUrl
* @param {[string]} scopes
* @returns {Promise<string>} a URL string that the caller should navigate to.
*/
async beginPairingFlow(pairingUrl, scopes) {
return "https://mozilla.com"
}
/**
* Complete an OAuth flow initiated by `beginOAuthFlow(...)`.
*
* @param {string} code
* @param {string} state
* @throws if there was an error during the login flow.
*/
async completeOAuthFlow(code, state) {
}
/**
* Try to get an OAuth access token.
*
* @typedef {Object} AccessTokenInfo
* @property {string} scope
* @property {string} token
* @property {ScopedKey} [key]
* @property {Date} expiresAt
*
* @typedef {Object} ScopedKey
* @property {string} kty
* @property {string} scope
* @property {string} k
* @property {string} kid
*
* @param {string} scope Single OAuth scope
* @param {Number} ttl Time in seconds for which the token will be used.
* @returns {Promise<AccessTokenInfo>}
* @throws if we couldn't provide an access token
* for this scope. The caller should then start the OAuth Flow again with
* the desired scope.
*/
async getAccessToken(scope, ttl) {
return {
scope: "profile",
token: "deadbeef",
key: {
kty: "aaaa",
scope: "oldsync",
k: "huh",
kid: "123abc"
},
expiresAt: Date.now(),
}
}
/**
* Get the session token if held.
*
* @returns {Promise<string>}
* @throws if a session token is not being held.
*/
async getSessionToken() {
return "abcdef";
}
/**
* Check whether the currently held refresh token is active.
*
* @typedef {Object} IntrospectInfo
* @property {boolean} active
*
* @returns {Promise<IntrospectInfo>}
*/
async checkAuthorizationStatus() {
return {active: true}
}
/*
* This method should be called when a request made with
* an OAuth token failed with an authentication error.
* It clears the internal cache of OAuth access tokens,
* so the caller can try to call `getAccessToken` or `getProfile`
* again.
*/
async clearAccessTokenCache() {
// Huh
}
/*
* Disconnect from the account and optionaly destroy our device record.
* `beginOAuthFlow(...)` will need to be called to reconnect.
*/
async disconnect() {
// Ho?
}
/**
* Gets the logged-in user profile.
*
* @typedef {Object} Profile
* @property {string} uid
* @property {string} email
* @property {Avatar} [avatar]
* @property {string} [displayName]
*
* @typedef {Object} Avatar
* @property {string} url
* @property {boolean} isDefault
*
* @returns {Promise<Profile>}
* @throws if no suitable access token was found to make this call.
* The caller should then start the OAuth login flow again with
* at least the `profile` scope.
*/
async getProfile() {
return {
uid: "12345",
email: "foo@bar.com",
avatar: {
isDefault: false,
url: "https://barbar.foo/img.jpg",
},
displayName: null,
}
}
/**
* Start a migration process from a browser-id based authenticated account.
*
* @returns {Promise<boolean>} true if the migration was successful.
*/
async migrateFromSessionToken(sessionToken, kSync, kXCS) {
return true
}
/**
* Call this function after migrateFromSessionToken is un-successful
* (or after app startup) to figure out if we can call `retryMigrateFromSessionToken`.
*
* @returns {Promise<boolean>} true if resume the migration is possible.
*/
async isInMigrationState() {
return false
}
/**
* Retry a migration that failed earlier because of transient reasons.
*
* @returns {Promise<boolean>} true if the migration was successful.
*/
async retryMigrateFromSessionToken(sessionToken, kSync, kXCS) {
return true
}
/**
* Called after a password change was done through webchannel.
*
* @param {string} sessionToken
*/
async handleSessionTokenChange(sessionToken) {
// Hep
}
/**
* @returns {Promise<string>}
*/
async getTokenServerEndpointURL() {
return "https://mozilla.org";
}
/**
* @returns {Promise<string>}
*/
async getPairingAuthorityURL() {
return "https://mozilla.org";
}
/**
* @returns {Promise<string>}
*/
async getConnectionSuccessURL() {
return "https://mozilla.org";
}
/**
* @returns {Promise<string>}
*/
async getManageAccountURL() {
return "https://mozilla.org";
}
/**
* @returns {Promise<string>}
*/
async getManageDevicesURL() {
return "https://mozilla.org";
}
/**
* Fetch the devices in the account.
*
* @typedef {Object} Device
* @property {string} id
* @property {string} displayName
* @property {DeviceType} deviceType
* @property {boolean} isCurrentDevice
* @property {Number} [lastAccessTime]
* @property {[DeviceCapability]} capabilities
* @property {boolean} subscriptionExpired
* @property {DevicePushSubscription} [subscription]
*
* @typedef {Object} DevicePushSubscription
* @property {string} endpoint
* @property {string} publicKey
* @property {string} authKey
*
* @returns {Promise<[Device]>}
*/
async fetchDevices() {
return [{
id: "foo",
displayName: "Ed's device",
deviceType: DeviceType.desktop,
isCurrentDevice: false,
lastAccessTime: null,
capabilities: [DeviceCapability.sendTab],
subscriptionExpired: false,
subscription: {
endpoint: "https://foo.bar",
publicKey: "abcde",
publicKey: "authKey",
}
}]
}
/**
* Rename the local device
*
* @param {string} name
*/
async setDeviceDisplayName(name) {
// Yay
}
/**
* Handle an incoming Push message payload.
*
* @typedef {Object} DeviceConnectedEvent
* @property {string} deviceName
*
* @typedef {Object} DeviceDisconnectedEvent
* @property {string} deviceId
* @property {boolean} isLocalDevice
*
* @param {string} payload
* @return {Promise<[TabReceivedCommand|DeviceConnectedEvent|DeviceDisconnectedEvent]>}
*/
async handlePushMessage(payload) {
// TODO implement mock. Need to figure out how callers
// will tell which case is which. Maybe add a "type" prop?
}
/**
* Fetch for device commands we didn't receive through Push.
*
* @typedef {Object} TabReceivedCommand
* @property {Device} [from]
* @property {TabData} tabData
*
* @typedef {Object} TabData
* @property {string} title
* @property {string} url
*
* @returns {Promise<[TabReceivedCommand]>}
*/
async pollDeviceCommands() {
return [{
from: null, // go lazy here
tabData: {
title: "Bobotron.com",
url: "https://mozilla.org",
}
}]
}
/**
* Send a tab to a device identified by its ID.
*
* @param {string} targetId
* @param {string} title
* @param {string} url
*/
async sendSingleTab(targetId, title, url) {
// Woo
}
/**
* Update our FxA push subscription.
*
* @param {string} endpoint
* @param {string} publicKey
* @param {string} authKey
*/
async setDevicePushSubscription(endpoint, publicKey, authKey) {
}
/**
* Initialize the local device (should be done only once after log-in).
*
* @param {string} name
* @param {DeviceType} deviceType
* @param {[DeviceCapability]} supportedCapabilities
*/
async initializeDevice(name, deviceType, supportedCapabilities) {
}
/**
* Update the device capabilities if needed.
*
* @param {[DeviceCapability]} supportedCapabilities
*/
async ensureCapabilities(supportedCapabilities) {
}
}
/**
* @enum
*/
const DeviceType = {
desktop: Symbol("desktop"),
mobile: Symbol("mobile"),
tablet: Symbol("tablet"),
tv: Symbol("tv"),
vr: Symbol("vr"),
unknown: Symbol("unknown")
};
/**
* @enum
*/
const DeviceCapability = {
sendTab: Symbol("sendTab")
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment