nwa-client
Version:
Native WebApp client library
219 lines (206 loc) • 9.63 kB
text/typescript
import { AppRequest } from './models';
import { IApp } from './interfaces';
declare global {
interface Window {
App: IApp;
Sentry: any;
select: any;
}
}
function getMeta(name) {
const meta = document.querySelector('meta[name="' + name + '"]');
if (meta) {
return meta.getAttribute('content');
} else {
return '#FFFFFFFF';
}
}
const isAndroid = /android/i.test(navigator.userAgent);
if (typeof window.App !== 'undefined') {
if (!window.App.on) {
const appDebug = localStorage && localStorage.getItem('appDebug') === 'true';
let requestId = 0;
const handlers: ((...args) => boolean | void)[][] = [];
const requests: AppRequest[] = [];
window.App = new Proxy(window.App, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(target: any, propKey: string) {
if (propKey === 'isWeb') {
return false;
}
return (...args) => {
if (window.Sentry && appDebug) {
window.Sentry.captureMessage(propKey, {
extra: args,
level: 'info',
});
}
if (propKey === 'emit') {
const channel = args.shift();
if (channel in handlers) {
try {
let success = true;
for (const handler of handlers[channel]) {
success = success && handler(...args) !== false;
}
return success;
} catch (e) {
return false;
}
} else {
return false;
}
} else if (propKey === 'onSuccess' || propKey === 'onError' || propKey === 'resolve' || propKey === 'reject') {
const request = requests[args[0]];
if (request) {
if (args.length > 1) {
return request[propKey](args[1]);
} else {
return request[propKey]();
}
} else {
// eslint-disable-next-line max-len, no-console
console.error('Unable to resolve request. (id=' + args[0] + ', now=' + Date.now() + ', data=' + JSON.stringify(args[1]) + ')');
}
} else if (target[propKey] === undefined) {
if (propKey === 'on') {
if (!handlers[args[0]]) {
handlers[args[0]] = [];
}
handlers[args[0]].push(args[1]);
}
const id = 'req-' + (requestId++) + '-' + Date.now();
if (isAndroid) {
if (target.request) {
return new Promise((resolve, reject) => {
requests[id] = { resolve, reject };
target.request(propKey, id, JSON.stringify(args));
});
} else {
return new Promise((resolve, reject) => {
requests[id] = { resolve, reject };
target.postMessage(propKey, id, JSON.stringify(args));
});
}
} else {
return target.postMessage(JSON.stringify({ method: propKey, params: args })).then((result) => {
if (result && result.compatError) {
throw result.compatError;
} else {
return result;
}
});
}
}
};
},
});
// Bypass native browser functionalities
if (isAndroid) {
window.print = () => window.App.print();
navigator.share = (data) => window.App.share(data);
navigator.clipboard.writeText = (data) => window.App.copy(data);
navigator.clipboard.readText = () => window.App.paste();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.setAppBadge = (data) => window.App.setAppBadge(data);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.clearAppBadge = () => window.App.clearAppBadge();
navigator.vibrate = (data) => window.App.vibrate(data);
window.open = (url, name) => window.App.openUrl(url, name);
if (!('permissions' in navigator)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.permissions = {};
}
navigator.permissions.query = window.App.hasPermission;
} else {
const positionCallbacks: any[] = [];
window.print = () => window.App.print();
window.open = (url, name) => window.App.openUrl(url, name);
navigator.share = (data) => window.App.share(data);
navigator.clipboard.writeText = (data) => window.App.copy(data);
navigator.clipboard.readText = () => window.App.paste();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.setAppBadge = (data) => window.App.setAppBadge(data);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.clearAppBadge = () => window.App.clearAppBadge();
navigator.vibrate = (data) => window.App.vibrate(data);
window.App.on('position', location => {
if (positionCallbacks.length) {
positionCallbacks.forEach(obj => obj.callback(location));
} else {
window.App.watchPosition(false);
}
})
navigator.geolocation.watchPosition = (callback, errorCallback, options) => {
const watchId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
if (!positionCallbacks.length) {
window.App.watchPosition(true, options).then(callback).catch(errorCallback);
}
positionCallbacks.push({
id: watchId,
callback,
});
return watchId
}
navigator.geolocation.clearWatch = (watchId) => {
positionCallbacks.filter((cb, i, arr) => {
if (cb.id == watchId) {
arr.splice(i, 1);
return true;
} else {
return false;
}
})
if (!positionCallbacks.length) {
window.App.watchPosition(false);
}
}
navigator.geolocation.getCurrentPosition = (callback, errorCallback, settings) => window.App.getCurrentPosition(settings).then(callback).catch(errorCallback)
}
window.select = function (params: any) {
return App.select(params).then((files: any) => {
const dataTransfer = new DataTransfer();
for (const file of files) {
const bytes = atob(file.data);
let length = bytes.length;
const out = new Uint8Array(length);
// Loop and convert.
while (length--) {
out[length] = bytes.charCodeAt(length);
}
dataTransfer.items.add(new File([out], file.name, { type: file.type }));
}
return dataTransfer;
});
};
}
document.addEventListener('DOMContentLoaded', () => {
try {
const viewport = getMeta('viewport');
const keyboardResize = !!viewport && viewport.includes('interactive-widget=resizes-content');
const appBarOffset = !!viewport && !viewport.includes('viewport-fit=cover');
const navBarOffset = !!viewport && !viewport.includes('viewport-fit=cover');
const backgroundColor = parseInt(getMeta('theme-color')!.substring(1), 16);
const appBarColor = parseInt(getMeta('app-bar-color')!.substring(1), 16);
const navBarColor = parseInt(getMeta('nav-bar-color')!.substring(1), 16);
window.App.updateAppStyle({
backgroundColor,
appBarColor,
navBarColor,
appBarOffset,
navBarOffset,
keyboardResize,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Unable to update app style', e);
}
}, false);
}
const App: IApp = window.App;
export default App;