nwa-client
Version:
Native WebApp client library
406 lines (383 loc) • 18 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 v1 = !/\/2\.\d\.\d/i.test(navigator.userAgent);
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 (!v1 && propKey === 'shareFile') {
propKey = 'share';
args = [{ 'url': args[0] }];
}
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]);
if (v1) {
target.postMessage(JSON.stringify({ 'listening': args[0] }));
return;
}
}
const id = 'req-' + (requestId++) + '-' + Date.now();
if (v1) {
return new Promise((resolve, reject) => {
requests[id] = new AppRequest(id, propKey, resolve, reject);
target.postMessage(JSON.stringify({ 'id': id, 'method': propKey, 'params': args }));
});
} else if (!isAndroid) {
return target.postMessage(JSON.stringify({ method: propKey, params: args })).then((result) => {
if (result && result.compatError) {
throw result.compatError;
} else {
return result;
}
});
} 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));
});
}
}
};
},
});
const 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;
});
};
// Bypass native browser functionalities
if (v1) {
window.alert = window.App.alert;
window.confirm = (message) => {
let result;
const promise = window.App.confirm(message);
promise.then((res) => result = res);
while (result === undefined) {
//
}
return result;
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.print = () => window.App.printHtml(document.documentElement.outerHTML);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.setAppBadge = window.App.setAppBadge;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.clearAppBadge = window.App.clearAppBadge;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.vibrate = window.App.vibrate;
if (!('share' in navigator)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.share = (data) => window.App.share(data?.text ?? data?.url);
}
if (!('permissions' in navigator)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.permissions = {};
}
navigator.permissions.query = window.App.hasPermission;
if (!('clipboard' in navigator)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.clipboard = {};
}
navigator.clipboard.readText = window.App.paste;
navigator.clipboard.writeText = window.App.copy;
if (!('geolocation' in navigator)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigator.geolocation = {};
}
const positionCallbacks: (PositionCallback | undefined)[] = [];
window.App.on('position', (location) => {
if (positionCallbacks.length === 0) {
window.App.watchPosition(false, undefined);
} else {
positionCallbacks.forEach((callback) => {
if (callback) {
callback({
timestamp: location.time,
coords: {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
altitude: location.altitude,
altitudeAccuracy: location.headingAccuracy,
heading: location.heading,
speed: location.speed,
},
});
}
});
}
});
navigator.geolocation.watchPosition = (callback, errorCallback, options) => {
const watchId = positionCallbacks.length;
let hasListeners = false;
positionCallbacks.forEach((element) => {
if (element) {
hasListeners = true;
}
});
positionCallbacks[watchId] = callback;
if (!hasListeners) {
window.App.watchPosition(true, options).then((succeed) => {
if (!succeed && errorCallback) {
errorCallback({
code: 0,
message: 'Unable to start service',
PERMISSION_DENIED: 1,
POSITION_UNAVAILABLE: 2,
TIMEOUT: 3,
});
}
}).catch((error) => {
if (errorCallback) {
let code = 0;
switch (error.code) {
case 'PERMISSION_DENIED': code = 1; break;
case 'POSITION_UNAVAILABLE': code = 2; break;
case 'TIMEOUT': code = 3; break;
}
errorCallback({
code: code,
message: error.message,
PERMISSION_DENIED: 1,
POSITION_UNAVAILABLE: 2,
TIMEOUT: 3,
});
}
});
}
return watchId;
};
navigator.geolocation.clearWatch = (watchId) => {
positionCallbacks[watchId] = undefined;
let hasListeners = false;
positionCallbacks.forEach((element) => {
if (element) {
hasListeners = true;
}
});
if (!hasListeners) {
window.App.watchPosition(false, undefined);
}
};
navigator.geolocation.getCurrentPosition = (callback, errorCallback, settings) => {
window.App.getCurrentPosition(settings).then((location) => callback({
timestamp: location.time,
coords: {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
altitude: location.altitude,
altitudeAccuracy: location.headingAccuracy,
heading: location.heading,
speed: location.speed,
},
})).catch((error) => {
if (errorCallback) {
let code = 0;
switch (error.code) {
case 'PERMISSION_DENIED': code = 1; break;
case 'POSITION_UNAVAILABLE': code = 2; break;
case 'TIMEOUT': code = 3; break;
}
errorCallback({
code: code,
message: error.message,
PERMISSION_DENIED: 1,
POSITION_UNAVAILABLE: 2,
TIMEOUT: 3,
});
}
});
};
if (!('Notification' in window)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Notification = {
constuct(title, options) {
window.App.alert(title)
}
};
}
// eslint-disable-next-line max-len
window.Notification.requestPermission = () => window.App.requestNotificationPermission().then((allowed) => allowed ? 'granted' : 'denied');
window.select = () => new Promise((resolve, reject) => resolve([]))
} else 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;
window.select = select
} 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 = select
}
}
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;