How to run one background script per private mode window in safari? - safari

I'm developing a Safari app extension (manifest v3) and I want to be able to start 2 private instances and have them completely separate, including a separate background script for each.
However it appears that there is only 1 background script for all the private pages. The background.js code to verify this boils down to:
function editTitle(x) {
document.title = x;
}
async function getCurrentTab() {
let [tab] = await browser.tabs.query({ active: true });
return tab.id;
}
function sample(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function makeId(length) {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYM'.split('');
return Array(length).fill('').map((c) => sample(letters)).join('');
}
(async function() {
const myId = makeId(10);
console.log("Starting", myId);
const startedAt = Date.now();
setInterval(async () => {
const tabId = await getCurrentTab();
browser.scripting.executeScript({
target: {tabId, allFrames: true},
func: editTitle,
args: [myId]
}, () => {
console.log('done');
});
}, 1000);
})();
When I run it, I can see that all the tabs end up getting the same title, meaning that myId value is shared across all private instances. Is there any approach that would allow me to completely separate private windows?

Related

How to make pagination work? Async await function in vue.js 3 setup

I was trying to make an app which lists a user's repositories from github using github API, however I'm having a big problem with fetching data from all pages (so far I can only get repos from one page). I tried to fix it by using an async/await function (instead of Promise), but it's also my first time using vue3 and I have no idea how to have a function inside of the setup() method.
The current code is here:
https://github.com/agzpie/user_repos
My try at using async/await, which didn't work:
import ListElement from "./components/ListElement";
import { ref, reactive, toRefs, watchEffect, computed } from "vue";
export default {
name: "App",
components: {
ListElement,
},
setup() {
const name = ref(null);
const userName = ref(null);
const state = reactive({ data: [] });
let success = ref(null);
const userNameValidator = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;
const split1 = reactive({ spl1: [] });
const split2 = reactive({ spl2: [] });
async function myFetch() {};
/*
* Check for input in the form and then fetch data
*/
watchEffect(() => {
if (!userName.value) return;
if (!userNameValidator.test(userName.value)) {
console.log("Username has invalid characters");
return;
}
let hasNext = false;
state.data = [];
do {
async function myFetch() {
let url = `https://api.github.com/users/${userName.value}/repos?per_page=5`;
let response = await fetch(url);
if (!response.ok) {
success.value = false;
throw new Error(`HTTP error! status: ${response.status}`);
}
success.value = true;
// check response.headers for Link to get next page url
split1.spl1 = response.headers.get("Link").split(",");
let j = 0;
while (j < split1.spl1.length) {
split2.spl2[j] = split1.spl1[j].split(";");
console.log(split2.spl2[j][0]);
console.log(split2.spl2[j][1]);
if (split2.spl2[j][1].includes("next")) {
let urlNext = split2.spl2[j][0].replace(/[<>(\s)*]/g, "");
console.log(urlNext);
url = urlNext;
hasNext = true;
break;
} else {
hasNext = false;
}
j++;
}
// second .then
let myData = await response.json();
state.data.push(...myData);
console.log("data", myData);
name.value = "";
}
myFetch().catch((err) => {
if (err.status == 404) {
console.log("User not found");
} else {
console.log(err.message);
console.log("oh no (internet probably)!");
}
});
} while (hasNext);
});
// Sort list by star count
const orderedList = computed(() => {
if (state.data == 0) {
return [];
}
return [...state.data].sort((a, b) => {
return a.stargazers_count < b.stargazers_count ? 1 : -1;
});
});
return {
myFetch,
success,
isActive: true,
name,
userName,
ListElement,
...toRefs(state),
orderedList,
};
},
};
Any help would be highly appreciated
The call to myFetch() near the end is a call to an async function without an await, so it is effectively going to loop (if hasNext was initialized to true, but it isn't) without waiting for it to complete.
You should probably change that line to await myFetch() and wrap it all with a try/catch block.
I also don't really care for the way you're directly updating state inside the async myFetch call (it could also be doing several of those if it looped) and perhaps it should be returning the data from myFetch instead, and then you can use let result = await myFetch() and then make use of that when it returns.
Also, instead of awaiting myFetch() result, you could not await it but push it onto a requests array and then use await Promise.all(requests) outside the loop and it is one operation to await, all requests running in parallel. In fact, it should probably be await Promise.allSettled(requests) in case one of them fails. See allSettled for more.
But also I wonder why you're reading it paged if the goal is to fetch them all anyway? To reduce load on the server? If that is true, issuing them paged but in parallel would probably increase the load since it will still read and return all the data but require multiple calls.

Vuejs created and mounted doesn't work properly even if at the same level than methods

I'm experiencing a strange behaviour with created() and mounted() in Vue.js. I need to set 2 lists in created() - so it means those 2 lists will help me to create a third list which is a merge.
Here is the code :
// return data
created () {
this.retrieveSellOffers();
this.getAllProducts();
},
mounted () {
this.mergeSellOffersProducts();
},
methods: {
retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
SellOfferServices.getAllBySellerId(this.sellerId)
.then((response) => {
this.sellOffers = response.data;
console.log("this.sellOffers");
console.log(this.sellOffers);
})
.catch((e) => {
console.log(e);
});
},
getAllProducts() {
ProductServices.getAll()
.then((response) => {
this.products = response.data;
console.log("this.products");
console.log(this.products);
})
.catch((e) => {
console.log(e);
});
},
mergeSellOffersProducts () {
console.log(this.products) // print empty array
console.log(this.sellOffers) // print empty array
for (var i = 0; i < this.sellOffers.length; i++) {
if (this.sellOffers[i].productId === this.products[i]._id) {
this.arr3.push({id: this.sellOffers[i]._id, price: this.sellOffers[i].price, description: this.products[i].description});
}
}
this.arr3 = this.sellOffers;
},
}
//end of code
So my problem is when I enter in mergeSellOffersProducts(), my 2 lists are empty arrays :/
EDIT :
This way worked for me :
async mounted() {
await this.retrieveSellOffers();
await this.getAllProducts();
this.mergeSellOffersProducts();
},
methods: {
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('link/api/selloffer/seller/', { params: { sellerId: this.sellerId } })).data;
},
async getAllProducts() {
this.products = (await axios.get('link/api/product')).data;
},
}
I think the reason is: Vue does not wait for the promises to resolve before continuing with the component lifecycle.
Your functions retrieveSellOffers() and getAllProducts() contain Promise so maybe you have to await them in the created() hook:
async created: {
await this.retrieveSellOffers();
await this.getAllProducts();
}
So I tried to async my 2 methods :
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('linkhidden/api/selloffer/', { params: { sellerId: '615b1575fde0190ad80c3410' } })).data;
console.log("this.sellOffers")
console.log(this.sellOffers)
},
async getAllProducts() {
this.products = (await axios.get('linkhidden/api/product')).data;
console.log("this.products")
console.log(this.products)
},
mergeSellOffersProducts () {
console.log("here")
console.log(this.sellOffers)
console.log(this.products)
this.arr3 = this.sellOffers;
},
My data are well retrieved, but yet when I enter in created, the two lists are empty...
You are calling a bunch of asynchronous methods and don't properly wait for them to finish, that's why your data is not set in mounted. Since Vue does not await its lifecycle hooks, you have to deal with the synchronization yourself.
One Vue-ish way to fix it be to replace your method mergeSellOffersProducts with a computed prop (eg mergedSellOffersProducts). Instead of generating arr3 it would simply return the merged array. It will be automatically updated when products or sellOffers is changed. You would simply use mergedSellOffersProducts in your template, instead of your current arr3.
If you only want to update the merged list when both API calls have completed, you can either manually sync them with Promise.all, or you could handle this case in the computed prop and return [] if either of the arrays is not set yet.
When you're trying to merge the 2 lists, they aren't filled up yet. You need to await the calls.
async created () {
await this.retrieveSellOffers();
await this.getAllProducts();
},
async mounted () {
await this.mergeSellOffersProducts();
},

React Native Hooks initializer not taking the correct value

What I am trying to do is sync a list of attendees from an online database, and if the current user is in the list, then disable a button, else enable the button.
I am using react native hook (I am not sure if I am using the term correctly as I am fairly new to react), in order to set the value of disabling the button.
The issue that I am facing is that the value is getting initialized to false, even tho it should clearly get initialized to true.
After adding some logging I made sure that the function is executing correctly and reaching the code where it sets the value to true.
const [buttonDisabled, changeButtonState] = useState( () => {
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
const dbRef = firebase.database().ref();
var Attendees = [];
var disable = false;
dbRef.child("gameAttendees").child(gameinfo.gameID).get().then((snapshot) => {
if (snapshot.exists()) {
Attendees = snapshot.val().Attendees;
for(var i=0;i<Attendees.length;i++){
if(Attendees[i]==userId){
return true;
}
}
} else {
console.log("no value");
return false;
}
}).catch((error) => {
console.error(error);
});
});
Adding an example of an async mount effect:
const Comp = () => {
const [s, setS] = useState(); // State will be undefined for first n renders
useEffect(() => {
// Call the async function and set the component state some time in the future
someAsyncFunction().then(result => setS(result));
}, []); // An effect with no dependencies will run only once on mount
return </>;
};

Looping list in WebdriverIO

I'm trying to loop through a list of links, and perform some actions with each one. WebdriverIO are synchronous for test code like following(mocha) for all their API. But how to integrate existing node.js code for synchronous flow. For following code
const assert = require('assert');
const webdriverio = require('webdriverio')
describe('webdriver.io page', function() {
it('should be a pending test');
before(() => {
var options = {
desiredCapabilities: {
browserName: 'chrome'
}
};
client = webdriverio.remote(options)
return client.init();
})
after(function() {
browser.end();
});
it('should have the right title - the fancy generator way', function () {
var list = ["https://www.google.com", "https://www.yahoo.com", "https://www.github.com"]
for(var i = 0 ; i < list.length;i++){
browser.url(list.length);
var title = browser.getTitle();
browser.saveScreenshot('./snapshot' + i + '.png').call(function(){
fs.exists(screenshotPath, function(fileExists) {
fileExists.should.be.true;
done();
})
});
}
});
});
I have 2 other options for you for a sync loop
https://www.npmjs.com/package/serial-loop
or you can create your own loop like this
function testonly() {
//.. do some stuff
// when done make again
testonly()
// when you finish with your stuff you can start a new function as example after()
if(test == 'test') {
after()
}
}
function after() {
//..
}
Or you can use the callback method. Hope this was what you were looking for. Of course you can use webdriver.io stuff in this loops!

subscribed events in durandaljs are not unsubscribed

This is the flow of the application:
The shell.js loads the schoolyeardialog.js which contains the schoolyearbrowser.js which loads the schoolyearwizard.js via create or edit button.
When I repeat these steps multiple times: click create/edit button then I do multiple
requests in my SchoolyearWizard to this:
$.when(service.editSchoolyear(schoolyearId))
The reason is that the subscribed events are not correctly unsubscribed in my opinion.
I have tried different ways of unsubscribing in the SchoolyearDialog.js file.
Either the events did not fire - when I did the subscription.off(); after the app.on(...)
or It was unsubscribed at the wrong position.
Where should I unsubscribe correctly?
If you guys need a sample repo as visual studio solution I can provide this if it helps or maybe you see clearly and immediately the error?!
I have also thought about unsubscribing from the 2 events create/edit when the SchoolyearDialog module is "unloaded" because then both events could/would be unsubscribed not only the create OR edit subscription as it is now when I either click the add or edit button... how would I do that?
SHELL
define(['plugins/router', 'durandal/app', 'viewmodels/SchoolyearDialog', 'knockout'], function (router, app, schoolyearDialog, ko) {
self.schoolyearIsLoaded = ko.observable(false);
var saveTimeTableSubscription = app.on('savedTimeTable').then(function (options) {
// after coming the 2nd time here
if (!self.schoolyearIsLoaded()) {
router.map([{ route: 'lessonplanner', moduleId: 'viewmodels/lessonplanner', title: 'lesson planner', nav: true },
{ route: 'documentbrowser', moduleId: 'viewmodels/documentbrowser', title: 'document browser', nav: true }])
.buildNavigationModel();
self.schoolyearIsLoaded(true);
}
router.navigate("lessonplanner", true);
});
return {
router: router,
activate: function () {
router.map([{ route: '', moduleId: 'viewmodels/SchoolyearDialog', nav: true, title: 'Schoolyearbrowser' }
]).buildNavigationModel();
return router.activate('SchoolyearDialog');
}
};
});
SchoolyearDialog
define(['durandal/app', 'knockout', 'plugins/router', 'viewmodels/SchoolyearWizard'],
function (app, ko, router, wizard) {
var ctor = function () {
debugger;
var self = this;
self.createSubscribe = ko.observable();
self.editSubscribe = ko.observable();
self.activeScreen = ko.observable('viewmodels/SchoolyearBrowser'); // set the schoolyear browser as default module
var createWizardSubscription = app.on('createWizard').then(function () {
self.createSubscribe().off();
self.createSubscribe(null);
self.activeScreen(new wizard('create'));
}, self);
self.createSubscribe(createWizardSubscription);
var editWizardSubscription = app.on('editWizard').then(function (schoolyearId) {
self.editSubscribe().off();
self.editSubscribe(null);
self.activeScreen(new wizard('edit', schoolyearId));
}, self);
self.editSubscribe(editWizardSubscription);
}
return ctor;
});
SchoolyearBrowser
define(['durandal/app', 'plugins/dialog', 'knockout', 'services/dataservice', 'plugins/router'],
function (app, dialog, ko, dataservice, router) {
var SchoolyearBrowser = function () {
var self = this;
self.schoolyears = ko.observableArray();
$.when(dataservice.getSchoolyears())
.done(function (schoolyearModels) {
self.schoolyears(schoolyearModels);
});
self.create = function () {
app.trigger('createWizard');
}
self.edit = function () {
app.trigger('editWizard', 1);
}
};
return SchoolyearBrowser;
});
SchoolyearWizard
define(['durandal/activator', 'viewmodels/step1', 'viewmodels/step2', 'knockout', 'durandal/app', 'services/dataservice', 'viewmodels/CreateEditSchoolyearViewModel'],
function (activator, Step1, Step2, ko, app, service, CreateEditSchoolyearViewModel) {
var ctor = function (viewMode, schoolyearId) {
debugger;
// depending on the mode I could setup 2 different step modules for create and edit ? and the Wizard has one property called content
if (viewMode === 'edit') {
$.when(service.editSchoolyear(schoolyearId))
.done(function (response) {
debugger;
self.viewModel(new CreateEditSchoolyearViewModel(response));
}).fail(function (error) {
alert(error);
});
}
else if (viewMode === 'create') {
$.when(service.createSchoolyear())
.done(function (response) {
debugger;
self.viewModel(new CreateEditSchoolyearViewModel(response));
}).fail(function (error) {
alert(error);
});
}
var self = this;
var steps = [new Step1(viewMode), new Step2(viewMode)];
var step = ko.observable(0); // Start with first step
self.activeStep = activator.create();
var stepsLength = steps.length;
self.viewModel = ko.observable();
this.hasPrevious = ko.computed(function () {
return step() > 0;
});
self.caption = ko.observable();
this.activeStep(steps[step()]);
this.hasNext = ko.computed(function () {
if ((step() === stepsLength - 1) && self.activeStep().isValid()) {
// save
self.caption('save');
return true;
} else if ((step() < stepsLength - 1) && self.activeStep().isValid()) {
self.caption('next');
return true;
}
});
this.isLastStep = function () {
return step() === stepsLength - 1;
}
this.next = function () {
if (this.isLastStep()) {
var vm = this.activeStep(); //.viewModel;
$.when(service.saveCreateSchoolyear({ schoolyearId: 1 })).done(function () {
app.trigger('savedTimeTable', { isSuccess: true });
}).fail(function (e) {
alert(e);
});
}
else if (step() < stepsLength) {
step(step() + 1);
self.activeStep(steps[step()]);
}
}
this.previous = function () {
if (step() > 0) {
step(step() - 1);
self.activeStep(steps[step()]);
}
}
}
return ctor;
});
This helped me greatly:
activator.deactivate function allows the previous object to execute custom deactivation logic."
SchoolyearDialog.js
self.deactivate = function () {
self.createSubscribe().off();
self.editSubscribe().off();
}
When the schoolyearDialog is deactivated both events are unsubscribed independing wether button create/edit is clicked. This is for me a clean solution :)
I agree with your solution but I'd recommend not to use plain .off() without parameters as this will cause to deregister all the events in the application.
Rather pass the event name as a parameter to your off method:
self.createSubscribe().off('savedTimeTable');