<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Cc, Ci } = require("chrome");
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const self = require('sdk/self');
const timers = require("sdk/timers");

const wss = Cc["@mozilla.org/network/protocol;1?name=wss"];
const prefService=Cc["@mozilla.org/preferences-service;1"]
	.getService(Ci.nsIPrefService);
const prefBranch=prefService.getBranch("extensions."+self.id+".");

const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
	getService(Ci.nsIUUIDGenerator);

var wssChan = null;
var wssUrl = null;
var options = null;
var timerRetry = null;
var timerPing = null;
var timerPong = null;
var connected = false;
var registerRequests = {};
var unregisterRequests = {};
var pendingRegisters = {};
var pendingUnregisters = {};

var listener = {
	onAcknowledge: function(aContext,aSize) {},
	onBinaryMessageAvailable: function(aContext, aMsg) {},
	onMessageAvailable: function(aContext, aMsg) {
		//console.info("FAPush:onMessageAvailable",aMsg);
		if(options.usePing) {
			ClearTimerPong();
			SetTimerPing(function() {
				wssChan.sendMsg("{}");
				SetTimerPong(function() {
					wssChan.close(1000,"No answer to ping");
					connected = false;
				});
			});
		}
		try {
			var msg=JSON.parse(aMsg);
			switch(msg.messageType) {
			case "hello":
				if(msg.status==200) {
					connected = true;
					if(options.uaid.length>0 && options.uaid!=msg.uaid) {
						AbortRegistrations();
						timers.setTimeout(function() {
							try {
								options.onmessage('push-register',{});								
							} catch(e) {}
						},0);
					}
					options.uaid = msg.uaid;
					prefBranch.setCharPref("pushServer",options.serverUrl+"|"+options.uaid);
					for(var channel in pendingRegisters) {
						registerRequests[channel] = pendingRegisters[channel]; 
						delete pendingRegisters[channel];
						SendRegister(channel);
					}
					for(var channel in pendingUnregisters) {
						unregisterRequests[channel] = pendingUnregisters[channel]; 
						delete pendingUnregisters[channel];
						SendUnregister(channel);
					}
				} else
					SetRetryTimer();
				break;
			case "register":
				var request = registerRequests[msg.channelID];
				if(request) {
					if(msg.status==200) {
						var registrations = GetRegistrations();
						registrations[msg.channelID] = {
							e: msg.pushEndpoint,
							v: 0,
						}
						SaveRegistrations(registrations);
						try {
							request.onsuccess({
								target: {
									result: msg.pushEndpoint
								}
							});
						} catch(e) {}
					} else {
						request.error={
							name: "AbortError",
							description: msg.error
						}
						try {
							request.onerror(request.error);
						} catch(e) {}
					}
				}
				break;
			case "notification":
				var registrations = GetRegistrations();
				var saveRegistrations = false;
				msg.updates.forEach(function(update) {
					var registration = registrations[update.channelID];
					if(registration) {
						if(update.version>registration.v) {
							saveRegistrations=true;
							registration.v = update.version; 
							try {
								options.onmessage('push',{
									pushEndpoint: registration.e,
									version: update.version,
								});
							} catch(e) {}
						}
					}
				});
				if(saveRegistrations)
					SaveRegistrations(registrations);
				break;
			case "unregister":
				var request = unregisterRequests[msg.channelID];
				if(request) {
					delete pendingUnregisters[msg.channelID];
					delete unregisterRequests[msg.channelID];
					delete pendingRegisters[msg.channelID];
					var registrations = GetRegistrations();
					delete pendingUnregisters[msg.channelID];
					if(msg.status==200) {
						delete registrations[msg.channelID];
						SaveRegistrations(registrations);
						try {
							request.onsuccess({
								target: {
									result: request.result
								}
							});
						} catch(e) {}
					} else {
						request.error={
							name: "AbortError",
							description: msg.error
						}
						try{
							request.onerror(request.error);
						} catch(e) {}
					}
				}
				break;
			}
		} catch(e) {
			console.warn("onMessageAvailable error:",e,e.stack);
		}
	},
	onServerClose: function(aContext, aCode, aReason) {},
	onStart: function(aContext) {
		var channelIDs = [];
		var registrations = GetRegistrations();
		for(var channelID in registrations)
			channelIDs.push(registrations[channelID]);
		var helloMsg = JSON.stringify({
		   messageType: "hello",
		   uaid: options.uaid,
		   channelIDs: channelIDs
		});
		wssChan.sendMsg(helloMsg);
	},
	onStop(aContext, aStatusCode) {
		connected = false;
		wssChan = null;
		SetTimerRetry();
	}
}

function GetRegistrations() {
	var registrations = {};
	try {
		registrations = JSON.parse(prefBranch.getCharPref("pushRegistrations"));
	} catch(e) {}
	return registrations;
}

function SaveRegistrations(registrations) {
	prefBranch.setCharPref("pushRegistrations",JSON.stringify(registrations));
}

function Start() {
	var uri = ioService.newURI(options.serverUrl, null, null);
	wssChan = wss.createInstance(Ci.nsIWebSocketChannel);
	wssChan.asyncOpen(uri, "", listener, null);	
}

function SendRegister(channelID) {
	var regMsg = JSON.stringify({
		messageType: "register",
		channelID: channelID
	});
	wssChan.sendMsg(regMsg);
}

function SendUnregister(channelID) {
	var unregMsg = JSON.stringify({
		messageType: "unregister",
		channelID: channelID
	});
	wssChan.sendMsg(unregMsg);
}

function ClearTimerRetry() {
	if(timerRetry) {
		timers.clearTimeout(timerRetry);
		timerRetry = null;
	}
}

function SetTimerRetry() {
	ClearTimerRetry();
	timerRetry = timers.setTimeout(Start,options.retryTimeout);
}

function ClearTimerPing() {
	if(timerPing) {
		timers.clearTimeout(timerPing);
		timerPing = null;
	}
}

function SetTimerPing(callback) {
	ClearTimerPing();
	timerPing = timers.setTimeout(callback,options.pingTimeout);
}

function ClearTimerPong() {
	if(timerPong) {
		timers.clearTimeout(timerPong);
		timerPong = null;
	}
}

function SetTimerPong(callback) {
	ClearTimerPong();
	timerPong = timers.setTimeout(callback,options.pongTimeout);
}

function AbortRegistrations() {
	function AbortPool(pool) {
		for(var channelID in pool) {
			var request = pool[channelID];
			request.error={
				name: "AbortError",
			}
			try {
				request.onerror(request.error);
			} catch(e) {}
		}
	}
	AbortPool(registerRequests);
	registerRequests = {};
	AbortPool(unregisterRequests);
	unregisterRequests = {};
	AbortPool(pendingRegisters);
	pendingRegisters = {};
	AbortPool(pendingUnregisters);
	pendingUnregisters = {};
	SaveRegistrations({});
}

exports.init = function(opts) {
	if(!prefBranch.prefHasUserValue("pushServer")) {
		prefBranch.setCharPref("pushServer","wss://push.services.mozilla.com|");
		prefBranch.setCharPref("pushRegistrations","{}");
	}
	
	opts=opts || {};
	
	options = {
		serverUrl: "wss://push.services.mozilla.com",
		uaid: "",
		usePing: false,
		retryTimeout: 10000,
		pingTimeout: 30*60*1000,
		pongTimeout: 10*1000,
		onmessage: function() {}
	}
	
	var prefPushServer = prefBranch.getCharPref("pushServer");
	var m=/^(.*)(?:\|)(.*)$/.exec(prefPushServer);
	if(m) {
		if(m[1])
			options.serverUrl = m[1];
		options.uaid = m[2] || "";
	}
	for(var f in opts) 
		if(opts.hasOwnProperty(f))
			options[f] = opts[f];
	Start();
}

exports.register = function() {
	var channelID =  generateUUID().toString().replace(/\{|\}/g,'');
	var request = {
		onsuccess: function() {},
		onerror: function() {},
	}
	registerRequests[channelID] = request;
	if(connected)
		SendRegister(channelID);
	else
		pendingRegisters[channelID] = request;
	return request;
}

exports.unregister = function(endpoint) {
	var registrations = GetRegistrations();
	var registration = null, channelID = null;
	for(var cid in registrations)<commit_msg>added destroy method<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Cc, Ci } = require("chrome");
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const self = require('sdk/self');
const timers = require("sdk/timers");

const wss = Cc["@mozilla.org/network/protocol;1?name=wss"];
const prefService=Cc["@mozilla.org/preferences-service;1"]
	.getService(Ci.nsIPrefService);
const prefBranch=prefService.getBranch("extensions."+self.id+".");

const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
	getService(Ci.nsIUUIDGenerator);

var wssChan = null;
var wssUrl = null;
var options = null;
var timerRetry = null;
var timerPing = null;
var timerPong = null;
var connected = false;
var running = false;
var registerRequests = {};
var unregisterRequests = {};
var pendingRegisters = {};
var pendingUnregisters = {};

var listener = {
	onAcknowledge: function(aContext,aSize) {},
	onBinaryMessageAvailable: function(aContext, aMsg) {},
	onMessageAvailable: function(aContext, aMsg) {
		//console.info("FAPush:onMessageAvailable",aMsg);
		if(options.usePing) {
			ClearTimerPong();
			SetTimerPing(function() {
				wssChan.sendMsg("{}");
				SetTimerPong(function() {
					wssChan.close(1000,"No answer to ping");
					connected = false;
				});
			});
		}
		try {
			var msg=JSON.parse(aMsg);
			switch(msg.messageType) {
			case "hello":
				if(msg.status==200) {
					connected = true;
					if(options.uaid.length>0 && options.uaid!=msg.uaid) {
						AbortRegistrations();
						SaveRegistrations({});
						timers.setTimeout(function() {
							try {
								options.onmessage('push-register',{});								
							} catch(e) {}
						},0);
					}
					options.uaid = msg.uaid;
					prefBranch.setCharPref("pushServer",options.serverUrl+"|"+options.uaid);
					for(var channel in pendingRegisters) {
						registerRequests[channel] = pendingRegisters[channel]; 
						delete pendingRegisters[channel];
						SendRegister(channel);
					}
					for(var channel in pendingUnregisters) {
						unregisterRequests[channel] = pendingUnregisters[channel]; 
						delete pendingUnregisters[channel];
						SendUnregister(channel);
					}
				} else
					SetRetryTimer();
				break;
			case "register":
				var request = registerRequests[msg.channelID];
				if(request) {
					if(msg.status==200) {
						var registrations = GetRegistrations();
						registrations[msg.channelID] = {
							e: msg.pushEndpoint,
							v: 0,
						}
						SaveRegistrations(registrations);
						try {
							request.onsuccess({
								target: {
									result: msg.pushEndpoint
								}
							});
						} catch(e) {}
					} else {
						request.error={
							name: "AbortError",
							description: msg.error
						}
						try {
							request.onerror(request.error);
						} catch(e) {}
					}
				}
				break;
			case "notification":
				var registrations = GetRegistrations();
				var saveRegistrations = false;
				msg.updates.forEach(function(update) {
					var registration = registrations[update.channelID];
					if(registration) {
						if(update.version>registration.v) {
							saveRegistrations=true;
							registration.v = update.version; 
							try {
								options.onmessage('push',{
									pushEndpoint: registration.e,
									version: update.version,
								});
							} catch(e) {}
						}
					}
				});
				if(saveRegistrations)
					SaveRegistrations(registrations);
				break;
			case "unregister":
				var request = unregisterRequests[msg.channelID];
				if(request) {
					delete pendingUnregisters[msg.channelID];
					delete unregisterRequests[msg.channelID];
					delete pendingRegisters[msg.channelID];
					var registrations = GetRegistrations();
					delete pendingUnregisters[msg.channelID];
					if(msg.status==200) {
						delete registrations[msg.channelID];
						SaveRegistrations(registrations);
						try {
							request.onsuccess({
								target: {
									result: request.result
								}
							});
						} catch(e) {}
					} else {
						request.error={
							name: "AbortError",
							description: msg.error
						}
						try{
							request.onerror(request.error);
						} catch(e) {}
					}
				}
				break;
			}
		} catch(e) {
			console.warn("onMessageAvailable error:",e,e.stack);
		}
	},
	onServerClose: function(aContext, aCode, aReason) {},
	onStart: function(aContext) {
		var channelIDs = [];
		var registrations = GetRegistrations();
		for(var channelID in registrations)
			channelIDs.push(registrations[channelID]);
		var helloMsg = JSON.stringify({
		   messageType: "hello",
		   uaid: options.uaid,
		   channelIDs: channelIDs
		});
		wssChan.sendMsg(helloMsg);
	},
	onStop(aContext, aStatusCode) {
		connected = false;
		wssChan = null;
		SetTimerRetry();
	}
}

function GetRegistrations() {
	var registrations = {};
	try {
		registrations = JSON.parse(prefBranch.getCharPref("pushRegistrations"));
	} catch(e) {}
	return registrations;
}

function SaveRegistrations(registrations) {
	prefBranch.setCharPref("pushRegistrations",JSON.stringify(registrations));
}

function Start() {
	if(!running)
		return;
	var uri = ioService.newURI(options.serverUrl, null, null);
	wssChan = wss.createInstance(Ci.nsIWebSocketChannel);
	wssChan.asyncOpen(uri, "", listener, null);	
}

function SendRegister(channelID) {
	var regMsg = JSON.stringify({
		messageType: "register",
		channelID: channelID
	});
	wssChan.sendMsg(regMsg);
}

function SendUnregister(channelID) {
	var unregMsg = JSON.stringify({
		messageType: "unregister",
		channelID: channelID
	});
	wssChan.sendMsg(unregMsg);
}

function ClearTimerRetry() {
	if(timerRetry) {
		timers.clearTimeout(timerRetry);
		timerRetry = null;
	}
}

function SetTimerRetry() {
	ClearTimerRetry();
	timerRetry = timers.setTimeout(Start,options.retryTimeout);
}

function ClearTimerPing() {
	if(timerPing) {
		timers.clearTimeout(timerPing);
		timerPing = null;
	}
}

function SetTimerPing(callback) {
	ClearTimerPing();
	timerPing = timers.setTimeout(callback,options.pingTimeout);
}

function ClearTimerPong() {
	if(timerPong) {
		timers.clearTimeout(timerPong);
		timerPong = null;
	}
}

function SetTimerPong(callback) {
	ClearTimerPong();
	timerPong = timers.setTimeout(callback,options.pongTimeout);
}

function AbortRegistrations() {
	function AbortPool(pool) {
		for(var channelID in pool) {
			var request = pool[channelID];
			request.error={
				name: "AbortError",
			}
			try {
				request.onerror(request.error);
			} catch(e) {}
		}
	}
	AbortPool(registerRequests);
	registerRequests = {};
	AbortPool(unregisterRequests);
	unregisterRequests = {};
	AbortPool(pendingRegisters);
	pendingRegisters = {};
	AbortPool(pendingUnregisters);
	pendingUnregisters = {};
}

exports.init = function(opts) {
	if(!prefBranch.prefHasUserValue("pushServer")) {
		prefBranch.setCharPref("pushServer","wss://push.services.mozilla.com|");
		prefBranch.setCharPref("pushRegistrations","{}");
	}
	
	opts=opts || {};
	
	options = {
		serverUrl: "wss://push.services.mozilla.com",
		uaid: "",
		usePing: false,
		retryTimeout: 10000,
		pingTimeout: 30*60*1000,
		pongTimeout: 10*1000,
		onmessage: function() {}
	}
	
	var prefPushServer = prefBranch.getCharPref("pushServer");
	var m=/^(.*)(?:\|)(.*)$/.exec(prefPushServer);
	if(m) {
		if(m[1])
			options.serverUrl = m[1];
		options.uaid = m[2] || "";
	}
	for(var f in opts) 
		if(opts.hasOwnProperty(f))
			options[f] = opts[f];
	running = true;
	Start();
}

exports.destroy = function() {
	running = false;
	ClearTimerRetry();
	ClearTimerPing();
	ClearTimerPong();
	AbortRegistrations();
	if(wssChan) {
		wssChan.close(1000,"Closed");
		wssChan = null;
	}
}

exports.register = function() {
	var channelID =  generateUUID().toString().replace(/\{|\}/g,'');
	var request = {
		onsuccess: function() {},
		onerror: function() {},
	}
	registerRequests[channelID] = request;
	if(connected)
		SendRegister(channelID);
	else
		pendingRegisters[channelID] = request;
	return request;
}

exports.unregister = function(endpoint) {
	var registrations = GetRegistrations();
	var registration = null, channelID = null;
	for(var cid in registrations)
{'fraction_non_alphanumeric': 0.1409116005153058, 'fraction_numerical': 0.005889209250966199, 'mean_word_length': 3.63915765509391, 'pattern_counts': {'":': 8, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3035533', 'n_tokens_mistral': 6669, 'n_tokens_neox': 5413, 'n_words': 942}