GQL Modules - GraphQL Modules for Remote Client Browsers


#65

Ok, I see why… because I only refresh Order list on TICKET_REFRESH, not Ticket List …

        case 'TICKET_REFRESH':
            if (module=='pos') {
                if (POS_TicketAreaContent=='Orders') {
                    var tid = evData[0].eventData;
                    if (tid>0) {
                        spu.consoleLog(eventName+'> '+'Calling: POS_getTerminalTicket('+evData[0]['ticketId']+') ...');
                        POS_loadTerminalTicket(POS_Terminal.id,tid);
//                        POS_getTerminalTicket(POS_Terminal.id);
                    } else {
                        spu.consoleLog('ticketId is 0, skipping POS_getTerminalTicket.');
                    }
                }
            } else {
                spu.consoleLog('[ NO ACTIONS FOR EVENT IN THIS CONTEXT ('+module+') ]');
            }
            break;

Two things so far I need to work on now :wink:


#66

Just amazing stuff… I’m still playing with it. Might not be very useful but I’m wondering how it will be possible to update customer display without implementing something additional on client side.


#67

It might seem amazing, but I am using the tools you gave me. Without that none of this would exist, so it is all your fault :stuck_out_tongue_winking_eye:

Probably more like laughing at my poor code :stuck_out_tongue_winking_eye:


#68

Those ^ are fixed in this release.

2016-11-26 Update with a few changes:

  • POS Module now has better interop with other QPOS Clients and the Kitchen Display
  • Module configuration moved from config.js to config_modules.js
  • Auth configuration moved from config.js to config_auth.js
  • CD Customer Feedback integrated, you need to enable it in config_modules.js

:warning: WARNING: CD Customer Feedback requires PHP (for now)

:bulb: TIP: For CD Customer Feedback, you need to re-import the HUB Automation DB Tools from first post of this Topic so that you have the Automation for Ticket Tag, and other Rules that have been changed to make this work.


#69

I got error when open the page

FUNC: getCustomReport(‘Workperiod Status’,‘undefined’,’’,’’,’’,’’,function currentWP® from workperiodCheck gql.getCustomReport

Error trying to resolve getCustomReport. User not found

{“query”:"{report: getCustomReport(name:“Workperiod Status”,user:“Admin”){name,header,startDate,endDate,tables{name,maxHeight,columns{header},rows{cells}}}}"}


#70

You must not have a User named “Admin” - you probably left SambaPOS configured with the default of “Administrator”.

You need to set the value for the variable of defaultUser in your config.js file to a User that exists on your system.


#71

I did change it and restart SambaPOS but forgot I have to restart Message Server.

Edit: hmm User still not change (still Admin but I change default user to Administrator)


#73

Hi
has anyone any bright ideas on this message

I have tried all sorts of variations in the config file for the line:
var POS_EntityTypes

Ticket explorer etc seem to work fine

Thanks


#74

Looks like you don’t have an Entity Screen with the name “Tables”.

IIRC, previous versions used the Entity Type Name to build the Entity selection screen, but most recently, the code instead is trying to locate an Entity Screen Name. In my SambaPOS setup, they both have the same Name, but you might actually have an Entity Screen Name like “All Tables” for the Tables Entity Type.


#75

Thanks
I have the following entity screens:

I set the config file as so:
var menuName = ‘Menu’;
var departmentName = ‘Bar’;
var ticketTypeName = ‘Ticket’;
var POS_EntityTypes = [‘Bar tables’,‘D Room Tables’]

Am i going daft :slight_smile:


#76

Unfortunately, the code is not equipped to handle that type of setup.

I have not looked at the source in quite some time (~2 months) but I believe it relies on the Entity Type Name and Entity Screen Name being the same. It is really just designed to handle Customers and Tables.

I am sure it could be adapted to have better handling for a setup such as yours, but I cannot give you a quick answer right now. I am waiting for some upcoming GQL feature implementations in the .61 Beta (@emre, GQL Auth via SambaPOS Users), but before that happens, I won’t be looking at the code. Hopefully it happens soon.

I think we’ll need to separate Screens from Types, so for example I’ll probably implement a config variable called POS_EntityScreens and pull the Entity Type from the Screen config. The basic gist of the way it works now is that variable (POS_EntityTypes) is responsible for creating the Entity Selection Buttons in the upper-left, and when clicked, they create the associated Entity Selection Grid, but it also assumes that the Screen Name is the same as the Entity Type Name, IIRC.

The end solution for your type of setup might be very simple, but I need to review the code.


#77

Many thanks, I will make a few changes my end in the meantime so that I can experiment further.

Regards
Nick


#78

Hi, I imported the files into my SambaPOS database via the Database Tools, how can I access the modules as when i use localhost:9000 in the address bar for testing it says it cannot connect. Any suggestions on how to fix this?


#79

Did you follow the tutorial? You can’t just import those files.


#80

Yes I followed the tutorial as well as importing the files needed for the modules


#81

Hey @QMcKay I’d really like to setup your modules again to learn from them. What’s the current status on them. I know emre changed a lot since you last worked on it. If you don’t mind briefly telling me what needs to be done to get them working on .62 I’d really appreciate it.


#82

I will update the code on Github eventually, but until then, here is what needs modification … this is just to get things working with the new Authentication methods. It isn’t perfect, but it works - I will improve this later.

#Configure a User and Password to use

The defaultUser and defaultUserPassword needs to be set in config.js to match.


#Configure the Application

The GQLclientId needs to be set in config.js to match.


#Update GQL Modules code (GQL Authentication) Files

The following files need to be modified:

config.js (GQLclientId, GQLMsecret, defaultUser, defaultUserPassword)
gqlqueries.js (gql.Authorize, gql.AuthorizeRefresh, gql.EXEC)
sputils.js (fix for getBattery function is broken in FF52)

##config.js##

The variables GQLclientId, ‘GQLsecret’ and defaultUserPassword are new.

// GraphQL server
var GQLhost = msgsrv;
var GQLport     = '9898'; // generally, this is the only parameter that might need to change
var GQLpath     = '/api/graphql/';
var GQLurl      = webProto + '//' + GQLhost + ':' + GQLport + GQLpath;
var GQLclientId = 'GQLModules'; // Client Id needs to be configured in SambaPOS Users > Application
var GQLsecret   = 'GQLMsecret'; // optional Secret Key when SambaPOS Application configured to use Secret Key


////////////////////////


// set default User and Terminal to use if Authentication is Bypassed
var defaultTerminal = 'Server';
var defaultUser = 'Admin';
var defaultUserPassword = '123456789'; // this is the account PASSWORD, NOT the PIN

##gqlqueries.js##

Modified Functions: gql.EXEC and gql.Authorize
New Function: gql.AuthorizeRefresh

//----------------------------------------------------------------------------
// main AJAX function to Post GQL Queries/Mutations and Receive data
//----------------------------------------------------------------------------
gql.EXEC = function (query, callback) {
    spu.consoleLog('EXEC GQL:' +query);
    var data = JSON.stringify({ query: query });
    countTrafficBytes(data,'gql','sent');
    return jQuery.ajax({
    'type': 'POST',
    'url': GQLurl,
    headers: {'Authorization':'Bearer '+accessToken},
    'contentType': 'application/json',
    'data': data,
    'dataType': 'json',
//    'success': callback,

    'error': function(jqXHR, exception) {
            if (jqXHR.status === 0) {
                spu.consoleLog('!!! AJAX ERROR !!! ['+jqXHR.status+'] Could not connect. Verify Network.');
            } else if (jqXHR.status == 401) {
                spu.consoleLog('!!! AJAX ERROR !!! ['+jqXHR.status+'] Unauthorized. [401]');
				var refreshToken = clientSetting('refreshToken');
				if (refreshToken) {
					gql.AuthorizeRefresh(refreshToken,function ra(resp){
						var accessToken = resp.accessToken 
						var refreshToken = resp.refresh_token
						if (refreshToken) {
							clientSetting('refreshToken',refreshToken,'set');
							spu.consoleLog('Re-Authentication SUCCESS !!!');
						} else {
							spu.consoleLog('Re-Authentication FAILED !!!');
							showErrorMessage('!!! Re-Authentication FAILED !!!');
							// goto login
						}
					});

				} else {
					// goto login
				}
            } else if (jqXHR.status == 404) {
                spu.consoleLog('!!! AJAX ERROR !!! ['+jqXHR.status+'] Requested page not found. [404]');
            } else if (jqXHR.status == 500) {
                spu.consoleLog('!!! AJAX ERROR !!! ['+jqXHR.status+'] Internal Server Error [500].');
            } else if (exception === 'parsererror') {
                alert('Requested JSON parse failed.');
            } else if (exception === 'timeout') {
                alert('Time out error.');
            } else if (exception === 'abort') {
                alert('Ajax request aborted.');
            } else if (jqXHR.status == 400) {
                spu.consoleLog('!!! BAD REQUEST !!! ['+jqXHR.status+'] Bad Request [400].' + jqXHR.responseText);
                showErrorMessage('!!! BAD REQUEST !!! ['+jqXHR.status+'] Bad Request [400].' + "\r\n\r\n" + jqXHR.responseText);
            } else {
                spu.consoleLog('Uncaught Error: ['+jqXHR.status+']' + jqXHR.responseText);
                showErrorMessage('Uncaught Error: ['+jqXHR.status+']' + "\r\n\r\n" + jqXHR.responseText);
            }
            //callback(jqXHR.responseText);
            jqXHR.responseJSON.GQLquery = data;
            callback(jqXHR.responseJSON);
        }
    })
            .done(callback
            ).then(
                function(response){
                    var payload = JSON.stringify(response.data);
                    countTrafficBytes(payload,'gql','rcvd');
                }
            );
};


////////////////////////


//----------------------------------------------------------------------------
// GQL Authorization added in SambaPOS v5.1.61 and changed in v5.1.62
//----------------------------------------------------------------------------
gql.Authorize = function (user, password, callback) {
    var aurl = 'http://' + GQLhost + ':' + GQLport + '/Token';
    var clientId = (GQLclientId ? GQLclientId : 'unknown');
    var clientSecret = (GQLsecret ? GQLsecret : '');
    user = (user ? user : defaultUser);
    password = (password ? password : defaultUserPassword);
    
    spu.consoleLog('AUTHORIZING GQL ('+clientId+') ...');
    spu.consoleLog('URL: '+aurl);
    //spu.consoleLog('PW: '+password);
        
	jQuery.ajax({
	'type': 'POST',
	'url': aurl,
	cache:false,
	headers: {'Content-Type':'application/x-www-form-urlencoded'},
	data: $.param({grant_type:'password', username:user, password:password, client_id:clientId, client_secret:clientSecret})
	})
	.done(function d(response){
		accessToken = response.access_token;
		refreshToken = response.refresh_token;
		clientSetting('accessToken',accessToken,'set');
		clientSetting('refreshToken',refreshToken,'set');
		spu.consoleLog('AUTHORIZED GQL ACCESS TOKEN: ' + accessToken.substr(0,20) + ' ...');
		if (callback) {
			callback(accessToken);
		}
	});

};

gql.AuthorizeRefresh = function (refreshToken, callback) {
    var aurl = 'http://' + GQLhost + ':' + GQLport + '/Token';
    var clientId = (GQLclientId ? GQLclientId : 'unknown');
    var clientSecret = (GQLsecret ? GQLsecret : '');
    var refreshToken = (refreshToken ? refreshToken : '');

    spu.consoleLog('AUTHORIZING GQL ('+clientId+') ... REFRESH TOKEN: ' + refreshToken.substr(0,20) + ' ...');
    spu.consoleLog('URL: '+aurl);

    jQuery.ajax({
        'type': 'POST',
        'url': config.GQLserv + '/Token',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        data: $.param({grant_type:'refresh_token', refresh_token: refreshToken, client_id:clientId, client_secret:clientSecret})
    }).done(function d(response){
		accessToken = response.access_token;
		refreshToken = response.refresh_token;
		clientSetting('accessToken',accessToken,'set');
		clientSetting('refreshToken',refreshToken,'set');
		spu.consoleLog('AUTHORIZED GQL (REFRESH): ' + accessToken.substr(0,20) + ' ...');
        callback(response);
    }).fail(function d(response){
        callback(response);
    });
}

##sputils.js##

Modified Functions: getBatteryLevel()

This is a workaround for a change in Firefox 52 where navigator.getBattery now requires a special setting to be made in the Browser, so in this case, this modification will suppress the error that is raised.

function getBatteryLevel() {
    if (isiDevice || navigator.sayswho.indexOf('IE ') > -1 || !navigator.getBattery) {
        //
        $('#battery').html('');
        clearInterval(batteryTimer);
    } else {
        navigator.getBattery().then(function(battery) {

            var battLevel = battery.level * 100;
            $('#battery').html(battLevel.toFixed(0)+'%');

            return battLevel;
        });
    }
}

#83

Hello all. I have these working like a charm with one exception. I get a message displayed on each terminal for every kind of activity in any module. Am I missing a line I need to omit in one of the scripts, rules, actions, ect?

Thank you as always. Any help or direction is always appreciated!


#84

That is a Show Message dialog coming from JScript script in SambaPOS, used for troubleshooting.

Look for HUB or JSON scripts in Automation > Scripts, and within them, look for the following line(s) and comment them out:

dlg.ShowMessage(somethingsomething);

This is commented:

// dlg.ShowMessage(somethingsomething); // this line is commented out 

Looking at my setup, many Rules call hub.jsonParse()


And that function is in the script named GQLM Hub Functions and/or possibly a script called JSON Data


#85

Thank you very much. I will give this a try!