react-gapi
Version:
Google API per React hook
379 lines (375 loc) • 14.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function createGapiMock(setWindowProp = 'gapi') {
let nextInteraction;
const _user = {
isSignedIn: false,
scopes: [],
props: {
id: undefined,
name: '',
givenName: '',
familyName: '',
imageUrl: '',
email: ''
},
resolveNextInteraction: ()=>{
if (typeof nextInteraction === 'function') {
nextInteraction();
nextInteraction = undefined;
} else if (!nextInteraction) {
nextInteraction = Promise.resolve();
}
}
};
const user = {
isSignedIn: (scopes = [], userProps = {})=>{
_user.isSignedIn = true, _user.scopes = scopes;
_user.props = {
..._user.props,
...userProps,
id: (userProps.id ?? _user.props.id) ?? mockId()
};
},
isNotSignedIn: (scopes = [], userProps)=>{
_user.isSignedIn = false, _user.scopes = scopes;
_user.props = {
..._user.props,
...userProps
};
},
nextInteraction: ()=>{
if (!nextInteraction) {
return new Promise((res)=>{
nextInteraction = res;
});
} else if (nextInteraction instanceof Promise) {
const p = nextInteraction;
nextInteraction = undefined;
return p;
}
throw 'user.nextInteraction() was called multiple times before an interaction happened.\n' + 'This was probably not intended.';
}
};
const _discoveryDocs = {};
const registerDiscoveryDocs = (o)=>Object.keys(o).forEach((k)=>_discoveryDocs[k] = o[k]
)
;
const _modules = {
auth2: createAuthModuleMock,
client: createClientModuleMock
};
const registerModuleMocks = (o)=>Object.keys(o).forEach((k)=>_modules[k] = o[k]
)
;
const gapi = {
load: (modules, then)=>{
Promise.all(modules.split(':').map((k)=>new Promise((res)=>{
res();
if (!gapi[k]) {
gapi[k] = _modules[k] ? _modules[k]({
gapi,
user,
_user,
_discoveryDocs
}) : (name)=>({
init: ()=>notImplemented(`Mock for gapi module "${name}"`)
})
;
}
res();
})
)).then(typeof then === 'function' ? then : then.callback);
}
};
if (setWindowProp) {
window[setWindowProp] = gapi;
}
return {
gapi,
user,
registerModuleMocks,
registerDiscoveryDocs
};
}
function mockId() {
return Math.random().toString(10).substr(2, 10);
}
function notImplemented(descr) {
return `${descr} is not implemented yet.`;
}
function createAuthModuleMock({ user , _user }) {
let initConfig;
let authInstance;
const auth2 = {
init: (config)=>{
const { client_id , fetch_basic_profile =true , scope ='' , } = config;
if (!client_id) {
throw {
message: `Missing required parameter 'client_id'`
};
} else if (!fetch_basic_profile && !scope) {
throw {
message: `Missing required parameter 'scope'`
};
}
if (!initConfig) {
initConfig = {
client_id,
fetch_basic_profile,
scope
};
} else if (client_id !== initConfig.client_id || fetch_basic_profile !== initConfig.fetch_basic_profile || scope !== initConfig.scope) {
throw {
message: 'gapi.auth2 has been initialized with different options. Consider calling gapi.auth2.getAuthInstance() instead of gapi.auth2.init().'
};
}
if (!authInstance) {
const effectiveScope = fetch_basic_profile ? scope + ' openid email profile' : scope;
authInstance = createAuthInstanceMock(fetch_basic_profile, effectiveScope);
}
return authInstance;
},
getAuthInstance: ()=>authInstance
,
authorize: ()=>{
throw notImplemented('auth2.authorize');
},
enableDebugLogs: ()=>{
throw notImplemented('auth2.enableDebugLogs');
}
};
function createAuthInstanceMock(fetch_basic_profile, initScope) {
let currentUser;
const isSignedInListeners = [];
const currentUserListeners = [];
const auth = {
then: (onInit, onFailure)=>{
Promise.resolve().then(()=>onInit(auth)
, (r)=>onFailure(r)
);
},
signIn: ({ scope ='' , prompt =undefined } = {})=>{
const scopeArray = (initScope ?? '' + ' ' + scope).split(' ').filter((k)=>Boolean(k)
);
return requestScopes(scopeArray, prompt);
},
signOut: ()=>new Promise((res)=>{
const isSignedInChanged = _user.isSignedIn;
_user.isSignedIn = false;
currentUser = createCurrentUserMock();
res(currentUser);
notifyListeners(isSignedInChanged);
})
,
disconnect: ()=>new Promise((res)=>{
_user.scopes = [];
auth.signOut().then(()=>res()
);
})
,
currentUser: {
// currentUser.get() can return undefined on newly initialized GoogleAuth
get: ()=>currentUser
,
listen: (c)=>{
currentUserListeners.push(c);
}
},
isSignedIn: {
get: ()=>_user.isSignedIn
,
listen: (c)=>{
isSignedInListeners.push(c);
}
},
grantOfflineAccess: ()=>{
throw notImplemented('GoogleAuth.grantOfflineAccess');
},
attachClickHandler: ()=>{
throw notImplemented('GoogleAuth.attachClickHandler');
}
};
if (_user.isSignedIn) {
currentUser = createCurrentUserMock();
}
return auth;
function notifyListeners(isSignedInChanged) {
if (isSignedInChanged) {
isSignedInListeners.forEach((c)=>c(_user.isSignedIn)
);
}
currentUserListeners.forEach((c)=>currentUser && c(currentUser)
);
}
function requestScopes(scopes, prompt) {
if (_user.promise) {
throw 'The behavior when a previous Promise is still pending is unknown - call user.grantsScopes() or user.closesPopup()';
}
return _user.promise = new Promise((res, rej)=>{
function clearInteraction() {
delete user.grantsScopes;
delete user.closesPopup;
Promise.resolve().then(()=>{
delete _user.promise;
});
}
user.grantsScopes = (grantedScopes = true, userProps = {})=>{
const isSignedInChanged = !_user.isSignedIn;
_user.isSignedIn = true;
if (grantedScopes === true) {
grantedScopes = scopes;
}
// openid is always added
grantedScopes.push('openid');
grantedScopes.forEach((k)=>{
if (!_user.scopes.includes(k)) {
_user.scopes.push(k);
}
// the module adds these scopes automatically
if (k === 'profile' && !_user.scopes.includes('https://www.googleapis.com/auth/userinfo.profile')) {
_user.scopes.push('https://www.googleapis.com/auth/userinfo.profile');
} else if (k === 'email' && !_user.scopes.includes('https://www.googleapis.com/auth/userinfo.email')) {
_user.scopes.push('https://www.googleapis.com/auth/userinfo.email');
}
});
_user.props = {
..._user.props,
...userProps,
id: (userProps?.id ?? _user.props.id) ?? mockId()
};
currentUser = createCurrentUserMock();
clearInteraction();
res(currentUser);
notifyListeners(isSignedInChanged);
};
user.deniesAccess = ()=>{
clearInteraction();
rej({
error: 'access_denied'
});
};
user.closesPopup = ()=>{
clearInteraction();
rej({
error: 'popup_closed_by_user'
});
};
if (scopes.every((k)=>_user.scopes.includes(k)
)) {
// if a user granted scopes before, the popup closes automatically
if (_user.isSignedIn && prompt && ![
'consent',
'select_account'
].includes(prompt)) {
user.grantsScopes();
}
} else if (prompt === 'none') {
rej({
error_subtype: 'access_denied',
error: 'immediate_failed'
});
}
_user.resolveNextInteraction();
});
}
function createCurrentUserMock() {
const basicProfile = createBasicProfileMock();
return {
getId: ()=>_user.props.id ?? ''
,
isSignedIn: ()=>_user.isSignedIn
,
getHostedDomain: ()=>{
throw notImplemented('GoogleUser.getHostedDomain');
},
getGrantedScopes: ()=>_user.isSignedIn ? _user.scopes.concat(fetch_basic_profile ? [
'email',
'openid',
'profile'
] : []).join(' ') : ''
,
getBasicProfile: ()=>basicProfile
,
getAuthResponse: ()=>{
throw notImplemented('GoogleUser.getHostedDomain');
},
reloadAuthResponse: ()=>{
throw notImplemented('GoogleUser.getHostedDomain');
},
hasGrantedScopes: (scope)=>scope.split(' ').filter((k)=>Boolean(k) && !_user.scopes.includes(k)
).length > 0
,
grant: (options = {})=>authInstance && authInstance.signIn(options)
,
grantOfflineAccess: ()=>{
throw notImplemented('GoogleUser.getHostedDomain');
},
disconnect: ()=>authInstance && authInstance.disconnect()
};
function createBasicProfileMock() {
return initConfig?.fetch_basic_profile ? {
getId: ()=>_user.props.id ?? ''
,
getName: ()=>_user.props.name ?? ''
,
getGivenName: ()=>_user.props.givenName ?? ''
,
getFamilyName: ()=>_user.props.familyName ?? ''
,
getImageUrl: ()=>_user.props.imageUrl ?? ''
,
getEmail: ()=>_user.props.email ?? ''
} : {
getId: ()=>_user.props.id ?? ''
,
getName: ()=>''
,
getGivenName: ()=>''
,
getFamilyName: ()=>''
,
getImageUrl: ()=>''
,
getEmail: ()=>''
};
}
}
}
return auth2;
}
function createClientModuleMock({ gapi , _discoveryDocs }) {
const client = {
init: ({ clientId =undefined , scope =undefined , discoveryDocs =[] })=>new Promise((res1, rej1)=>{
const p = [];
if (scope) {
if (gapi.auth2) {
p.push(gapi.auth2.init({
client_id: clientId,
scope
}));
} else {
throw 'gapi.auth2 not loaded';
}
}
p.push(...discoveryDocs.map((k)=>new Promise((res, rej)=>{
try {
if (typeof _discoveryDocs[k] === 'function') {
_discoveryDocs[k](gapi);
} else {
throw `Tried to load discoveryDocs ${k} - use registerDiscoveryDocs to mock discoveryDocs`;
}
res();
} catch (e) {
rej(e);
}
})
));
Promise.all(p).then(()=>res1()
, ()=>rej1()
);
})
};
return client;
}
exports.createGapiMock = createGapiMock;