Support HOC cases in server entries (#47379)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
6a6977cb71
commit
e6a3bab489
11 changed files with 366 additions and 62 deletions
|
@ -47,6 +47,7 @@ pub fn server_actions<C: Comments>(
|
|||
closure_idents: Default::default(),
|
||||
action_idents: Default::default(),
|
||||
exported_idents: Default::default(),
|
||||
inlined_action_idents: Default::default(),
|
||||
|
||||
annotations: Default::default(),
|
||||
extra_items: Default::default(),
|
||||
|
@ -73,6 +74,7 @@ struct ServerActions<C: Comments> {
|
|||
in_action_closure: bool,
|
||||
closure_idents: Vec<Id>,
|
||||
action_idents: Vec<Name>,
|
||||
inlined_action_idents: Vec<(Id, Id)>,
|
||||
|
||||
// (ident, export name)
|
||||
exported_idents: Vec<(Id, String)>,
|
||||
|
@ -125,11 +127,17 @@ impl<C: Comments> ServerActions<C> {
|
|||
ident: &Ident,
|
||||
function: Option<&mut Box<Function>>,
|
||||
arrow: Option<&mut ArrowExpr>,
|
||||
call_expr_and_ident: Option<(&mut CallExpr, CallExpr, Ident)>,
|
||||
return_paren: bool,
|
||||
) -> (Option<Box<ParenExpr>>, Option<Box<Function>>) {
|
||||
let action_name: JsWord = gen_ident(&mut self.ident_cnt);
|
||||
let action_ident = private_ident!(action_name.clone());
|
||||
|
||||
if !self.in_action_file {
|
||||
self.inlined_action_idents
|
||||
.push((ident.to_id(), action_ident.to_id()));
|
||||
}
|
||||
|
||||
let export_name: JsWord = if self.in_default_export_decl {
|
||||
"default".into()
|
||||
} else {
|
||||
|
@ -140,7 +148,7 @@ impl<C: Comments> ServerActions<C> {
|
|||
self.export_actions.push(export_name.to_string());
|
||||
|
||||
// If it's already a top level function, we don't need to hoist it.
|
||||
if self.top_level && arrow.is_none() {
|
||||
if self.top_level && arrow.is_none() && call_expr_and_ident.is_none() {
|
||||
annotate_ident_as_action(
|
||||
&mut self.annotations,
|
||||
ident.clone(),
|
||||
|
@ -148,6 +156,7 @@ impl<C: Comments> ServerActions<C> {
|
|||
self.file_name.to_string(),
|
||||
export_name.to_string(),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// export const $ACTION_myAction = myAction;
|
||||
|
@ -205,6 +214,7 @@ impl<C: Comments> ServerActions<C> {
|
|||
self.file_name.to_string(),
|
||||
export_name.to_string(),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
|
||||
|
@ -304,6 +314,7 @@ impl<C: Comments> ServerActions<C> {
|
|||
self.file_name.to_string(),
|
||||
export_name.to_string(),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
f.body.visit_mut_with(&mut ClosureReplacer {
|
||||
|
@ -391,6 +402,60 @@ impl<C: Comments> ServerActions<C> {
|
|||
}
|
||||
|
||||
return (None, Some(Box::new(new_fn)));
|
||||
} else if let Some((c, original_call, inner_action_ident)) = call_expr_and_ident {
|
||||
let mut arrow_annotations = Vec::new();
|
||||
annotate_ident_as_action(
|
||||
&mut arrow_annotations,
|
||||
ident.clone(),
|
||||
vec![],
|
||||
self.file_name.to_string(),
|
||||
export_name.to_string(),
|
||||
true,
|
||||
Some(inner_action_ident),
|
||||
);
|
||||
|
||||
self.extra_items
|
||||
.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
span: DUMMY_SP,
|
||||
decl: Decl::Var(Box::new(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: Default::default(),
|
||||
decls: vec![VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: action_ident.into(),
|
||||
init: Some(Box::new(Expr::Call(c.clone()))),
|
||||
definite: Default::default(),
|
||||
}],
|
||||
})),
|
||||
})));
|
||||
|
||||
// Create a paren expr to wrap all annotations:
|
||||
// ($ACTION = hoc(...), $ACTION.$$id = "..", .., $ACTION)
|
||||
let mut exprs = vec![Box::new(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP,
|
||||
left: PatOrExpr::Pat(Box::new(Pat::Ident(ident.clone().into()))),
|
||||
op: op!("="),
|
||||
right: Box::new(Expr::Call(original_call)),
|
||||
}))];
|
||||
exprs.extend(arrow_annotations.into_iter().map(|a| {
|
||||
if let Stmt::Expr(ExprStmt { expr, .. }) = a {
|
||||
expr
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}));
|
||||
exprs.push(Box::new(Expr::Ident(ident.clone())));
|
||||
|
||||
let new_paren = ParenExpr {
|
||||
span: DUMMY_SP,
|
||||
expr: Box::new(Expr::Seq(SeqExpr {
|
||||
span: DUMMY_SP,
|
||||
exprs,
|
||||
})),
|
||||
};
|
||||
|
||||
return (Some(Box::new(new_paren)), None);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,6 +565,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
|
|||
&f.ident,
|
||||
Some(&mut f.function),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
|
@ -611,73 +677,143 @@ impl<C: Comments> VisitMut for ServerActions<C> {
|
|||
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
if !self.in_action_file {
|
||||
match n {
|
||||
Expr::Arrow(a) => {
|
||||
let is_action_fn = self.get_action_info(
|
||||
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
|
||||
Some(block)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
true,
|
||||
);
|
||||
if self.in_action_file {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_action_fn {
|
||||
// We need to give a name to the arrow function
|
||||
// action and hoist it to the top.
|
||||
let action_name = gen_ident(&mut self.ident_cnt);
|
||||
let ident = private_ident!(action_name);
|
||||
match n {
|
||||
Expr::Arrow(a) => {
|
||||
let is_action_fn = self.get_action_info(
|
||||
if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
|
||||
Some(block)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
let (maybe_new_paren, _) = self.add_action_annotations_and_maybe_hoist(
|
||||
&ident,
|
||||
None,
|
||||
Some(a),
|
||||
true,
|
||||
);
|
||||
|
||||
*n = attach_name_to_expr(
|
||||
ident,
|
||||
if let Some(new_paren) = maybe_new_paren {
|
||||
Expr::Paren(*new_paren)
|
||||
} else {
|
||||
Expr::Arrow(a.clone())
|
||||
},
|
||||
&mut self.extra_items,
|
||||
);
|
||||
}
|
||||
if !is_action_fn {
|
||||
return;
|
||||
}
|
||||
Expr::Fn(f) => {
|
||||
let is_action_fn = self.get_action_info(f.function.body.as_mut(), true);
|
||||
|
||||
if is_action_fn {
|
||||
let ident = match f.ident.as_mut() {
|
||||
None => {
|
||||
let action_name = gen_ident(&mut self.ident_cnt);
|
||||
let ident = Ident::new(action_name, DUMMY_SP);
|
||||
f.ident.insert(ident)
|
||||
}
|
||||
Some(i) => i,
|
||||
};
|
||||
// We need to give a name to the arrow function
|
||||
// action and hoist it to the top.
|
||||
let action_name = gen_ident(&mut self.ident_cnt);
|
||||
let ident = private_ident!(action_name);
|
||||
|
||||
let (maybe_new_paren, _) = self.add_action_annotations_and_maybe_hoist(
|
||||
ident,
|
||||
Some(&mut f.function),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
let (maybe_new_paren, _) =
|
||||
self.add_action_annotations_and_maybe_hoist(&ident, None, Some(a), None, true);
|
||||
|
||||
*n = attach_name_to_expr(
|
||||
ident,
|
||||
if let Some(new_paren) = maybe_new_paren {
|
||||
Expr::Paren(*new_paren)
|
||||
} else {
|
||||
Expr::Arrow(a.clone())
|
||||
},
|
||||
&mut self.extra_items,
|
||||
);
|
||||
}
|
||||
Expr::Fn(f) => {
|
||||
let is_action_fn = self.get_action_info(f.function.body.as_mut(), true);
|
||||
|
||||
if !is_action_fn {
|
||||
return;
|
||||
}
|
||||
let ident = match f.ident.as_mut() {
|
||||
None => {
|
||||
let action_name = gen_ident(&mut self.ident_cnt);
|
||||
let ident = Ident::new(action_name, DUMMY_SP);
|
||||
f.ident.insert(ident)
|
||||
}
|
||||
Some(i) => i,
|
||||
};
|
||||
|
||||
let (maybe_new_paren, _) = self.add_action_annotations_and_maybe_hoist(
|
||||
ident,
|
||||
Some(&mut f.function),
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
|
||||
if let Some(new_paren) = maybe_new_paren {
|
||||
*n = attach_name_to_expr(
|
||||
ident.clone(),
|
||||
Expr::Paren(*new_paren),
|
||||
&mut self.extra_items,
|
||||
);
|
||||
}
|
||||
}
|
||||
Expr::Call(c) => {
|
||||
// Here we need to handle HOCs that wrap actions, e.g.:
|
||||
// withValidator(($ACTION = async function () { ... }, ...))
|
||||
|
||||
// For now, we only handle the case where the HOC has a single argument:
|
||||
// the action function.
|
||||
if c.args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ExprOrSpread {
|
||||
expr:
|
||||
box Expr::Paren(ParenExpr {
|
||||
expr: box Expr::Seq(seq_expr),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) = c.args.first_mut()
|
||||
{
|
||||
if let Some(box Expr::Assign(AssignExpr {
|
||||
left: PatOrExpr::Pat(box Pat::Ident(pat_id)),
|
||||
..
|
||||
})) = seq_expr.exprs.first_mut()
|
||||
{
|
||||
let maybe_action_ident = self
|
||||
.inlined_action_idents
|
||||
.iter()
|
||||
.find(|id| id.0 == pat_id.id.to_id());
|
||||
if let Some(action_ident) = maybe_action_ident {
|
||||
// This is a HOC that wraps an
|
||||
// action.
|
||||
// We need to give a name to the result
|
||||
// action and hoist it to the top.
|
||||
let action_name = gen_ident(&mut self.ident_cnt);
|
||||
let ident = private_ident!(action_name);
|
||||
|
||||
let mut new_call = CallExpr {
|
||||
span: DUMMY_SP,
|
||||
callee: c.callee.clone(),
|
||||
args: vec![ExprOrSpread {
|
||||
spread: None,
|
||||
expr: Box::new(Expr::Ident(action_ident.1.clone().into())),
|
||||
}],
|
||||
type_args: Default::default(),
|
||||
};
|
||||
|
||||
let (maybe_new_paren, _) = self.add_action_annotations_and_maybe_hoist(
|
||||
&ident,
|
||||
None,
|
||||
None,
|
||||
Some((&mut new_call, c.clone(), action_ident.0.clone().into())),
|
||||
true,
|
||||
);
|
||||
|
||||
if let Some(new_paren) = maybe_new_paren {
|
||||
*n = attach_name_to_expr(
|
||||
ident.clone(),
|
||||
Expr::Paren(*new_paren),
|
||||
ident,
|
||||
if let Some(new_paren) = maybe_new_paren {
|
||||
// Keep the original $$bound value.
|
||||
Expr::Paren(*new_paren)
|
||||
} else {
|
||||
Expr::Call(c.clone())
|
||||
},
|
||||
&mut self.extra_items,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -720,6 +856,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
|
|||
match &**init {
|
||||
Expr::Fn(_f) => {}
|
||||
Expr::Arrow(_a) => {}
|
||||
Expr::Call(_c) => {}
|
||||
_ => {
|
||||
disallowed_export_span = *span;
|
||||
}
|
||||
|
@ -813,6 +950,20 @@ impl<C: Comments> VisitMut for ServerActions<C> {
|
|||
// export default foo
|
||||
self.exported_idents.push((ident.to_id(), "default".into()));
|
||||
}
|
||||
Expr::Call(call) => {
|
||||
// export default fn()
|
||||
let new_ident =
|
||||
Ident::new(gen_ident(&mut self.ident_cnt), DUMMY_SP);
|
||||
|
||||
self.exported_idents
|
||||
.push((new_ident.to_id(), "default".into()));
|
||||
|
||||
*default_expr.expr = attach_name_to_expr(
|
||||
new_ident,
|
||||
Expr::Call(call.clone()),
|
||||
&mut self.extra_items,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
disallowed_export_span = default_expr.span;
|
||||
}
|
||||
|
@ -857,6 +1008,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
|
|||
self.file_name.to_string(),
|
||||
export_name.to_string(),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
if !self.config.is_server {
|
||||
let params_ident = private_ident!("args");
|
||||
|
@ -1078,6 +1230,7 @@ fn annotate_ident_as_action(
|
|||
file_name: String,
|
||||
export_name: String,
|
||||
has_bound: bool,
|
||||
re_annotate_action: Option<Ident>,
|
||||
) {
|
||||
// myAction.$$typeof = Symbol.for('react.server.reference');
|
||||
annotations.push(annotate(
|
||||
|
@ -1109,11 +1262,23 @@ fn annotate_ident_as_action(
|
|||
annotations.push(annotate(
|
||||
&ident,
|
||||
"$$bound",
|
||||
ArrayLit {
|
||||
span: DUMMY_SP,
|
||||
elems: bound,
|
||||
}
|
||||
.into(),
|
||||
if let Some(re_annotate_ident) = re_annotate_action {
|
||||
Box::new(Expr::Member(MemberExpr {
|
||||
span: DUMMY_SP,
|
||||
obj: Box::new(Expr::Ident(re_annotate_ident)),
|
||||
prop: MemberProp::Ident(Ident {
|
||||
sym: "$$bound".into(),
|
||||
span: DUMMY_SP,
|
||||
optional: false,
|
||||
}),
|
||||
}))
|
||||
} else {
|
||||
ArrayLit {
|
||||
span: DUMMY_SP,
|
||||
elems: bound,
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
|
||||
// If an action doesn't have any bound values, we add a special property
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { validator, another } from 'auth'
|
||||
|
||||
const x = 1
|
||||
|
||||
export default function Page () {
|
||||
const y = 1
|
||||
return <Foo action={validator(async function (z) {
|
||||
'use server'
|
||||
return x + y + z
|
||||
})} />
|
||||
}
|
||||
|
||||
validator(async () => {
|
||||
'use server'
|
||||
})
|
||||
|
||||
another(validator(async () => {
|
||||
'use server'
|
||||
}))
|
|
@ -0,0 +1,32 @@
|
|||
/* __next_internal_action_entry_do_not_use__ $$ACTION_1,$$ACTION_3,$$ACTION_5,$$ACTION_7,$$ACTION_9,$$ACTION_11,$$ACTION_13 */ import { validator, another } from 'auth';
|
||||
const x = 1;
|
||||
export default function Page() {
|
||||
const y = 1;
|
||||
return <Foo action={$$ACTION_2 = validator(($$ACTION_0 = async function(z) {
|
||||
return $$ACTION_1($$ACTION_0.$$bound);
|
||||
}, $$ACTION_0.$$typeof = Symbol.for("react.server.reference"), $$ACTION_0.$$id = "188d5d945750dc32e2c842b93c75a65763d4a922", $$ACTION_0.$$bound = [
|
||||
y
|
||||
], $$ACTION_0)), $$ACTION_2.$$typeof = Symbol.for("react.server.reference"), $$ACTION_2.$$id = "56a859f462d35a297c46a1bbd1e6a9058c104ab8", $$ACTION_2.$$bound = $$ACTION_0.$$bound, $$ACTION_2}/>;
|
||||
}
|
||||
export async function $$ACTION_1(closure, z = closure[1]) {
|
||||
return x + closure[0] + z;
|
||||
}
|
||||
var $$ACTION_0;
|
||||
export const $$ACTION_3 = validator($$ACTION_1);
|
||||
var $$ACTION_2;
|
||||
$$ACTION_6 = validator(($$ACTION_4 = async ()=>$$ACTION_5($$ACTION_4.$$bound), $$ACTION_4.$$typeof = Symbol.for("react.server.reference"), $$ACTION_4.$$id = "1383664d1dc2d9cfe33b88df3fa0eaffef8b99bc", $$ACTION_4.$$bound = [
|
||||
y
|
||||
], $$ACTION_4)), $$ACTION_6.$$typeof = Symbol.for("react.server.reference"), $$ACTION_6.$$id = "faf016739650cb4995340c9d9ab06ce1c9407fa0", $$ACTION_6.$$bound = $$ACTION_4.$$bound, $$ACTION_6;
|
||||
export const $$ACTION_5 = async (closure)=>{};
|
||||
var $$ACTION_4;
|
||||
export const $$ACTION_7 = validator($$ACTION_5);
|
||||
var $$ACTION_6;
|
||||
$$ACTION_12 = another(($$ACTION_10 = validator(($$ACTION_8 = async ()=>$$ACTION_9($$ACTION_8.$$bound), $$ACTION_8.$$typeof = Symbol.for("react.server.reference"), $$ACTION_8.$$id = "0d0ca9684921f1c6dc36a2ec55ce57ba31407820", $$ACTION_8.$$bound = [
|
||||
y
|
||||
], $$ACTION_8)), $$ACTION_10.$$typeof = Symbol.for("react.server.reference"), $$ACTION_10.$$id = "dd70487b74c2c510c55e3e68aa3614cfa780850d", $$ACTION_10.$$bound = $$ACTION_8.$$bound, $$ACTION_10)), $$ACTION_12.$$typeof = Symbol.for("react.server.reference"), $$ACTION_12.$$id = "57cbac1f8911efd298cb885cba89919b14153dc1", $$ACTION_12.$$bound = $$ACTION_10.$$bound, $$ACTION_12;
|
||||
export const $$ACTION_9 = async (closure)=>{};
|
||||
var $$ACTION_8;
|
||||
export const $$ACTION_11 = validator($$ACTION_9);
|
||||
var $$ACTION_10;
|
||||
export const $$ACTION_13 = another($$ACTION_11);
|
||||
var $$ACTION_12;
|
|
@ -0,0 +1,6 @@
|
|||
'use server'
|
||||
|
||||
import { validator } from 'auth'
|
||||
|
||||
export const action = validator(async () => {})
|
||||
export default validator(async () => {})
|
|
@ -0,0 +1,17 @@
|
|||
/* __next_internal_action_entry_do_not_use__ action,default */ import { validator } from 'auth';
|
||||
export const action = validator(async ()=>{});
|
||||
export default $$ACTION_0 = validator(async ()=>{});
|
||||
var $$ACTION_0;
|
||||
import ensureServerEntryExports from "private-next-rsc-action-proxy";
|
||||
ensureServerEntryExports([
|
||||
action,
|
||||
$$ACTION_0
|
||||
]);
|
||||
action.$$typeof = Symbol.for("react.server.reference");
|
||||
action.$$id = "f14702b5a021dd117f7ec7a3c838f397c2046d3b";
|
||||
action.$$bound = [];
|
||||
action.$$with_bound = false;
|
||||
$$ACTION_0.$$typeof = Symbol.for("react.server.reference");
|
||||
$$ACTION_0.$$id = "c18c215a6b7cdc64bf709f3a714ffdef1bf9651d";
|
||||
$$ACTION_0.$$bound = [];
|
||||
$$ACTION_0.$$with_bound = false;
|
|
@ -14,5 +14,9 @@ export async function callServer(id: string, bound: any[]) {
|
|||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(await res.text())
|
||||
}
|
||||
|
||||
return (await res.json())[0]
|
||||
}
|
||||
|
|
|
@ -1398,6 +1398,14 @@ export async function renderToHTMLOrFlight(
|
|||
res.statusCode = 404
|
||||
return new RenderResult(await bodyResult({ asNotFound: true }))
|
||||
}
|
||||
|
||||
if (isFetchAction) {
|
||||
res.statusCode = 500
|
||||
return new RenderResult(
|
||||
(err as Error)?.message ?? 'Internal Server Error'
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -65,6 +65,25 @@ createNextDescribe(
|
|||
}, 'my-not-found')
|
||||
})
|
||||
|
||||
it('should support hoc auth wrappers', async () => {
|
||||
const browser = await next.browser('/header')
|
||||
await await browser.eval(`document.cookie = 'auth=0'`)
|
||||
|
||||
await browser.elementByCss('#authed').click()
|
||||
|
||||
await check(() => {
|
||||
return browser.elementByCss('h1').text()
|
||||
}, 'Error: Unauthorized request')
|
||||
|
||||
await await browser.eval(`document.cookie = 'auth=1'`)
|
||||
|
||||
await browser.elementByCss('#authed').click()
|
||||
|
||||
await check(() => {
|
||||
return browser.elementByCss('h1').text()
|
||||
}, 'HELLO, WORLD')
|
||||
})
|
||||
|
||||
it('should support importing actions in client components', async () => {
|
||||
const browser = await next.browser('/client')
|
||||
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
import UI from './ui'
|
||||
|
||||
import { getCookie, getHeader } from './actions'
|
||||
import { validator } from './validator'
|
||||
|
||||
export default function Page() {
|
||||
return <UI getCookie={getCookie} getHeader={getHeader} />
|
||||
return (
|
||||
<UI
|
||||
getCookie={getCookie}
|
||||
getHeader={getHeader}
|
||||
getAuthedUppercase={validator(async (str) => {
|
||||
'use server'
|
||||
return str.toUpperCase()
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function UI({ getCookie, getHeader }) {
|
||||
export default function UI({ getCookie, getHeader, getAuthedUppercase }) {
|
||||
const [result, setResult] = useState('')
|
||||
|
||||
return (
|
||||
|
@ -29,6 +29,19 @@ export default function UI({ getCookie, getHeader }) {
|
|||
>
|
||||
getHeader
|
||||
</button>
|
||||
<button
|
||||
id="authed"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const res = await getAuthedUppercase('hello, world')
|
||||
setResult(res)
|
||||
} catch (err) {
|
||||
setResult('Error: ' + err.message)
|
||||
}
|
||||
}}
|
||||
>
|
||||
getAuthedUppercase
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
11
test/e2e/app-dir/actions/app/header/validator.js
Normal file
11
test/e2e/app-dir/actions/app/header/validator.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { cookies } from 'next/headers'
|
||||
|
||||
export function validator(action) {
|
||||
return async function (...args) {
|
||||
const auth = cookies().get('auth')
|
||||
if (auth?.value !== '1') {
|
||||
throw new Error('Unauthorized request')
|
||||
}
|
||||
return action(...args)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue