Friday, January 13, 2012

Впишување во локална датотека од Firefox

Пред извесно време ми се појави потреба нашата апликација Infomatrix, и нејзиниот помал брат, Бизнис Херој, да впишуваат во датотеки кои се на локалниот фајл-систем на клиентот. Како што е познато, технологијата која ја користиме во Infoproject е потполно web-базирана, т.е. апликациите работат потполно и исклучиво во веб прелистувач (Firefox или Chrome/Chromium, засега без Internet Explorer).
Конкретно, датотеките кои ги впишуваме се потребни за да бидат преземени од соодветниот драјвер и отпечатени на фискален или етикетен принтер споени на COM (RS232, т.н. „сериска“) порта.


Претходни решенија

Има различни техники да се постигне ова, а онаа која и ние ја користевме до неодамна е „заобиколна“: датотеката се генерира од заднинскиот систем и се впишува во фолдер кој е деллив преку Samba (од Linux OS). На клиентскиот компјутер е стартуван мал програм кој „клечи“ во system tray-от и постојано прави polling на споделениот фолдер. Доколку таму се појави датотека, таа се презема и се копира локално, од каде што се печати на фискалниот/етикетниот принтер.
Оваа техника има неколку проблеми:
Работите се комплицираат ако постојат повеќе корисници со фискални/лабел принтери кои истовремено работат - мора да се вгради логика за препознавање на корисникот (субфолдер и слично) и типот на принтерот.
Најголемиот проблем е ако софтверот се сервира како сервис, SaaS - што е случај кај нас, особено со Бизнис Херој. Делењето на серверски фолдери со Samba преку интернет е потенцијално голем сигурносен проблем, на страна ако имате сервис сличен на Бизнис Херој, со претпоставени стотици корисници.

XPCOM компонентите на Mozilla

Решението кое овде е опишано, засега, функционира само во Mozilla Firefox, па корисникот е задолжен да го употребува исклучиво овој прелистувач ако сака да печати локално.
Основната техника на користењето на XPCOM компонентите на Mozilla, своевиден пандан на COM (Component Object Model) на Microsoft. Датотечниот објект е nsIFile, проширен на nsILocalFile.
Користењето на API-то е опишано во Mozilla Developer Network (MDN), во (соодветно наречениот) текст File I/O.
За да ви се олесни животот, програмерот кој се нарекува MonkeeSage (неговиот блог) го изработил wrapper-от io.js, мало JS библиотекче кое го поедноставува повикувањето на сите функционалности со едноставни методи OpenFile, write, delete, итн.

Елевација на привилегии, бррр

Сѐ би било навистина едноставно кога работите би завршиле тука: има API, има wrapper, седни и напиши код! Но, познато е дека сите интернет прелистувачи се sandbox-ирани, т.е. забранет им е пристапот до локалната машина, а особено впишување во локалниот фајл систем. Засега, Firefox може да чита локални датотеки, a Chrome не (или јас не сум свесен дека може). Internet Explorer го користи Scripting.FileSystemObject, но тоа е специфично за него и има многу сигурносни проблеми.
Иако има најави дека ова што сега ќе го опишам всушност ќе биде имплементирано во некоја од следните верзии на Chrome и Firefox, во FileIO интерфејсот, засега мораме да се послужиме со соодветен хак и рачно да ги подигнеме привилегиите на прелистувачот, за да може да впишува локално.

Значи, повторувам уште еднаш, се работи за малку „неортодоксно“ решение, и оние со послабо срце можеби ќе сакаат да го прескокнат. Но, ако се имплементира исправно, сигурносните проблеми се минимални, а за возврат се добива елегантно решение кое работи...

Значи, во адресната линија на Firefox впишете about:config, и откако ќе се заколнете дека ќе бидете внимателни, ќе ја добиете листата на конфигурациските поставки.
Во полето Filter впишете codebase_principal_support и стиснете ентер. Ќе добиете една линија на булова променлива, како на сликава:

Направете double-click на линијата за да ја поставите вредноста на true. 
При првиот обид да го користите API-то, Firefox ќе скокне со предупредувачка порака дека она што го барате не е безбедно:



Кликнете на Allow, но претходно чекирајте го боксот „Remember this decision“ за да не ве гњави повеќе.
Толку. Привилегиите се подигнати, коцката е фрлена.

Обезбедување од проблеми

Сепак, постои спас! Имаме можност привилегиите да ги ограничиме само на овие за впишување на локална датотека и со тоа значително, ако не и потполно да го отстраниме ризикот. Читајте понатаму...
За да се направи ограничувањето, во локалниот фајл систем најдете ја датотеката prefs.js која постои во default корисничкиот профил на Firefox. Отприлика, на Windows 7, оваа датотека ќе ја најдете во C:\Users\<korisnik>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile_id>.default, а во XP во C:\Documents and Settings\<korisnik>\Application Data\Mozilla\Firefox\Profiles\<profile_id>.default
Отворете ја дотичната датотека prefs.js и најдете ги линиите кои го содржат стрингот capability.principal.codebase
Сите овие стрингови треба да се променат во capability.principal.codebaseTrusted (значи, додадете Trusted на крајот). Еве и слика како отприлика изгледа ова:

Пример на употребен JavaScript

Еве извадок од кодот кој ние го користиме во апликацијата (поедноставен заради читливост), конкретно за касата Bravo на Дуна Компјутери. Ја користиме библиотеката mootools

Проверка дали се елевирани привилегиите:

try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    var ret_status = fiskalna_pecati_smetka_duna_bravo(stavki, storno);
    return ret_status;
}
catch(err) {
    alert(dict_fisk.err_nepravilna_instalacija+'\n'+err.message);
    return false;
}

Впишување во датотеката:


var file_name = storno == 0 ? 'C:\\bravo\\smetka.txt' : 'C:\\bravo\\storno.txt';
var file_out = FileIO.open(file_name);


var fisk_stavki = '';
stavki.each(function(item, index) {
    if (nz(item.kolicina, 0) > 0 && nz(item.cena_prodazna_so_danok, 0) > 0)
        var naziv      = nz(item.katalog_naziv, 'Produkt');
        var ddv_stapka = nz(item.danok_osnoven, 18);
        var ddv_oznaka = '';
        var cena       = roundNumber(item.cena_prodazna_so_danok, 1);
        if (cena.toString().indexOf('.') == -1)
            cena = cena.toString()+'.0';

        if (ddv_stapka == 18) ddv_oznaka = 'A';
        if (ddv_stapka == 5)  ddv_oznaka = 'B';
        if (ddv_stapka == 0)  ddv_oznaka = 'C';

        fisk_stavki += naziv+';'+item.kolicina+';'+ddv_oznaka+';'+cena+'\n';
});

var file_write = FileIO.write(file_out, fisk_stavki, 'w', 'UTF-8');

if (file_write == false) {
    alert(dict_fisk.err_ne_vpisuva_datoteka+'\n'+err.message);
    return false;
}

Имплементациски напомени

Практичната имплементација, со фискалната каса Браво на Дуна Компјутери, има уште една дрангулија - мониторско програмче кое „гледа“ кога ќе ја впишеме датотеката со ставките во фолдер и го повикува драјверот за касата Bravo.exe. Ние го користиме DirectoryMonitor. Бесплатен е, не зема многу ресурси и лесно се поставува. Секако поставете го флегот за автоматско стартување заедно со Windows.
Не заборавајте дека, конкретно за касата Браво, системот мора да има поставен македонски локал, но броевите и валутите да бидат со децимална точка, наместо со запирка (изгубив попладне додека го сфатив ова).

За да не одиме кај корисниците, обично користиме TeamViewer или нешто слично за да ги изведеме опишаните чекори. 

Иако не сум пробал, овој код би требало да може да впишува директно на некоја COM порта, бидејќи во Windows тие се третираат како фајлови. Можеби, дури и на LPT.
Ако некој проба, нека јави за резултатот во коментарите...


2 comments:

  1. Данке бите за споделувањето. Врло корисно за оние што се ориентираат на WEB апликации.

    ReplyDelete
  2. Одлично. Ме радува што има креативни решенија на нетипични проблеми

    ReplyDelete