I'm trying to learn just the bare basics about suitelets in my NetSuite environment.
I have copy/pasted this code from the Help Center and I am receiving this error when clicking the URl "An unexpected error has occurred. Please click here to notify support and provide your contact information."
/**
* #NApiVersion 2.x
* #NScriptType Suitelet
*/
define([], function() {
function onRequest(context) {
var html = '<html><body><h1>Hello World</h1></body></html>';
context.response.write(html);
context.response.setHeader({
name: 'Custom-Header-Demo',
value: 'Demo'
});
}
return {
onRequest: onRequest
};
});
I have tried putting log.debugs within the script but I'm not getting anything in the execution log. I have uploaded the script, deployed, and released the suitelet but I'm still getting this error.
Error Screenshot
Tips For Success With Suitlets
1. Think like you're coding Node Express / Nextjs APIs
Set up your code to handle each request method type
/**
* #NApiVersion 2.1
* #NScriptType Suitelet
*/
define(["N/cache"], /**
* #param {cache} cache
*/
(cache) => {
/**
* Defines the Suitelet script trigger point.
* #param {Object} scriptContext
* #param {ServerRequest} scriptContext.request - Incoming request
* #param {ServerResponse} scriptContext.response - Suitelet response
* #since 2015.2
*/
const onRequest = (scriptContext) => {
let sc = scriptContext;
let req = sc.request;
let res = sc.response;
let { method, parameters, body, clientIpAddress, headers } = req;
let { write, setHeader } = res;
switch (method) {
case "GET":
// Run logic for each specific request type (Save creating files for each operation)
// Set header first then return
setHeader({
name: "Content-Type",
value: "application/json",
});
// return your server response
write({
output: "Hello World",
});
break;
case "POST":
// Add logic here for POST
break;
default:
log.error({ title: `❌ method: ${method} not supported` });
}
};
return { onRequest };
});
2. Utilize WebStorm & The SuiteCloud IDE Plugin
This will help you template out files faster and import modules and their JSDocs automatically along with giving you better IntelliSense. Along with instantly uploading your file instead of having to do it through the UI of NetSuite every time.
3. Use 2.1 Where its supported
Using 2.x is similar to ES2012 which is not the best for using let, const, and all the new ES6 functions natively available to us. Along with using string templating or literals as in the example above.
4. Use N/cache If possible
If you're fetching data like a record or ID to retrieve. Using N/cache will make the suitelet's execution much faster.
Related to your error
Try what I suggested in the code snippet. You want to set your header first before you return your response. As you're trying to set a header where the response has already been sent. Also NetSuite don't understand half the time the type of data you're sending/returning. So setting the Content-Type header will allow your response to be displayed as expected.
// If you're trying to send JSON you stringify it first before you send the data
write({
output: JSON.stringify({
text: 'Hello World'
})
});
If you are sending HTML as a response you would do the following
// Set header first for content return define
setHeader({
name: "Content-Type",
value: "text/html",
});
write({
output: '<html><body><h1>Hello World</h1></body></html>'
});
Hope this helps! 🚀
I ran the script on my Netsuite account and I didn't get the error.
Which role user did you use it to create the script and view the results? Try with the Administrator role and for sure that error is not going to be throw.
I got this error when init contract in my React project. It showed when i using .tsx file. After checked the documentation from near-api-js and it didn't explain what is the headers, but when i go inside the package it actually have the 'headers' inside near.d.ts.
const nearConfig = getConfig(process.env.NEAR_ENV || 'testnet');
const keyStore = new nearAPI.keyStores.BrowserLocalStorageKeyStore();
const near = await nearAPI.connect({ keyStore, ...nearConfig });
I'm guessing you are missing one property in your nearConfig object, which is headers. The property can be an empty object ({}.)
I think you can add headers:{} when you connect to the nearAPI, like this:
const near = await nearAPI.connect({ keyStore, headers: {}, ...nearConfig });
Or, you can add headers in your getConfig() function.
The reason you need to add it is because the TypeScript definition of ConnectConfig requires it to be present.
From the type definition in near-api-js:
/**
* NEAR RPC API headers. Can be used to pass API KEY and other parameters.
* #see {#link JsonRpcProvider.JsonRpcProvider | JsonRpcProvider}
*/
headers: {
[key: string]: string | number;
};
Apollo is not storing the header from the query dynamically.
pages/index.vue
methods: {
fetchCars() {
const token = Cookies.get('XSRF-TOKEN')
console.log(token) // 🟢 Token is shown in console
this.$apollo.query({
query: gql`
query {
cars {
uuid
name
}
}
`,
headers: {
'X-XSRF-TOKEN': token, // ⭕ Fetch without header
},
})
},
},
Is there a way to set the header value new for every Apollo request?
I have a separate Frontend and Backend. For the Frontend I am using Nuxt.js with Apollo. I want to have a session based communication with my server. For this reason I need to send the CSRF-Token with every Request.
Now the problem: On the first load of the page there is no Cookie set on the browser. I do a GET-Request on every initialization of my Nuxt application.
plugins/csrf.js
fetch('http://127.0.0.1:8000/api/csrf-cookie', {
credentials: 'include',
})
Now I have a valid Cookie set on my side and want to communicate with the GraphQL Server but my header is not set dynamically in the query. Does anyone know how I can solve this?
My Laravel Backend is throwing now a 419 Token Mismatch Exception because I did not send a CSRF-Token with my request.
Link to the repository: https://github.com/SuddenlyRust/session-based-auth
[SOLVED] Working solution: https://github.com/SuddenlyRust/session-based-auth/commit/de8fb9c18b00e58655f154f8d0c95a677d9b685b Thanks to the help of kofh in the Nuxt Apollo discord channel 🎉
In order to accomplish this, we need to access the code that gets run every time a fetch happens. This code lives inside your Apollo client's HttpLink. While the #nuxtjs/apollo module gives us many options, we can't quite configure this at such a high level.
Step 1: Creating a client plugin
As noted in the setup section of the Apollo module's docs, we can supply a path to a plugin that will define a clientConfig:
// nuxt.config.js
{
apollo: {
clientConfigs: {
default: '~/plugins/apollo-client.js'
}
}
}
This plugin should export a function which receives the nuxt context. It should return the configuration to be passed to the vue-cli-plugin-apollo's createApolloClient utility. You don't need to worry about that file, but it is how #nuxtjs/apollo creates the client internally.
Step 2: Creating the custom httpLink
In createApolloClient's options, we see we can disable defaultHttpLink and instead supply our own link. link needs to be the output of Apollo's official createHttpLink utility, docs for which can be found here. The option we're most interested in is the fetch option which as the docs state, is
a fetch compatible API for making a request
This boils down to meaning a function that takes uri and options parameters and returns a Promise that represents the network interaction.
Step 3: Creating the custom fetch method
As stated above, we need a function that takes uri and options and returns a promise. This function will be a simple passthrough to the standard fetch method (you may need to add isomorphic-fetch to your dependencies and import it here depending on your setup).
We'll extract your cookie the same as you did in your question, and then set it as a header. The fetch function should look like this:
(uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
Putting it all together
Ultimately, your ~/plugins/apollo-client.js file should look something like this:
import { createHttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-fetch'
export default function(context) {
return {
defaultHttpLink: false,
link: createHttpLink({
uri: '/graphql',
credentials: 'include',
fetch: (uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
})
}
}
I'm looking into implementing a "subscription" type using server-sent events as the backing api.
What I'm struggling with is the interface, to be more precise, the http layer of such operation.
The problem:
Using the native EventSource does not support:
Specifying an HTTP method, "GET" is used by default.
Including a payload (The GraphQL query)
While #1 is irrefutable, #2 can be circumvented using query parameters.
Query parameters have a limit of ~2000 chars (can be debated)
which makes relying solely on them feels too fragile.
The solution I'm thinking of is to create a dedicated end-point for each possible event.
For example: A URI for an event representing a completed transaction between parties:
/graphql/transaction-status/$ID
Will translate to this query in the server:
subscription TransactionStatusSubscription {
status(id: $ID) {
ready
}
}
The issues with this approach is:
Creating a handler for each URI-to-GraphQL translation is to be added.
Deploy a new version of the server
Loss of the flexibility offered by GraphQL -> The client should control the query
Keep track of all the end-points in the code base (back-end, front-end, mobile)
There are probably more issues I'm missing.
Is there perhaps a better approach that you can think of?
One the would allow a better approach at providing the request payload using EventSource?
Subscriptions in GraphQL are normally implemented using WebSockets, not SSE. Both Apollo and Relay support using subscriptions-transport-ws client-side to listen for events. Apollo Server includes built-in support for subscriptions using WebSockets. If you're just trying to implement subscriptions, it would be better to utilize one of these existing solutions.
That said, there's a library for utilizing SSE for subscriptions here. It doesn't look like it's maintained anymore, but you can poke around the source code to get some ideas if you're bent on trying to get SSE to work. Looking at the source, it looks like the author got around the limitations you mention above by initializing each subscription with a POST request that returns a subscription id.
As of now you have multiple Packages for GraphQL subscription over SSE.
graphql-sse
Provides both client and server for using GraphQL subscription over SSE. This package has a dedicated handler for subscription.
Here is an example usage with express.
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-sse';
// Create the GraphQL over SSE handler
const handler = createHandler({ schema });
// Create an express app serving all methods on `/graphql/stream`
const app = express();
app.use('/graphql/stream', handler);
app.listen(4000);
console.log('Listening to port 4000');
#graphql-sse/server
Provides a server handler for GraphQL subscription. However, the HTTP handling is up to u depending of the framework you use.
Disclaimer: I am the author of the #graphql-sse packages
Here is an example with express.
import express, { RequestHandler } from "express";
import {
getGraphQLParameters,
processSubscription,
} from "#graphql-sse/server";
import { schema } from "./schema";
const app = express();
app.use(express.json());
app.post(path, async (req, res, next) => {
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};
const { operationName, query, variables } = getGraphQLParameters(request);
if (!query) {
return next();
}
const result = await processSubscription({
operationName,
query,
variables,
request: req,
schema,
});
if (result.type === RESULT_TYPE.NOT_SUBSCRIPTION) {
return next();
} else if (result.type === RESULT_TYPE.ERROR) {
result.headers.forEach(({ name, value }) => res.setHeader(name, value));
res.status(result.status);
res.json(result.payload);
} else if (result.type === RESULT_TYPE.EVENT_STREAM) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
});
result.subscribe((data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
req.on('close', () => {
result.unsubscribe();
});
}
});
Clients
The two packages mentioned above have companion clients. Because of the limitation of the EventSource API, both packages implement a custom client that provides options for sending HTTP Headers, payload with post, what the EvenSource API does not support. The graphql-sse comes together with it client while the #graphql-sse/server has companion clients in a separate packages.
graphql-sse client example
import { createClient } from 'graphql-sse';
const client = createClient({
// singleConnection: true, use "single connection mode" instead of the default "distinct connection mode"
url: 'http://localhost:4000/graphql/stream',
});
// query
const result = await new Promise((resolve, reject) => {
let result;
client.subscribe(
{
query: '{ hello }',
},
{
next: (data) => (result = data),
error: reject,
complete: () => resolve(result),
},
);
});
// subscription
const onNext = () => {
/* handle incoming values */
};
let unsubscribe = () => {
/* complete the subscription */
};
await new Promise((resolve, reject) => {
unsubscribe = client.subscribe(
{
query: 'subscription { greetings }',
},
{
next: onNext,
error: reject,
complete: resolve,
},
);
});
;
#graphql-sse/client
A companion of the #graphql-sse/server.
Example
import {
SubscriptionClient,
SubscriptionClientOptions,
} from '#graphql-sse/client';
const subscriptionClient = SubscriptionClient.create({
graphQlSubscriptionUrl: 'http://some.host/graphl/subscriptions'
});
const subscription = subscriptionClient.subscribe(
{
query: 'subscription { greetings }',
}
)
const onNext = () => {
/* handle incoming values */
};
const onError = () => {
/* handle incoming errors */
};
subscription.susbscribe(onNext, onError)
#gaphql-sse/apollo-client
A companion package of the #graph-sse/server package for Apollo Client.
import { split, HttpLink, ApolloClient, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { ServerSentEventsLink } from '#graphql-sse/apollo-client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const sseLink = new ServerSentEventsLink({
graphQlSubscriptionUrl: 'http://localhost:4000/graphql',
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
sseLink,
httpLink
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
If you're using Apollo, they support automatic persisted queries (abbreviated APQ in the docs). If you're not using Apollo, the implementation shouldn't be too bad in any language. I'd recommend following their conventions just so your clients can use Apollo if they want.
The first time any client makes an EventSource request with a hash of the query, it'll fail, then retry the request with the full payload to a regular GraphQL endpoint. If APQ is enabled on the server, subsequent GET requests from all clients with query parameters will execute as planned.
Once you've solved that problem, you just have to make a server-sent events transport for GraphQL (should be easy considering the subscribe function just returns an AsyncIterator)
I'm looking into doing this at my company because some frontend developers like how easy EventSource is to deal with.
There are two things at play here: the SSE connection and the GraphQL endpoint. The endpoint has a spec to follow, so just returning SSE from a subscription request is not done and needs a GET request anyway. So the two have to be separate.
How about letting the client open an SSE channel via /graphql-sse, which creates a channel token. Using this token the client can then request subscriptions and the events will arrive via the chosen channel.
The token could be sent as the first event on the SSE channel, and to pass the token to the query, it can be provided by the client in a cookie, a request header or even an unused query variable.
Alternatively, the server can store the last opened channel in session storage (limiting the client to a single channel).
If no channel is found, the query fails. If the channel closes, the client can open it again, and either pass the token in the query string/cookie/header or let the session storage handle it.
I have a Graph QL server running (Apollo Server 2) and the API behind it requires every request to include a token.
Currently the token comes from HTTP Request Cookie. This was simple enough to work. When the request comes in, grab the cookie from the header and pass it along to the HTTP request to be sent to the API server through the resolvers.
I'd like to make it so a GraphQL client can pass this token along through the POST query itself.
Basically wondering if I can define a global GQL variable of some sort. "All queries, this variable is required."
I had a similar implementation in Typescript, and in order to achieve something like this, I've define an object:
const globalInput = {
token: {
type: GraphQLString;
}
}
And then use it in your GraphQLObjectType:
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => ({
myObject: {
type: MyTypeObject,
args: { ...globalInput },
resolve: (source: any, args: any) => {
// global input values can be access in args
// ex: args.token
return {}
}
}
})
})
The problem is that I need to extend it(...globalInput) it in every object type.
But it does the job.