Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Add to your `pubspec.yaml`:

```yaml
dependencies:
spark_framework: ^1.0.0-alpha.1
spark_framework: ^1.0.0-alpha.2

dev_dependencies:
spark_generator: ^1.0.0-alpha.1
spark_generator: ^1.0.0-alpha.3
build_runner: ^2.4.0
```

Expand Down
4 changes: 4 additions & 0 deletions packages/spark/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.0-alpha.2

- Added support for cookies

## 1.0.0-alpha.1

- Initial version
55 changes: 44 additions & 11 deletions packages/spark/example/lib/spark_router.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/spark/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@ export 'src/page/page.dart';
export 'src/endpoint/endpoint.dart';
export 'src/errors/errors.dart';
export 'src/http/content_type.dart';
export 'src/http/cookie.dart';
1 change: 1 addition & 0 deletions packages/spark/lib/spark.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ export 'src/style/style.dart';
export 'src/style/css_types/css_types.dart';
export 'src/errors/errors.dart';
export 'src/http/content_type.dart';
export 'src/http/cookie.dart';
113 changes: 113 additions & 0 deletions packages/spark/lib/src/http/cookie.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/// HTTP Cookie helper.
library;

/// Represents the SameSite attribute of a cookie.
enum SameSite {
/// The cookie is withheld on cross-site requests.
strict('Strict'),

/// The cookie is sent on some cross-site requests (default).
lax('Lax'),

/// The cookie is sent on all requests.
none('None');

final String value;
const SameSite(this.value);
}

/// Represents an HTTP Cookie.
class Cookie {
/// The name of the cookie.
final String name;

/// The value of the cookie.
final String value;

/// The expiry date of the cookie.
final DateTime? expires;

/// The maximum age of the cookie in seconds.
final int? maxAge;

/// The domain the cookie belongs to.
final String? domain;

/// The path the cookie belongs to.
final String? path;

/// Whether the cookie is secure (HTTPS only).
final bool secure;

/// Whether the cookie is HTTP only (not accessible via JavaScript).
final bool httpOnly;

/// The SameSite policy for the cookie.
final SameSite? sameSite;

/// Creates a new [Cookie].
const Cookie(
this.name,
this.value, {
this.expires,
this.maxAge,
this.domain,
this.path,
this.secure = false,
this.httpOnly = false,
this.sameSite,
});

/// Formats the cookie as a Set-Cookie header value.
@override
String toString() {
final buffer = StringBuffer();
buffer.write('$name=$value');

if (expires != null) {
buffer.write('; Expires=${_formatHttpDate(expires!)}');
}
if (maxAge != null) {
buffer.write('; Max-Age=$maxAge');
}
if (domain != null) {
buffer.write('; Domain=$domain');
}
if (path != null) {
buffer.write('; Path=$path');
}
if (secure) {
buffer.write('; Secure');
}
if (httpOnly) {
buffer.write('; HttpOnly');
}
if (sameSite != null) {
buffer.write('; SameSite=${sameSite!.value}');
}

return buffer.toString();
}

// Simple HTTP date implementation to avoid dart:io dependency in shared code
String _formatHttpDate(DateTime date) {
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];

final d = date.toUtc();
return '${days[d.weekday - 1]}, ${d.day.toString().padLeft(2, '0')} ${months[d.month - 1]} ${d.year} ${d.hour.toString().padLeft(2, '0')}:${d.minute.toString().padLeft(2, '0')}:${d.second.toString().padLeft(2, '0')} GMT';
}
}
73 changes: 57 additions & 16 deletions packages/spark/lib/src/page/page_response.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import '../http/cookie.dart';

/// Sealed class representing possible responses from a page loader.
///
/// A loader can return one of:
Expand Down Expand Up @@ -57,8 +59,16 @@ final class PageData<T> extends PageResponse<T> {
/// Additional HTTP headers for the response.
final Map<String, String> headers;

/// List of cookies to set in the response.
final List<Cookie> cookies;

/// Creates a page data response with the given [data].
const PageData(this.data, {this.statusCode = 200, this.headers = const {}});
const PageData(
this.data, {
this.statusCode = 200,
this.headers = const {},
this.cookies = const [],
});
}

/// Response indicating a redirect to another URL.
Expand Down Expand Up @@ -100,33 +110,45 @@ final class PageRedirect extends PageResponse<Never> {
/// Additional HTTP headers for the response.
final Map<String, String> headers;

/// Creates a redirect response to the given [location].
///
/// List of cookies to set in the response.
final List<Cookie> cookies;

/// Creates a redirect response to the given [location].
///
/// Defaults to status code 302 (Found).
const PageRedirect(
this.location, {
this.statusCode = 302,
this.headers = const {},
this.cookies = const [],
});

/// Creates a permanent redirect (301 Moved Permanently).
///
/// Use this when a resource has permanently moved to a new location.
/// Browsers will cache this redirect.
const PageRedirect.permanent(String location)
: this(location, statusCode: 301);
const PageRedirect.permanent(
String location, {
List<Cookie> cookies = const [],
}) : this(location, statusCode: 301, cookies: cookies);

/// Creates a temporary redirect (307 Temporary Redirect).
///
/// Use this for temporary redirects that preserve the HTTP method.
const PageRedirect.temporary(String location)
: this(location, statusCode: 307);
const PageRedirect.temporary(
String location, {
List<Cookie> cookies = const [],
}) : this(location, statusCode: 307, cookies: cookies);

/// Creates a "See Other" redirect (303 See Other).
///
/// Use this to redirect after a POST request to a GET endpoint.
const PageRedirect.seeOther(String location)
: this(location, statusCode: 303);
const PageRedirect.seeOther(
String location, {
List<Cookie> cookies = const [],
}) : this(location, statusCode: 303, cookies: cookies);
}

/// Response for rendering an error page.
Expand Down Expand Up @@ -170,21 +192,40 @@ final class PageError extends PageResponse<Never> {
/// Creates an error response with the given [message].
///
/// Defaults to status code 500 (Internal Server Error).
const PageError(this.message, {this.statusCode = 500, this.data = const {}});
/// List of cookies to set in the response.
final List<Cookie> cookies;

/// Creates an error response with the given [message].
///
/// Defaults to status code 500 (Internal Server Error).
const PageError(
this.message, {
this.statusCode = 500,
this.data = const {},
this.cookies = const [],
});

/// Creates a 404 Not Found error.
const PageError.notFound([String message = 'Page not found'])
: this(message, statusCode: 404);
const PageError.notFound([
String message = 'Page not found',
List<Cookie> cookies = const [],
]) : this(message, statusCode: 404, cookies: cookies);

/// Creates a 403 Forbidden error.
const PageError.forbidden([String message = 'Access denied'])
: this(message, statusCode: 403);
const PageError.forbidden([
String message = 'Access denied',
List<Cookie> cookies = const [],
]) : this(message, statusCode: 403, cookies: cookies);

/// Creates a 400 Bad Request error.
const PageError.badRequest([String message = 'Bad request'])
: this(message, statusCode: 400);
const PageError.badRequest([
String message = 'Bad request',
List<Cookie> cookies = const [],
]) : this(message, statusCode: 400, cookies: cookies);

/// Creates a 401 Unauthorized error.
const PageError.unauthorized([String message = 'Unauthorized'])
: this(message, statusCode: 401);
const PageError.unauthorized([
String message = 'Unauthorized',
List<Cookie> cookies = const [],
]) : this(message, statusCode: 401, cookies: cookies);
}
2 changes: 1 addition & 1 deletion packages/spark/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: spark_framework
description: Lightweight isomorphic SSR framework for Dart with Custom Elements and Declarative Shadow DOM
version: 1.0.0-alpha.1
version: 1.0.0-alpha.2
resolution: workspace

homepage: https://spark.kleak.dev
Expand Down
Loading