ext.mjs raw

   1  // ext.mjs — Platform-adaptive extension/signer bridge.
   2  // Detects whether it is running in a browser extension background page
   3  // or in a regular page context and uses the appropriate APIs.
   4  //
   5  // Extension:  browser.storage.local, browser.runtime.onMessage
   6  // In-page:    localStorage (prefixed), direct handler dispatch
   7  
   8  var _isExt = typeof browser !== 'undefined'
   9    && !!browser.runtime && !!browser.runtime.onMessage;
  10  
  11  var _prefix = 'smesh-signer:';
  12  var _handler = null;
  13  var _readyResolve = null;
  14  var _ready = new Promise(function(r) { _readyResolve = r; });
  15  
  16  function _signalReady() {
  17    if (_readyResolve) { _readyResolve(); _readyResolve = null; }
  18  }
  19  
  20  // --- Storage ---
  21  
  22  export function StorageGet(key, fn) {
  23    if (_isExt) {
  24      browser.storage.local.get(key).then(function(result) {
  25        fn(result[key] || '');
  26      }).catch(function() { fn(''); });
  27    } else {
  28      fn(localStorage.getItem(_prefix + key) || '');
  29    }
  30  }
  31  
  32  export function StorageSet(key, value) {
  33    if (_isExt) {
  34      browser.storage.local.set({ [key]: value });
  35    } else {
  36      localStorage.setItem(_prefix + key, value);
  37    }
  38  }
  39  
  40  export function StorageRemove(key) {
  41    if (_isExt) {
  42      browser.storage.local.remove(key);
  43    } else {
  44      localStorage.removeItem(_prefix + key);
  45    }
  46  }
  47  
  48  // --- Message handling ---
  49  
  50  // OnMessage registers a handler for incoming requests.
  51  // fn receives (method, paramsJSON, senderTabID, respond).
  52  export function OnMessage(fn) {
  53    if (_isExt) {
  54      browser.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
  55        var method = msg.method || '';
  56        var params = msg.params ? JSON.stringify(msg.params) : '{}';
  57        var tabID = (sender.tab && sender.tab.id) || 0;
  58        function respond(result) {
  59          try {
  60            var parsed = JSON.parse(result);
  61            if (parsed.error) {
  62              sendResponse({ error: parsed.error });
  63            } else {
  64              sendResponse(parsed.result !== undefined ? parsed.result : parsed);
  65            }
  66          } catch (e) {
  67            sendResponse(result);
  68          }
  69        }
  70        try {
  71          fn(method, params, tabID, respond);
  72        } catch (e) {
  73          console.error('smesh handler error:', e);
  74          sendResponse({ error: e.message || 'handler error' });
  75        }
  76        return true;
  77      });
  78    } else {
  79      _handler = fn;
  80      _signalReady();
  81    }
  82  }
  83  
  84  // dispatch calls the registered handler directly (in-page path).
  85  // In extension context this is unused — messages arrive via browser.runtime.
  86  export function dispatch(method, params) {
  87    return _ready.then(function() {
  88      return new Promise(function(resolve) {
  89        if (!_handler) { resolve(null); return; }
  90        var paramsJSON = JSON.stringify(params || {});
  91        _handler(method, paramsJSON, 0, function(result) {
  92          try {
  93            var parsed = JSON.parse('' + result);
  94            if (parsed.error) {
  95              resolve({ error: parsed.error });
  96            } else {
  97              resolve(parsed.result !== undefined ? parsed.result : parsed);
  98            }
  99          } catch (e) {
 100            resolve('' + result);
 101          }
 102        });
 103      });
 104    });
 105  }
 106  
 107  // --- Tab messaging (extension only, no-op in-page) ---
 108  
 109  export function SendMessageToTab(tabID, msg) {
 110    if (!_isExt) return;
 111    try {
 112      browser.tabs.sendMessage(tabID, JSON.parse(msg));
 113    } catch (e) {
 114      browser.tabs.sendMessage(tabID, { data: msg });
 115    }
 116  }
 117  
 118  export function ConsoleLog(msg) {
 119    console.log('[smesh]', '' + msg);
 120    if (!_isExt) return;
 121    try {
 122      browser.tabs.query({ active: true, currentWindow: true }).then(function(tabs) {
 123        if (tabs.length > 0) {
 124          browser.tabs.sendMessage(tabs[0].id, { type: 'LOG', text: msg }).catch(function() {});
 125        }
 126      }).catch(function() {});
 127    } catch(e) {}
 128  }
 129  
 130  export function GetActiveTab(fn) {
 131    if (_isExt) {
 132      browser.tabs.query({ active: true, currentWindow: true }).then(function(tabs) {
 133        if (tabs.length > 0) {
 134          fn(tabs[0].id, tabs[0].url || '');
 135        } else {
 136          fn(0, '');
 137        }
 138      }).catch(function() { fn(0, ''); });
 139    } else {
 140      fn(0, '');
 141    }
 142  }
 143  
 144  export function SessionGet(key, fn) {
 145    try {
 146      fn(sessionStorage.getItem(_prefix + key) || '');
 147    } catch(e) {
 148      fn('');
 149    }
 150  }
 151  
 152  export function SessionSet(key, value) {
 153    try {
 154      sessionStorage.setItem(_prefix + key, '' + value);
 155    } catch(e) {}
 156  }
 157  
 158  export function IsInPage() {
 159    return !_isExt;
 160  }
 161