expo-realtime-maps-navigation
Version:
JavaScript-pure React Native navigation package with Google Places + HERE Routing APIs, dual maps support, and complete customization - No native modules required!
291 lines (290 loc) • 11.1 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityUtils = exports.useSecureStorage = exports.HereSecureStorage = exports.secureStorage = void 0;
const react_native_1 = require("react-native");
class SecureStorageManager {
constructor() {
this.keychain = null;
this.isAvailable = false;
this.initialize();
}
static getInstance() {
if (!SecureStorageManager.instance) {
SecureStorageManager.instance = new SecureStorageManager();
}
return SecureStorageManager.instance;
}
async initialize() {
try {
if (react_native_1.Platform.OS === 'ios' || react_native_1.Platform.OS === 'android') {
const Keychain = await Promise.resolve().then(() => __importStar(require('react-native-keychain')));
this.keychain = Keychain.default || Keychain;
this.isAvailable = true;
}
else {
this.isAvailable = typeof window !== 'undefined' && !!window.localStorage;
}
}
catch (error) {
console.warn('SecureStorage: Keychain not available, falling back to basic storage');
this.isAvailable = false;
}
}
async isSecureStorageAvailable() {
if (!this.isAvailable) {
await this.initialize();
}
return this.isAvailable;
}
async storeCredentials(service, credentials, options) {
try {
const entry = {
value: JSON.stringify(credentials),
timestamp: Date.now(),
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
};
if (this.keychain) {
const result = await this.keychain.setInternetCredentials(service, credentials.apiKey, JSON.stringify(entry), {
accessControl: this.keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
authenticationType: this.keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS,
accessGroup: options?.accessGroup,
...options,
});
return result !== false;
}
else if (react_native_1.Platform.OS === 'web') {
const encrypted = await this.encryptForWeb(JSON.stringify(entry));
localStorage.setItem(`secure_${service}`, encrypted);
return true;
}
else {
throw new Error('Secure storage not available');
}
}
catch (error) {
console.error('SecureStorage: Failed to store credentials:', error);
return false;
}
}
async getCredentials(service, options) {
try {
let storedData = null;
if (this.keychain) {
const result = await this.keychain.getInternetCredentials(service, {
authenticationPrompt: {
title: 'Access HERE Navigation',
subtitle: 'Authenticate to access navigation services',
description: 'This app uses HERE SDK for navigation',
fallbackLabel: 'Use passcode',
cancelLabel: 'Cancel',
...options,
},
});
if (result && result.password) {
storedData = result.password;
}
}
else if (react_native_1.Platform.OS === 'web') {
const encrypted = localStorage.getItem(`secure_${service}`);
if (encrypted) {
storedData = await this.decryptForWeb(encrypted);
}
}
if (!storedData) {
return null;
}
const entry = JSON.parse(storedData);
if (entry.expiresAt && Date.now() > entry.expiresAt) {
await this.removeCredentials(service);
return null;
}
return JSON.parse(entry.value);
}
catch (error) {
console.error('SecureStorage: Failed to get credentials:', error);
return null;
}
}
async removeCredentials(service) {
try {
if (this.keychain) {
await this.keychain.resetInternetCredentials(service);
return true;
}
else if (react_native_1.Platform.OS === 'web') {
localStorage.removeItem(`secure_${service}`);
return true;
}
return false;
}
catch (error) {
console.error('SecureStorage: Failed to remove credentials:', error);
return false;
}
}
async updateCredentials(service, credentials, options) {
try {
const existing = await this.getCredentials(service, options);
if (!existing) {
throw new Error('No existing credentials found');
}
const updated = {
...existing,
...credentials,
};
return await this.storeCredentials(service, updated, options);
}
catch (error) {
console.error('SecureStorage: Failed to update credentials:', error);
return false;
}
}
async rotateApiKey(service, newApiKey, options) {
return await this.updateCredentials(service, { apiKey: newApiKey }, options);
}
async validateCredentials(service) {
try {
const credentials = await this.getCredentials(service);
if (!credentials || !credentials.apiKey) {
return false;
}
if (credentials.apiKey.length < 10) {
return false;
}
return true;
}
catch (error) {
console.error('SecureStorage: Failed to validate credentials:', error);
return false;
}
}
async encryptForWeb(data) {
try {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
if (crypto.subtle) {
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, [
'encrypt',
'decrypt',
]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, dataBuffer);
return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}
else {
return btoa(data);
}
}
catch (error) {
console.warn('Web encryption failed, using base64:', error);
return btoa(data);
}
}
async decryptForWeb(encryptedData) {
try {
return atob(encryptedData);
}
catch (error) {
console.error('Web decryption failed:', error);
throw error;
}
}
}
exports.secureStorage = SecureStorageManager.getInstance();
exports.HereSecureStorage = {
async storeHereCredentials(credentials, options) {
return exports.secureStorage.storeCredentials('com.here.sdk', credentials, options);
},
async getHereCredentials(options) {
return exports.secureStorage.getCredentials('com.here.sdk', options);
},
async removeHereCredentials() {
return exports.secureStorage.removeCredentials('com.here.sdk');
},
async updateHereCredentials(credentials, options) {
return exports.secureStorage.updateCredentials('com.here.sdk', credentials, options);
},
async rotateHereApiKey(newApiKey, options) {
return exports.secureStorage.rotateApiKey('com.here.sdk', newApiKey, options);
},
async validateHereCredentials() {
return exports.secureStorage.validateCredentials('com.here.sdk');
},
async isHereCredentialsStored() {
const credentials = await exports.secureStorage.getCredentials('com.here.sdk');
return credentials !== null;
},
};
const useSecureStorage = () => {
return {
secureStorage: exports.secureStorage,
HereSecureStorage: exports.HereSecureStorage,
isAvailable: exports.secureStorage.isSecureStorageAvailable(),
};
};
exports.useSecureStorage = useSecureStorage;
exports.SecurityUtils = {
generateSecureToken(length = 32) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
const array = new Uint32Array(length);
crypto.getRandomValues(array);
for (let i = 0; i < length; i++) {
result += chars[array[i] % chars.length];
}
}
else {
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
}
return result;
},
validateApiKeyFormat(apiKey) {
return apiKey.length >= 10 && /^[A-Za-z0-9_-]+$/.test(apiKey);
},
maskSensitiveData(data, visibleChars = 4) {
if (data.length <= visibleChars * 2) {
return '*'.repeat(data.length);
}
const start = data.substring(0, visibleChars);
const end = data.substring(data.length - visibleChars);
const middle = '*'.repeat(data.length - visibleChars * 2);
return `${start}${middle}${end}`;
},
};
exports.default = exports.secureStorage;
;