24 |
25 |
26 | >
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
18 |
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: "Sample lambda streaming response SAM template using nextjs"
4 | Globals:
5 | Function:
6 | Timeout: 60
7 |
8 | Resources:
9 | StreamingNextjsFunction:
10 | Type: AWS::Serverless::Function
11 | Properties:
12 | MemorySize: 256
13 | PackageType: Image
14 | Architectures:
15 | - x86_64
16 | Environment:
17 | Variables:
18 | AWS_LWA_INVOKE_MODE: response_stream
19 | FunctionUrlConfig:
20 | AuthType: NONE
21 | InvokeMode: RESPONSE_STREAM
22 | Metadata:
23 | DockerTag: v1
24 | DockerContext: ./
25 | Dockerfile: Dockerfile
26 |
27 | Outputs:
28 | StreamingNextjsFunctionOutput:
29 | Description: "Streaming Nextjs Function ARN"
30 | Value: !GetAtt StreamingNextjsFunction.Arn
31 | StreamingNextjsFunctionUrlOutput:
32 | Description: "nextjs streaming response function url"
33 | Value: !GetAtt StreamingNextjsFunctionUrl.FunctionUrl
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nextjs response streaming example
2 |
3 | This example show how to use [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) to run a nextjs application with response streaming via a [AWS Lambda](https://aws.amazon.com/lambda) Function URL.
4 |
5 | ### Build and Deploy
6 |
7 | Run the following commands to build and deploy the application to lambda.
8 |
9 | ```bash
10 | sam build
11 |
12 | sam deploy --guided
13 | ```
14 | When the deployment completes, the Function URL will appear in the output list, which is the entrypoint for accessing
15 |
16 | ### Verify it works
17 |
18 | When you open the Function URL in a browser:
19 |
20 | - Primary product information will be loaded first at part of the initial response
21 |
22 | - Secondary, more personalized details (that might be slower) like recommended products and customer reviews are progressively streamed in.
23 |
24 |
25 | ### Thanks
26 |
27 | Page content and styles are powered by the [Next.js App Router Playground - Streaming with Suspense](https://app-dir.vercel.app/streaming).
28 |
29 | ### License
30 |
31 | This library is licensed under the MIT-0 License. See the LICENSE file.
32 |
--------------------------------------------------------------------------------
/ui/product-deal.tsx:
--------------------------------------------------------------------------------
1 | import { ProductCurrencySymbol } from '@/ui/product-currency-symbol';
2 | import { toUnit, type Dinero } from 'dinero.js';
3 |
4 | export const ProductDeal = ({
5 | price: priceRaw,
6 | discount: discountRaw,
7 | }: {
8 | price: Dinero;
9 | discount: {
10 | amount: Dinero;
11 | };
12 | }) => {
13 | const discount = toUnit(discountRaw.amount);
14 | const price = toUnit(priceRaw);
15 | const percent = Math.round(100 - (discount / price) * 100);
16 |
17 | return (
18 |
31 | Primary product information is loaded first as part of the initial
32 | response.
33 |
34 |
35 | Secondary, more personalized details (that might be slower) like
36 | ship date, other recommended products, and customer reviews are
37 | progressively streamed in.
38 |
81 | );
82 | };
83 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/app/api/products/route.ts:
--------------------------------------------------------------------------------
1 | import type { Product } from "@/app/api/products/product";
2 |
3 | export async function GET(request: Request) {
4 |
5 | const { searchParams } = new URL(request.url);
6 | const delay = searchParams.get('delay');
7 | if (delay) {
8 | await new Promise((resolve) => setTimeout(resolve, Number(delay)));
9 | }
10 |
11 | const id = searchParams.get('id');
12 | if (id) {
13 | let product = data.find((product) => product.id === id);
14 |
15 | const fields = searchParams.get('fields');
16 | if (product && fields) {
17 | product = fields.split(',').reduce((acc, field) => {
18 | // @ts-ignore
19 | acc[field] = product[field];
20 |
21 | return acc;
22 | }, {} as Product);
23 | }
24 | return new Response(JSON.stringify(product ?? null), {
25 | status: 200,
26 | headers: {
27 | 'content-type': 'application/json',
28 | },
29 | });
30 | }
31 |
32 | const filter = searchParams.get('filter');
33 | const products = filter
34 | ? data.filter((product) => product.id !== filter)
35 | : data;
36 |
37 | return new Response(JSON.stringify(products), {
38 | status: 200,
39 | headers: {
40 | 'content-type': 'application/json',
41 | },
42 | });
43 | }
44 |
45 | const data: Product[] = [
46 | {
47 | id: '1',
48 | stock: 2,
49 | rating: 5,
50 | name: 'Donec sit elit',
51 | description:
52 | 'Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis.',
53 | price: {
54 | amount: 4200,
55 | currency: { code: 'USD', base: 10, exponent: 2 },
56 | scale: 2,
57 | },
58 | isBestSeller: false,
59 | leadTime: 2,
60 | discount: { percent: 90, expires: 2 },
61 | image: 'https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f',
62 | imageBlur:
63 | 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAqgAwAEAAAAAQAAAAoAAAAA/8AAEQgACgAKAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8PDw8PD//bAEMBAgICBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/dAAQAAf/aAAwDAQACEQMRAD8A+3/HPx10jQPE0+k6ZrPh6TTtKsbi41R7nVUiu7WeMkQxi2H3lk2su4sCrjBHFd54c+InhvxJ4e0vxFa3aRw6pawXSKxG5VnQOAfcA81474z8G+ENU1OeXU9Dsbt/N8zdNbRSHfn72WU/N79a9U03TtPj061jjtYkRIkAARQAAowAMV2Sa7GsIH//2Q==',
64 | },
65 | {
66 | id: '2',
67 | stock: 5,
68 | rating: 4,
69 | name: 'Fusce commodo porta posuere',
70 | description:
71 | 'Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis.',
72 | price: {
73 | amount: 4600,
74 | currency: { code: 'USD', base: 10, exponent: 2 },
75 | scale: 2,
76 | },
77 | isBestSeller: false,
78 | leadTime: 1,
79 | image: 'https://images.unsplash.com/photo-1612547036242-77002603e5aa',
80 | imageBlur:
81 | 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAqgAwAEAAAAAQAAAAoAAAAA/8AAEQgACgAKAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8PDw8PD//bAEMBAgICBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/dAAQAAf/aAAwDAQACEQMRAD8AwNV+KvwHs7vSNLi8faJoy2VtcQatFLaSz3X2shBGyuUKjy23kgKwY4HStq5j0O4uZZ9Lfz7KR2aCQArviJyjYIyMrg4NXY7Cxddz20bE9SUU/wBKfsQcBQAPav6Gyrh+vQq1pyxMpKTuk+mr0Wr7/gf568TeI2DxlDDUqWXwpumrNpr3tIq7tFa6X67n/9k=',
82 | },
83 | {
84 | id: '3',
85 | stock: 3,
86 | rating: 3,
87 | name: 'Praesent tincidunt lectus',
88 | description:
89 | 'Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis.',
90 | price: {
91 | amount: 29200,
92 | currency: { code: 'USD', base: 10, exponent: 2 },
93 | scale: 2,
94 | },
95 | discount: { percent: 70, expires: 7 },
96 | isBestSeller: true,
97 | leadTime: 2,
98 | image: 'https://images.unsplash.com/photo-1496889050590-4db81f7fb62a',
99 | imageBlur:
100 | 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAqgAwAEAAAAAQAAAAoAAAAA/8AAEQgACgAKAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8PDw8PD//bAEMBAgICBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/dAAQAAf/aAAwDAQACEQMRAD8A+6vG/wC1t4J+GniLxP4L1Wynub7RrqWOQBJ1DvLbG9AVxC6ECHPO4DdhM7yBX2XpP2iTSrOROFaGMgegKivPPEGnafLrEckttE7yOu4siktgdzjmvTE4RQOAAKDSx//Z',
101 | },
102 | {
103 | id: '4',
104 | stock: 2,
105 | rating: 5,
106 | name: 'Morbi at viverra turpis',
107 | description:
108 | 'Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis.',
109 | price: {
110 | amount: 21200,
111 | currency: { code: 'USD', base: 10, exponent: 2 },
112 | scale: 2,
113 | },
114 | isBestSeller: false,
115 | leadTime: 2,
116 | image: 'https://images.unsplash.com/photo-1488241561087-799714b46586',
117 | imageBlur:
118 | 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAqgAwAEAAAAAQAAAAoAAAAA/8AAEQgACgAKAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8PDw8PD//bAEMBAgICBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/dAAQAAf/aAAwDAQACEQMRAD8Ay59W0U20sUGmapJf2hZZVR7TyWVbd5hKgd1k2yOohVeu4jHGTXBwa9PcQR3BhaAyqG8uQ/Om4Z2tjjI6HHGa2dVtrad3aeJJCOhZQf51TjACKAMAAV0u99z8bni6E4RUaVmt3fc//9k=',
119 | },
120 | {
121 | id: '5',
122 | stock: 1,
123 | rating: 4,
124 | name: 'Maecenas interdum',
125 | description:
126 | 'Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis.',
127 | price: {
128 | amount: 28700,
129 | currency: { code: 'USD', base: 10, exponent: 2 },
130 | scale: 2,
131 | },
132 | isBestSeller: false,
133 | leadTime: 4,
134 | image: 'https://images.unsplash.com/photo-1630936703945-7651b922d655',
135 | imageBlur:
136 | 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAqgAwAEAAAAAQAAAAoAAAAA/8AAEQgACgAKAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8PDw8PD//bAEMBAgICBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/dAAQAAf/aAAwDAQACEQMRAD8A9P8Aj/qni3w58aNel0PV9UENm3lraDUfLtpJJLeOMKlqwXYELGRX3/M3zD3/AE5tPDB+yQ7pFc7FySeTx1NeX+PfhF8J/FPjI+IfE3grRNX1UPC4u7zTba4uN0f3D5skbPlf4TnjtXvcdrbLGoEKAADjaKpQa3Z1Od9j/9k=',
137 | },
138 | ];
139 |
--------------------------------------------------------------------------------