vscode `provideCompletionItem` in Middleware not getting triggered for string - vscode-extensions

I am trying to implement embedded language support for SQL in vscode extension using request forwarding mechanism.
According to the documentation we can hijack the completion using middleware option and provide the sub-language completion using an existing language server. As an example user can get CSS language support inside HTML using this mechanism.
In my scenario, I want get SQL Language support inside another language (new language) when writing a SQL query.
Sample Code
let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: '.mylang' }],
middleware: {
provideCompletionItem: async (document, position, context, token, next) => {
if (
!isInsideSQLRegion(
myLanguageService,
document.getText(),
document.offsetAt(position)
)
) {
return await next(document, position, context, token);
}
const originalUri = document.uri.toString();
virtualDocumentContents.set(
originalUri,
getSQLVirtualContent(myLanguageService, document.getText())
);
const vdocUriString = `embedded-content://sql/${encodeURIComponent(originalUri)}.sql`;
const vdocUri = Uri.parse(vdocUriString);
return await commands.executeCommand<CompletionList>(
'vscode.executeCompletionItemProvider',
vdocUri,
position,
context.triggerCharacter
);
}
}
};
Here the provideCompletionItem get triggered each time we write a new character in the editor but it seems that it is not getting triggered when user writes " or single quote.
Is there a way to get provideCompletionItem when single or double quotes is being entered ?

Related

Getting language from browser doesn't work with SSR

I have a website with multi language ['de', 'fr', 'it', 'en']
Current behaviour:
When I enter example.com without a previous session I am redirected to example.com/de (first value in the array)
Wanted behaviour:
I want to be redirected to the browser's language I have (in case there is none in session)
I have extended the service LanguageService to override the initialize() function as follows:
initialize(): void {
let value;
this.getActive()
.subscribe((val) => (value = val))
.unsubscribe();
if (value) {
// don't initialize, if there is already a value (i.e. retrieved from route or transferred from SSR)
return;
}
const languages = this.getLanguages();
const sessionLanguage = this.sessionStorageCustom && this.sessionStorageCustom.getItem('language');
if (sessionLanguage && languages?.includes(sessionLanguage)) {
this.setActive(sessionLanguage);
} else {
const browserLanguage = this.getBrowserLanguage();
if (browserLanguage && languages?.includes(browserLanguage)) {
this.setActive(browserLanguage);
}
}
}
Helpers:
private getLanguages(): string[] | null {
let languages = this.siteContextParamsService.getParamValues('language');
// Removing English from options
languages = languages.filter((l) => !(l.toLowerCase() === 'en'));
if (languages) return languages;
return null;
}
private getBrowserLanguage(): string | null {
let language = this.winRef.nativeWindow?.navigator.language;
if (language) {
language = language.slice(0, 2);
return language;
}
return null;
}
Constructor:
private sessionStorageCustom: Storage | undefined;
constructor(
protected store: Store<StateWithSiteContext>,
protected winRef: WindowRef,
protected config: SiteContextConfig,
protected siteContextParamsService: SiteContextParamsService
) {
super(store, winRef, config);
// cannot use default variable because it's private
this.sessionStorageCustom = winRef.sessionStorage;
}
On CSR everything works as expected but when in SSR I always go to the default language.
(Because on server side there is no browser's language. I assume.)
How can I force this code be executed at the client side? or what can I do to accomplish this?
By default the Spartacus siteContext will always default to the SSR transfer state if it is present before running the browser language logic.
I can see two solutions you could try:
You can remove the SSR transfer state. This way Spartacus will run your logic in browser every time. You can do it with:
state: {
ssrTransfer: {
keys: { [SITE_CONTEXT_FEATURE]: false },
},
},
This solution is not ideal because the SSR page will contain the default falback language which might not be the one the user is using so the page might flicker.
You can add custom logic in the LanguageService that will run only in the server and use the Accept-Language header to set the language. This header is set by the browser to let the server know what language the user wants. You can read this article which provides a great example on how to use this mechanism in Angular. The example is not in Spartacus but the same logic can be used.
One final thought, the Spartacus siteContext persistence will be updated in 4.0 to use the global state persistence mechanism.

Auth0 hooks post-user-registration edit user_metadata

I created a post-user-registration hook, in which i would like to save some information to user_metadata. However, I don't see the data being saved
/*
#param {object} user.user_metadata - user metadata
*/
module.exports = function (user, context, cb) {
// Perform any asynchronous actions, e.g. send notification to Slack.
user.user_metadata = {
"someinfo": "abcd"
}
cb();
};
Something like:
module.exports = function (user, context, cb) {
var response = {};
user.user_metadata.foo = 'bar';
response.user = user;
return cb(null, response);
};
worked fine for me.
For rules the docs say that you can't directly update the user_metadata. As described on the link you have to use the updateUserMetadata function after you set the new values. I am not sure if this applies to hooks too (probably not, since the auth0 object is not defined on hooks).
p.s. Keep in mind that hooks only run for Database Connections, as outlined in the docs. Is there a chance you used an account based on social login?

Hapi server methods vs server.app.doSomething

I am writing a hapi js plugin, and was wondering what's the difference between the two ways of exposing methods that other plugins can use.
Method 1:
server.method("doSomething",
function () {
// Something
});
Method 2:
server.app.doSomething = function () {
// Something
};
In the first approach, the function can later be called as server.doSomething(), while using the second approach as server.app.doSomething().
So why would I use one way instead of another?
Looking at the API docs it sounds like they intended server.methods to be used for functions and server.app to be used for app settings/configuration. My guess is you should stick with server.method if you want to expose server level methods to be used in your plugins.
server.methods
An object providing access to the server methods where each server
method name is an object property.
var Hapi = require('hapi');
var server = new Hapi.Server();
server.method('add', function (a, b, next) {
return next(null, a + b);
});
server.methods.add(1, 2, function (err, result) {
// result === 3
});
server.app
Provides a safe place to store server-specific run-time application
data without potential conflicts with the framework internals. The
data can be accessed whenever the server is accessible. Initialized
with an empty object.
var Hapi = require('hapi');
server = new Hapi.Server();
server.app.key = 'value';
var handler = function (request, reply) {
return reply(request.server.app.key);
};

Prevent duplicate routes in express.js

Is there a nice way to prevent duplicate routes from being registered in express? I have a pretty large application with hundreds of routes across different files, and it gets difficult to know if I've already registered a certain route when I go to add a new one. For example, I'd like to throw an error when express gets to routes487.js:
File: routes1.js
var ctrl = require('../controllers/testctrl');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi);
app.get('/there', auth.getToken, ctrl.there);
};
File: routes487.js
var ctrl = require('../controllers/testctrl487');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi487);
};
You could try a custom solution by wrapping express methods with the validation. Consider the following modification to your express app:
// route-validation.js
module.exports = function (app) {
var existingRoutes = {}
, originalMethods = [];
// Returns true if the route is already registered.
function routeExists(verb, path) {
return existingRoutes[verb] &&
existingRoutes[verb].indexOf(path) > -1;
}
function registerRoute(verb, path) {
if (!existingRoutes[verb]) existingRoutes[verb] = [];
existingRoutes[verb].push(path);
}
// Return a new app method that will check repeated routes.
function validatedMethod(verb) {
return function() {
// If the route exists, app.VERB will throw.
if (routeExists(verb, arguments[0]) {
throw new Error("Can't register duplicate handler for path", arguments[0]);
}
// Otherwise, the route is saved and the original express method is called.
registerRoute(verb, arguments[0]);
originalMethods[verb].apply(app, arguments);
}
}
['get', 'post', 'put', 'delete', 'all'].forEach(function (verb) {
// Save original methods for internal use.
originalMethods[verb] = app[verb];
// Replace by our own route-validator methods.
app[verb] = validatedMethod(verb);
});
};
You just need to pass your app to this function after creation and duplicate route checking will be implemented. Note that you might need other "verbs" (OPTIONS, HEAD).
If you don't want to mess with express' methods (we don't know whether or how express itself or middleware modules will use them), you can use an intermediate layer (i.e., you actually wrap your app object instead of modifying its methods). I actually feel that would be a better solution, but I feel lazy to type it right now :)

Multiple image attachments to couchdb (nano) with Node.js Express 4.0 & formidable

I am trying to insert multiple images to couchdb via nano using express 4 and formidable. I can access and insert individual files without difficulty using formidable and nano, however, when I try to insert one file after another, I get conflict errors. I am very new to js and node, and I know my understanding of callbacks and asynchronous functions is limited. This is what I have at the moment. Any help would be greatly appreciated.
function uploadImage(req, res) {
var form = new formidable.IncomingForm(),
files = [],
fields = [];
uploadcount = 1;
form.on('field', function(field, value) {
fields.push([field, value]);
})
form.on('file', function(field, file) {
files.push([field, file]);
var docid = fields[0][1];
getrevision();
function getRevision(){
dbn.get(docid, { revs_info: true }, function(err,body, file){
if (!err) {
exrev = body._rev;
insertImage(exrev);
}else{
console.log(err);
}
});
}
function insertImage(exrevision){
var exrev = exrevision;
fs.readFile(file.path, function (err, data) {
if (err){
console.log(err);}else{
var imagename = docid + "_" + uploadcount + ".png";
dbn.attachment.insert(docid,imagename,data,'image/png',
{ rev: exrev }, function(err,body){
if (!err) {
uploadcount++;
}else{
console.log(err);
}
});
};
});
};
});
form.on('end', function() {
console.log('done');
res.redirect('/public/customise.html');
});
form.parse(req);
};
I found a solution by dumping the files first into a temporary directory, then proceeding to insert the files into couchdb via nano all within a single function. I couldn't find a way to pause the filestream to wait for the couchdb response, so this sequential method seems adequate.
This is a problem handling asynchronous calls. Because each attachment insert requires the doc's current rev number, you can't do the inserts in parallel. You must insert a new attachment only after you get a response from the the previous one.
You may use the promise and deferred mechanism to do this. But, I personally have solved a similar problem using a package called "async". In async, you can use async.eachSeries() to make these async calls in series.
Another point is about the revision number, you may just use the lighter weight db.head() function, instead of db.get(). The rev number is presented under the "etag" header. You can get the rev like this:
// get Rev number
db.head(bookId, function(err, _, headers) {
if (!err) {
var rev = eval(headers.etag);
// do whatever you need to do with the rev number
......
}
});
In addition, after each attachment insert, the response from couchdb will look something like this:
{"ok":true,"id":"b2aba1ed809a4395d850e65c3ff2130c","rev":"4-59d043853f084c18530c2a94b9a2caed"}
The rev property will give the new rev number which you may use for inserting to the next attachment.