javascript - call exported function from another class - javascript

Here's the scenario: There is a file called api.js which has method api() to make api calls. There is another class called AutoLogout which has functionality to show autologout modal and logging out user after certain time in case of no activity. These works fine.
index.js in ../services
export { default as api } from './api';
// export { api, onResponse } from './api'; tried this as well
export { default as userService } from './userService';
api.js
import userService from './userService';
export function onResponse(response) {
// returns response to calling function
return response;
}
async function api(options) {
const settings = Object.assign(
{
headers: {
'content-type': 'application/json',
'x-correlation-id': Math.random()
.toString(36)
.substr(2),
},
mode: 'cors',
credentials: 'include',
body: options.json != null ? JSON.stringify(options.json) : undefined,
},
options,
);
const response = await window.fetch(`/api/v0${options.endpoint}`, settings);
// calling onResponse() to send the response
onResponse(response);
if (response.status === 403) return userService.logout();
if (response.status > 299) throw new Error();
if (response.status === 204) return true;
return response.json ? response.json() : false;
}
export default api;
Now, in response header I've "x-expires-at" and I want to use it in autologout. So, that if api call is made the user token resets.
auto-lougout.js
import { userService, api } from '../services';
// import { userService, api, onResponse } from '../services'; tried this as well
export default class AutoLogout {
constructor() {
super();
if (!userService.getUser()) userService.logout();
// here I am not able to call onResponse() from api.js
// getting response received from onResponse()
api.onResponse((resp) => { console.log(resp.headers.get('x-expires-at'))});
}
}
Trying to implement as an example given in this article:
https://zpao.com/posts/calling-an-array-of-functions-in-javascript/
Here I cannot use export { api, onResponse }; as api is already being used at multiple places in whole project.
How do I call onResponse function in one js file from another class in another js file ? Am I using callback correctly here ? If not, how to use callback correctly in such scenario ?

Here I cannot use export { api, onResponse }; as api is already being used at multiple places in whole project.
The correct import/export syntax for your project would be
// api.js
export function onResponse() { … }
export default function api() { … }
// index.js
export { default as userService } from './userService';
export { default as api, onResponse } from './api';
// elsewhere
import { userService, api, onResponse } from '../services';
// use these three
Am I using callback correctly here?
No, not at all. onResponse should not be a function declared in your api.js file, and it should not be exported from there - sparing you all the above hassle.
If not, how to use callback correctly in such scenario?
Make the callback a parameter of the function that uses it:
export default async function api(options, onResponse) {
// ^^^^^^^^^^
const settings = Object.assign(…);
const response = await window.fetch(`/api/v0${options.endpoint}`, settings);
onResponse(response);
…
}
Then at the call of the api function, pass your callback as an argument:
api(options, resp => {
console.log(resp.headers.get('x-expires-at'));
});

Related

How to access a component function from main.js in Vue 3 with Composition API

Using Vue 3 and composition API I have a component that have this function:
const retrieveSignedJWT = async (callback) => {
if (jwtUrl.value && !callback) {
//console.log("There's no callback use the by default URL")
await fetch(jwtUrl.value)
.then(async (response) => {
const data = await response.text();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
let jwt = data;
token.value = data;
decodeToken(jwt);
retrieveCategories();
})
.catch((error) => {
errorMessage.value = error;
console.error("There was an error!", error);
});
} else {
//Function has a callback
token.value = callback;
}
};
What I need to do is to find a way to expose the previous component function so I can call it from the main.js. The scenario is that I'm creating an IIFFE with Vue 3 and Vite (a widget that the end user will load from a script) and hooking up a public function to it so the user can use it at any point in their code. That function can have or not have a callback that will expose a token implemented.
import { createApp } from "vue";
import "#/assets/styles/index.scss";
import App from "./App.vue";
import store from "./store";
let div = document.createElement("div");
document.body.appendChild(div);
div.setAttribute("id", "my-widget");
window.myWidget = {
load: function (endUserRetrievedJWT) {
if (endUserRetrievedJWT) {
const endUserJWT = endUserRetrievedJWT();
//Calling my component function w a call back
retrieveSignedJWT(endUserJWT);
} else {
//Calling my component function without a call back
retrieveSignedJWT();
}
},
};
createApp(App).use(store).mount("#my-widget");
So basically I'm trying to find a way to invoke the parent component function from the main.js file in order to manage how to save a token to the state of my application. That token can come from a default URL or in the shape of a callback function that the end-user will pass as an argument to the globally expose function coming from main.js.
main.js is meant for setting up your Vue application. Don't use this for any other code! It won't work.
Simply create a separate .js file (e.g. utils.js) and export the function from there.
I'm not completely sure what you're trying to achieve exactly but it looks like an authentication process for logging in. What you're probably trying to do is call the login/logout functions from within a Vue component? This could be as simple as
// Separate file, e.g. `authentication.js`
export const login = (cb) => {
// do not name the callback function (cb) 'callback' as that may get you unexpected behavior
// Your code
somePromise().then((res) => {
cb(res);
});
}
// Your component
import { login } from 'path/to/authentication';
login(myCallback);
const myCallback = (res) => {
...
};
I finally ended up exposing the function in the mounted hook like this:
onMounted(() => {
window.myWidget = {
load: retrieveEndUserJWT,
};
retrieveDataAttributes();
callWebsocket(websocket.value);
});
Then inside the same component, I create a method that will process the callback:
const retrieveEndUserJWT = async (endUserRetrievingTokenCallback) => {
const jwt = await endUserRetrievingTokenCallback();
processingToken(jwt);
};
And in the processingToken method, I deal with that token coming from the end-user callback function. I still have to navigate the pros and cons of exposing the function in the mounted hook.

Create T3 App Redirect inside a TRPC middleware if user is not signed

How can I trigger a redirect on the server side if a signed in user has not completed their profile page
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
// redirect to profile page if user has not completed profile
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
This is not currently possible in the way you are describing to the best of my knowledge.
Here are some alternatives that might be helpful:
In getServerSideProps
this only works if you want to redirect before the initial page load. You could also create a wrapper around gSSP to make this more DRY if you're going to use it on a lot of pages.
import { type GetServerSidePropsContext } from "next";
import { getServerAuthSession } from "../server/auth";
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const session = await getServerAuthSession(ctx);
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {},
};
}
export default function AuthedPage() {
return <div>Authed</div>;
}
As part of a query or mutation clientside
this is useful for a query or mutation that is only fired after the page has loaded. Again this is a very simple example and could be DRYed, probably the easiest way would be to extract into a custom hook.
import { useRouter } from "next/router";
import { api } from "../utils/api";
export default function AuthedPage() {
const router = useRouter();
// `authedHello` is the example Create T3 App "hello" procedure
// but as a protectedProcedure, ie throws "UNAUTHORIZED" if no session.
// Replace this with a middleware that throws on whatever condition you need it to.
const authedHello = api.example.protectedHello.useQuery(
{ text: "world" },
{
retry: (_count, err) => {
// `onError` only runs once React Query stops retrying
if (err.data?.code === "UNAUTHORIZED") {
return false;
}
return true;
},
onError: (err) => {
if (err.data?.code === "UNAUTHORIZED") {
void router.push("/");
}
},
}
);
return (
<div>
<h1>Authed Page</h1>
<p>{authedHello.data?.greeting}</p>
</div>
);
}
Using Next.js middleware
This is easy to apply to a bunch of routes using the matcher, but it falls a bit outside of T3 conventions.
// pages/middleware.ts
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "../server/auth";
import type { NextApiRequest, NextApiResponse } from "next";
export async function middleware(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession(req, res, authOptions);
if (!session?.user) {
return NextResponse.redirect(new URL("/", req.url));
}
}
export const config = {
matcher: ["/protectedPage", "/anotherProtectedPage"],
};
Using require in next-auth's useSession
this is useful if you want to guard a page but can't use getServerSideProps. It doesn't quite solve your specific problem, but might be useful to other people who find this. See: https://next-auth.js.org/getting-started/client#require-session

Cannot read property of undefined, Typescript

I'm having issues with creating a class function with optional parameters. I'm plagued with the following error and since I'm a new comer to typescript I'm not really sure as to what I have to change in my code to fix it. This code works perfectly fine in an earlier project written in JS only.
It specifically highlights the getRequestOptions, when I go check it in base-service.ts in the browser error.
08:32:12.755 client.ts:22 [vite] connecting...
08:32:13.067 client.ts:52 [vite] connected.
08:32:17.043 locales-service.ts:7 getting locales
08:32:17.048 base-service.ts:19 get /Api/GetLocales/ undefined undefined undefined
08:32:17.288 base-service.ts:21 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'getRequestOptions')
at request (base-service.ts:21)
at getLocales (locales-service.ts:9)
at setup (locale-selector.vue:22)
at callWithErrorHandling (runtime-core.esm-bundler.js:6737)
at setupStatefulComponent (runtime-core.esm-bundler.js:6346)
at setupComponent (runtime-core.esm-bundler.js:6302)
at mountComponent (runtime-core.esm-bundler.js:4224)
at processComponent (runtime-core.esm-bundler.js:4199)
at patch (runtime-core.esm-bundler.js:3791)
at mountChildren (runtime-core.esm-bundler.js:3987)
And my code
base-service.ts
import axios from '#/plugins/axios'
export default class BaseService {
getRequestOptions(url:any, method:any, data?:any, params?:any , customHeaders?:any) {
const headers = {
...customHeaders
}
return {
url:url,
method:method,
data: data,
params: params,
headers: headers,
}
}
async request(method:any, url:any, data?:any, params?:any, headers?:any) {
console.log(method,url,data,params,headers)
const options = this.getRequestOptions(method, url, data, params, headers)
try {
console.log(options)
const response = await axios(options)
console.log("Getting response from api")
console.log(response)
if (response.status === 200) {
return response.data
}
} catch (error) {
console.log(error)
}
}
}
locales-service.ts
import BaseService from '../base-service'
//?Import models in the future
//import { } from '#/common/models'
class LocaleServ extends BaseService {
async getLocales() {
console.log("getting locales")
let result = await super.request(
'get',
'/Api/GetLocales/'
)
console.log(result)
return result
}
}
export const LocaleService = new LocaleServ()
Any help would be greatly appreciated
Edit:
<script lang="ts">
import { ref } from 'vue'
import { defineComponent } from 'vue'
import CountryFlag from 'vue-country-flag-next'
import { LocaleService } from '#/services'
export default defineComponent({
name: 'LocaleSelector',
components: {
CountryFlag
},
setup () {
const { getLocales } = LocaleService
const data = getLocales()
return {
data:data
}
}
})
</script>
And then in the component I call it with {{data}}
The issue lied in the way I called the destructured getLocales() method from LocaleService in locale-service.vue
I noticed that this was undefined after attaching a debugger. According to this article, destructuring it caused it to lose it's context. Therefore when I called getLocales() in base-service.ts, this was undefined.
Simple work around to fix the, caused due to inexperience, problem was to turn.
const { getLocales } = LocaleService
const data = getLocales()
into
const data = LocaleService.getLocales()

Read header from response with apollo-datasource-rest

import { RESTDataSource } from 'apollo-datasource-rest';
export class Foo extends RESTDataSource {
async getFoo(id) {
const responseFoo = await this.get(`/api/v1/foo/${id}`);
return FooReducer(responseFoo);
}
}
Lets say I use this.get go do a GET Request. How can I read the headers in the response? Is this completely missing in the apollo-datasource-rest package?
In the didRecieveResponse method in the RESTDataSource, you can access the response object. Define/override as follows in the RESTDataSource class:
export class Foo extends RESTDataSource {
async didReceiveResponse(response, request) {
// use this.authHeader value in the class anywhere
this.authHeader = response.headers.get('authorization');
}
async getFoo(id) {
const responseFoo = await this.get(`/api/v1/foo/${id}`);
console.log(this.authHeader)
return FooReducer(responseFoo);
}
}
The the willSendRequest method (where you can access the req object and set headers etc) runs before firing an http request and didReceiveResponse method is called after receiving a response from an http request.

Why axios request not working inside vuejs (nuxt.js) methods

I have installed axios inside my nuxt.js application. Here my configuration file code:
File: nuxt.config.js
modules: [
'#nuxtjs/vuetify',
'#nuxtjs/axios',
],
axios: {
// proxyHeaders: false
}
Here my example working code:
export default {
data() {
return {
ip: ''
}
},
async asyncData({ $axios }) {
const ip = await $axios.$get('http://icanhazip.com')
return { ip }
}
}
And here my not working code:
export default {
data() {
return {
ip: ''
}
},
methods: {
async asyncData() {
const ip = await this.$axios.$get('http://icanhazip.com')
this.ip = ip
}
}
}
Why inside methods axios request not working?
You cannot call asyncData in you methods object. asyncData is for pre rendering only.
Rename your function to something else and it should be fine:
export default {
data() {
return {
ip: ''
}
},
methods: {
async getData() {
const ip = await this.$axios.$get('http://icanhazip.com')
this.ip = ip
}
}
}
Also when you are using asyncData as in your top example, you should not initialise "ip" in your data function. What is returned from asyncData is merged into data anyway.
AsyncData method will be called everytime before loading the page also note that asyncdata is only available in page component in nuxt. You don't have access to the component instance through this inside asyncData because it is called before initiating the component. You can use the returned data from asyncData data in your template without initialising in your data.
Nuxt asyncData

Categories

Resources