diff --git a/README.md b/README.md index f2b72e5..3ec0c78 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,10 @@ This difference can be put into good use when we handle `updateFromClient` or `u - [encoders & decoders are auto-generated](https://github.com/choonkeat/elm-auto-encoder-decoder) in `src/Protocol/Auto.elm` ; also see [gotchas regarding imported types](https://github.com/choonkeat/elm-auto-encoder-decoder#dont-be-alarmed-with-i-cannot-find--variable-compiler-errors) - we're using `elm-auto-encoder-decoder` in `elm-webapp` only for convenience; you can switch it out for your own encoders & decoders. BUT if you continue using `elm-auto-encoder-decoder`, don't use them anywhere else (e.g. as encoder to save in db, exposed as part of your external api, etc...). Main reason being that the serialized format could change future releases of `elm-auto-encoder-decoder` and thus MUST NOT be relied on. +## How do I... + +- Support OAuth login? See https://github.com/choonkeat/elm-webapp-oauth-example#readme + ## License Copyright © 2021 Chew Choon Keat diff --git a/bin/elm-webapp b/bin/elm-webapp index 46093c8..c9477f5 100755 --- a/bin/elm-webapp +++ b/bin/elm-webapp @@ -30,7 +30,7 @@ fs.writeFileSync(dst + `/lambda.js`, clientContent('lambda.js')) fs.writeFileSync(src + `/Server.elm`, `port module Server exposing (..) import Webapp.Server -import Webapp.Server.HTTP exposing (Method(..), StatusCode(..)) +import Webapp.Server.HTTP exposing (Method(..), Request, Response, StatusCode(..)) import Json.Decode import Json.Encode import Platform exposing (Task) @@ -102,7 +102,7 @@ type alias ServerState = type Msg - = Msg + = OnHttpResponse Request (Result Response Response) @@ -127,8 +127,11 @@ init flags = update : Msg -> ServerState -> ( ServerState, Cmd Msg ) update msg serverState = case msg of - Msg -> - ( serverState, Cmd.none ) + OnHttpResponse request (Ok response) -> + ( serverState, writeResponse request response ) + + OnHttpResponse request (Err response) -> + ( serverState, writeResponse request response ) subscriptions : ServerState -> Sub Msg @@ -154,7 +157,7 @@ routeDecoder urlUrl = Nothing -updateFromRoute : ( Webapp.Server.HTTP.Method, Protocol.RequestContext, Maybe Route ) -> Time.Posix -> Webapp.Server.HTTP.Request -> ServerState -> ( ServerState, Cmd Msg ) +updateFromRoute : ( Method, Protocol.RequestContext, Maybe Route ) -> Time.Posix -> Request -> ServerState -> ( ServerState, Cmd Msg ) updateFromRoute ( method, ctx, route ) now request serverState = case ( method, ctx, route ) of ( GET, _, _ ) -> @@ -280,5 +283,5 @@ now execute: 3. make `) -function clientContent(key) { const dict = {"index.js":"global.XMLHttpRequest = require('xhr2')\n\n//\nconst fs = require('fs')\nconst crypto = require('crypto')\nconst hash = crypto.createHash('sha256')\nconst jsData = fs.readFileSync('public/assets/client.js', { encoding: 'utf8' })\nhash.update(jsData)\nconst jsSha = hash.digest('hex')\n//\n\n// regular elm initialization\nconst { Elm } = require('./build/Server.js')\nvar app = Elm.Server.init({\n flags: {\n assetsHost: process.env.ASSETS_HOST || '',\n jsSha: jsSha\n }\n})\n\nfunction loggerWith (logger, ...context) {\n return function (...messages) {\n logger(context, ...messages)\n /* context is wrapped in [ square brackets ] */\n }\n}\nconst log = loggerWith(console.log, 'js')\nconst httpServer = (process.env.LAMBDA\n ? require('./lambda.js').lambdaHttpServer\n : require('./node.js').nodeHttpServer\n)\nlog('httpServer', httpServer)\nexports.handler = httpServer({\n log: loggerWith(log, 'http'),\n app: app\n})\n","node.js":"function nodeHttpServer ({ app, log }) {\n log('nodeHttpServer')\n\n const fs = require('fs')\n const http = require('http')\n const fullUrl = require('full-url')\n const nodeStatic = require('node-static')\n const fileServer = new nodeStatic.Server('./public')\n const WebSocketServer = require('websocket').server\n\n // Start http server, accept requests and pass to Elm\n const server = http.createServer((req, res) => {\n const body = [] // https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/#request-body\n req.on('data', (chunk) => {\n body.push(chunk)\n }).on('end', () => {\n fileServer.serve(req, res, function (e) {\n // if static file is found by fileServer, serve it\n if (!e || e.status !== 404) return\n\n // otherwise, Webapp.Server will handle the request\n const contentType = req.headers['content-type'] || ''\n const encoding = (contentType.match(/; charset=(\\S+)/) || ['', 'utf8'])[1] // charset or default to \"utf8\"\n const bodyString = Buffer.concat(body).toString(encoding)\n app.ports.onHttpRequest.send({\n response: res, // this value will be used by \"onHttpResponse\" below\n method: req.method,\n url: fullUrl(req),\n path: req.url,\n body: bodyString,\n headers: req.headers\n })\n })\n }).resume()\n })\n app.ports.onHttpResponse.subscribe(({ statusCode, body, headers, request }) => {\n log('onHttpResponse status=', statusCode, Buffer.byteLength(body), 'bytes')\n request.response.writeHead(statusCode, { ...headers, 'Content-Length': Buffer.byteLength(body) })\n request.response.end(body)\n })\n\n if (app.ports.writeWs) {\n app.ports.writeWs.subscribe(({ key, connection, body }) => {\n log('[ws] writeWs key=', key, body)\n connection.sendUTF(body)\n })\n }\n\n var wsServer\n if (app.ports.onWebsocketEvent) {\n wsServer = new WebSocketServer({\n // WebSocket server is tied to a HTTP server. WebSocket request is just\n // an enhanced HTTP request. For more info http://tools.ietf.org/html/rfc6455#page-6\n httpServer: server\n })\n\n // Start websocket server, accept requests and pass to Elm\n wsServer.on('request', function (request) {\n log('[ws] Connection from origin ' + request.origin + '.', request.key)\n\n // accept connection - you should check 'request.origin' to make sure that\n // client is connecting from your website\n // (http://en.wikipedia.org/wiki/Same_origin_policy)\n if (request.origin !== 'http://localhost:8000' && false) {\n request.reject()\n log('[ws]', (new Date()) + ' Connection from origin ' + request.origin + ' rejected.')\n return\n }\n\n var connection = request.accept(null, request.origin)\n app.ports.onWebsocketEvent.send({\n open: connection,\n key: request.key,\n headers: (request.httpRequest || {}).headers\n })\n\n // user sent some message\n connection.on('message', function (payload) {\n log('[ws] payload', payload)\n app.ports.onWebsocketEvent.send({\n message: connection,\n key: request.key,\n payload: payload\n })\n })\n // user disconnected\n // https://github.com/theturtle32/WebSocket-Node/blob/1f7ffba2f7a6f9473bcb39228264380ce2772ba7/docs/WebSocketConnection.md#close\n connection.on('close', function (reasonCode, description) {\n log('[ws] close', reasonCode, description)\n app.ports.onWebsocketEvent.send({\n reasonCode,\n description,\n close: connection,\n key: request.key\n })\n })\n })\n }\n\n var port = process.env.PORT || 8000\n var listener = server.listen(port)\n log('server listening at', port, '...')\n\n // https://stackoverflow.com/a/35480020\n var lastSocketKey = 0\n var socketMap = {}\n listener.on('connection', function (socket) {\n var socketKey = ++lastSocketKey\n socketMap[socketKey] = socket\n socket.on('close', function () { delete socketMap[socketKey] })\n })\n\n const shutdown = (signal) => {\n log('signal received.')\n if (app.ports.onWebsocketEvent) {\n wsServer.unmount()\n log('[ws] server unmounted.')\n }\n server.close(() => {\n log('server closed.')\n process.exit(signal ? 0 : 1) // not great, but there are Timers dangling and won't quit\n })\n if (app.ports.onWebsocketEvent) {\n wsServer.shutDown()\n log('[ws] server shutdown.')\n }\n Object.keys(socketMap).forEach(function (socketKey) { socketMap[socketKey].destroy() })\n }\n\n process.on('SIGTERM', shutdown)\n process.on('SIGINT', shutdown)\n\n if (process.argv.indexOf('--watch') > -1) {\n try {\n fs.watch('./src', { recursive: true }, function (eventType, filename) {\n log('[watch]', eventType, filename)\n setTimeout(shutdown, 100)\n })\n log('[watch] file changes in ./src')\n } catch (e) {\n // log('[watch] unsupported')\n }\n }\n}\nexports.nodeHttpServer = nodeHttpServer\n","lambda.js":"function lambdaHttpServer ({ app }) {\n const fs = require('fs')\n const mime = require('mime')\n const path = require('path')\n const zlib = require('zlib')\n const crypto = require('crypto')\n\n function toQueryString (kvpairs) {\n const result = []\n for (const k in kvpairs) {\n const values = kvpairs[k]\n for (const i in values) {\n result.push(encodeURIComponent(k) + '=' + encodeURIComponent(values[i]))\n }\n }\n if (result.length === 0) {\n return ''\n }\n return '?' + result.join('&')\n }\n\n function toLowerCaseKeys (kvpairs) {\n const result = {}\n for (const k in kvpairs) {\n result[k.toLowerCase()] = kvpairs[k]\n }\n return result\n }\n\n // when Elm tries to write http response\n // we find the {req,res} pair according to `requestid`, and res.write...\n app.ports.onHttpResponse.subscribe(({ statusCode, body, headers, request }) => {\n request.callback(null, {\n body: body,\n statusCode: statusCode,\n headers: {\n ...headers,\n 'Content-Length': '' + Buffer.byteLength(body)\n }\n })\n })\n\n return function (event, ctx, callback) {\n ctx.callbackWaitsForEmptyEventLoop = false\n\n event.headers = event.headers || {} // blank when `lambda > test` or other events\n\n // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html\n const requestPath = (event.path || event.rawPath || (event.requestContext && event.requestContext.http && event.requestContext.http.path))\n const queryString = (event.multiValueQueryStringParameters ? toQueryString(event.multiValueQueryStringParameters) : event.rawQueryString)\n const url =\n (event.headers['CloudFront-Forwarded-Proto'] || 'https') + '://' +\n (event.headers.Host || (event.requestContext && event.requestContext.domainName)) +\n requestPath + (queryString ? ('?' + queryString) : '')\n\n try {\n // try serving static file\n let staticContent = fs.readFileSync('./public/assets/' + path.basename(requestPath), { encoding: 'utf8', flag: 'r' })\n const staticContentLength = staticContent.length\n const etag = crypto.createHmac('SHA256', requestPath).update(staticContent).digest('base64')\n\n callback(null, {\n statusCode: 200,\n headers: {\n ETag: JSON.stringify(etag),\n 'Content-Type': mime.types[path.extname(requestPath).substring(1)] || mime.default_type,\n 'Content-Length': '' + staticContentLength\n },\n body: staticContent.toString('base64')\n })\n } catch (e) {\n // not static file, send to elm\n const contentType = event.headers['Content-Type'] || ''\n const encoding = (contentType.match(/; charset=(\\S+)/) || ['', 'utf8'])[1] // charset or default to `utf8`\n const bodyString = Buffer.from(event.body || '', event.isBase64Encoded ? 'base64' : null).toString(encoding)\n event.headers['x-request-id'] = ctx.awsRequestId\n\n app.ports.onHttpRequest.send({\n ctx: ctx,\n callback: callback,\n method: event.requestContext.httpMethod,\n url: url,\n path: requestPath,\n body: bodyString,\n headers: toLowerCaseKeys(event.headers)\n })\n }\n }\n}\n\nexports.lambdaHttpServer = lambdaHttpServer\n","Makefile":"# like `make run` but with a file-watching-recompile loop\nwatch:\n\t#\n\t# on ./src file changes, server will exit\n\t# once recompile succeeds (retry on file changes), server will start\n\t#\n\t# ctrl-c to shutdown gracefully\n\t#\n\tuntil make compile; do node scripts/wait-for-changes.js .; done\n\tuntil node index.js --watch; do until make compile; do node scripts/wait-for-changes.js .; done; done\n\n# like `make watch` but without file watching loop\nrun: compile\n\tnode index.js\n\ncompile: elm.json package.json src/Protocol/Auto.elm build/Server.js public/assets/client.js\n\nsrc/Protocol/Auto.elm: src/Protocol.elm\n\t#\n\t# for every type in `src/Protocol.elm`\n\t# generate a json encoder/decoder in `src/Protocol/Auto.elm`\n\t#\n\t# read more at https://github.com/choonkeat/elm-auto-encoder-decoder\n\t#\n\tWATCHING=false elm-auto-encoder-decoder src/Protocol.elm\n\nbuild/Server.js: src/**/*.elm\n\telm make src/Server.elm --output build/Server.js\n\npublic/assets/client.js: src/**/*.elm\n\telm make src/Client.elm --output public/assets/client.js\n\n#\n\ninstall: elm.json package.json\n\tyes | elm install elm/url > /dev/null\n\tyes | elm install elm/json > /dev/null\n\tyes | elm install elm/http > /dev/null\n\tyes | elm install elm/time > /dev/null\n\tyes | elm install choonkeat/elm-webapp > /dev/null\n\t@printf \"\\nready. type \\`make\\` to start server\\n\\n\"\n\nelm.json:\n\tyes | elm init > /dev/null\n\npackage.json:\n\tnpm init -y\n\tnpm install --save xhr2 full-url node-static websocket\n\tnpm install --save-dev elm-auto-encoder-decoder\n\n#\n\ndeploy-aws-lambda: FUNCTION_ZIP=$(FUNCTION_NAME).zip\ndeploy-aws-lambda: compile\n\ttest -n \"$(FUNCTION_NAME)\" || (echo Missing FUNCTION_NAME; exit 1)\n\ttest -n \"$(S3BUCKET)\" || (echo Missing S3BUCKET; exit 1)\n\t@echo Deploying $(FUNCTION_NAME) ...\n\tzip -q -r $(FUNCTION_ZIP) . -i *.js 'js/*' 'build/*' 'node_modules/*' 'public/*'\n\ttime aws s3 cp $(FUNCTION_ZIP) s3://$(S3BUCKET)/$(FUNCTION_ZIP)\n\ttime aws lambda update-function-code --function-name $(FUNCTION_NAME) --s3-bucket $(S3BUCKET) --s3-key $(FUNCTION_ZIP) --publish\n","scripts/wait-for-changes.js":"#!/usr/bin/env node\n\nconst fs = require('fs')\nconst target = process.argv.reverse()[0]\nfs.watch(target, { recursive: true }, function (eventType, filename) {\n console.log('[wait]', eventType, filename)\n this.close()\n})\nconsole.log('[wait] until file changes in', target)\n","src/Protocol.elm":"module Protocol exposing (..)\n\nimport Json.Decode\nimport Json.Encode\n\n\n{-| All messages that Client can send to Server\n-}\ntype MsgFromClient\n = SetGreeting String\n\n\n{-| All messages that Server can reply to Client\n-}\ntype MsgFromServer\n = CurrentGreeting String\n\n\n{-| Http headers will be parsed into a RequestContext\nFailure to parse means error; keep an always successful scenario, e.g. Anonymous\n-}\ntype RequestContext\n = Cookied String\n | Anonymous\n","src/Protocol/Auto.elm":"module Protocol.Auto exposing (..)\n\n\n{- this file is generated by do not modify manually -}\n\n\nimport Protocol exposing (..)\nimport Dict\nimport Json.Decode\nimport Json.Encode\nimport Platform\nimport Set\n\n\n\n-- HARDCODE\n\n\nencodeString : String -> Json.Encode.Value\nencodeString =\n Json.Encode.string\n\n\nencodeInt : Int -> Json.Encode.Value\nencodeInt =\n Json.Encode.int\n\n\nencodeFloat : Float -> Json.Encode.Value\nencodeFloat =\n Json.Encode.float\n\n\nencodeBool : Bool -> Json.Encode.Value\nencodeBool =\n Json.Encode.bool\n\n\nencodeList : (a -> Json.Encode.Value) -> List a -> Json.Encode.Value\nencodeList =\n Json.Encode.list\n\n\nencodeSetSet : (comparable -> Json.Encode.Value) -> Set.Set comparable -> Json.Encode.Value\nencodeSetSet encoder =\n Set.toList >> encodeList encoder\n\n\nencodeDictDict : (a -> Json.Encode.Value) -> (b -> Json.Encode.Value) -> Dict.Dict a b -> Json.Encode.Value\nencodeDictDict keyEncoder =\n Json.Encode.dict (\\k -> Json.Encode.encode 0 (keyEncoder k))\n\n\nencode_Unit : () -> Json.Encode.Value\nencode_Unit value =\n Json.Encode.list identity [ encodeString \"\" ]\n\n\nencodeJsonDecodeValue : Json.Decode.Value -> Json.Encode.Value\nencodeJsonDecodeValue a =\n a\n\n\nencodeJsonEncodeValue : Json.Encode.Value -> Json.Encode.Value\nencodeJsonEncodeValue a =\n a\n\n\n\n--\n\n\ndecodeJsonDecodeValue : Json.Decode.Decoder Json.Decode.Value\ndecodeJsonDecodeValue =\n Json.Decode.value\n\n\ndecodeJsonEncodeValue : Json.Decode.Decoder Json.Decode.Value\ndecodeJsonEncodeValue =\n Json.Decode.value\n\n\ndecodeString : Json.Decode.Decoder String\ndecodeString =\n Json.Decode.string\n\n\ndecodeInt : Json.Decode.Decoder Int\ndecodeInt =\n Json.Decode.int\n\n\ndecodeFloat : Json.Decode.Decoder Float\ndecodeFloat =\n Json.Decode.float\n\n\ndecodeBool : Json.Decode.Decoder Bool\ndecodeBool =\n Json.Decode.bool\n\n\ndecodeList : (Json.Decode.Decoder a) -> Json.Decode.Decoder (List a)\ndecodeList =\n Json.Decode.list\n\n\ndecodeSetSet : (Json.Decode.Decoder comparable) -> Json.Decode.Decoder (Set.Set comparable)\ndecodeSetSet =\n Json.Decode.list >> Json.Decode.map Set.fromList\n\n\ndecodeDictDict : (Json.Decode.Decoder comparable) -> (Json.Decode.Decoder b) -> Json.Decode.Decoder (Dict.Dict comparable b)\ndecodeDictDict keyDecoder valueDecoder =\n Json.Decode.dict valueDecoder\n |> Json.Decode.map (\\dict ->\n Dict.foldl (\\string v acc ->\n case Json.Decode.decodeString keyDecoder string of\n Ok k ->\n Dict.insert k v acc\n Err _ ->\n acc\n ) Dict.empty dict\n )\n\n\ndecode_Unit : Json.Decode.Decoder ()\ndecode_Unit =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"\" -> (Json.Decode.succeed ())\n _ -> Json.Decode.fail (\"Unexpected Unit: \" ++ word)\n )\n\n\n-- PRELUDE\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Nothing\") [],CustomTypeConstructor (TitleCaseDotPhrase \"Just\") [ConstructorTypeParam \"a\"]], name = TypeName \"Maybe\" [\"a\"] } -}\nencodeMaybe : (a -> Json.Encode.Value) -> Maybe a -> Json.Encode.Value\nencodeMaybe arga value =\n case value of\n (Nothing) -> (Json.Encode.list identity [ encodeString \"Nothing\" ])\n (Just m0) -> (Json.Encode.list identity [ encodeString \"Just\", (arga m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Err\") [ConstructorTypeParam \"x\"],CustomTypeConstructor (TitleCaseDotPhrase \"Ok\") [ConstructorTypeParam \"a\"]], name = TypeName \"Result\" [\"x\",\"a\"] } -}\nencodeResult : (x -> Json.Encode.Value) -> (a -> Json.Encode.Value) -> Result x a -> Json.Encode.Value\nencodeResult argx arga value =\n case value of\n (Err m0) -> (Json.Encode.list identity [ encodeString \"Err\", (argx m0) ])\n (Ok m0) -> (Json.Encode.list identity [ encodeString \"Ok\", (arga m0) ])\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Nothing\") [],CustomTypeConstructor (TitleCaseDotPhrase \"Just\") [ConstructorTypeParam \"a\"]], name = TypeName \"Maybe\" [\"a\"] } -}\ndecodeMaybe : (Json.Decode.Decoder (a)) -> Json.Decode.Decoder (Maybe a)\ndecodeMaybe arga =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Nothing\" -> (Json.Decode.succeed Nothing)\n \"Just\" -> (Json.Decode.succeed Just |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (arga))))\n _ -> Json.Decode.fail (\"Unexpected Maybe: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Err\") [ConstructorTypeParam \"x\"],CustomTypeConstructor (TitleCaseDotPhrase \"Ok\") [ConstructorTypeParam \"a\"]], name = TypeName \"Result\" [\"x\",\"a\"] } -}\ndecodeResult : (Json.Decode.Decoder (x)) -> (Json.Decode.Decoder (a)) -> Json.Decode.Decoder (Result x a)\ndecodeResult argx arga =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Err\" -> (Json.Decode.succeed Err |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (argx))))\n \"Ok\" -> (Json.Decode.succeed Ok |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (arga))))\n _ -> Json.Decode.fail (\"Unexpected Result: \" ++ word)\n )\n \n\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.SetGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromClient\" [] } -}\nencodeProtocolMsgFromClient : Protocol.MsgFromClient -> Json.Encode.Value\nencodeProtocolMsgFromClient value =\n case value of\n (Protocol.SetGreeting m0) -> (Json.Encode.list identity [ encodeString \"Protocol.SetGreeting\", (encodeString m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.CurrentGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromServer\" [] } -}\nencodeProtocolMsgFromServer : Protocol.MsgFromServer -> Json.Encode.Value\nencodeProtocolMsgFromServer value =\n case value of\n (Protocol.CurrentGreeting m0) -> (Json.Encode.list identity [ encodeString \"Protocol.CurrentGreeting\", (encodeString m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Cookied\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []],CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Anonymous\") []], name = TypeName \"Protocol.RequestContext\" [] } -}\nencodeProtocolRequestContext : Protocol.RequestContext -> Json.Encode.Value\nencodeProtocolRequestContext value =\n case value of\n (Protocol.Cookied m0) -> (Json.Encode.list identity [ encodeString \"Protocol.Cookied\", (encodeString m0) ])\n (Protocol.Anonymous) -> (Json.Encode.list identity [ encodeString \"Protocol.Anonymous\" ])\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.SetGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromClient\" [] } -}\ndecodeProtocolMsgFromClient : Json.Decode.Decoder (Protocol.MsgFromClient)\ndecodeProtocolMsgFromClient =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.SetGreeting\" -> (Json.Decode.succeed Protocol.SetGreeting |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n _ -> Json.Decode.fail (\"Unexpected Protocol.MsgFromClient: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.CurrentGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromServer\" [] } -}\ndecodeProtocolMsgFromServer : Json.Decode.Decoder (Protocol.MsgFromServer)\ndecodeProtocolMsgFromServer =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.CurrentGreeting\" -> (Json.Decode.succeed Protocol.CurrentGreeting |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n _ -> Json.Decode.fail (\"Unexpected Protocol.MsgFromServer: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Cookied\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []],CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Anonymous\") []], name = TypeName \"Protocol.RequestContext\" [] } -}\ndecodeProtocolRequestContext : Json.Decode.Decoder (Protocol.RequestContext)\ndecodeProtocolRequestContext =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.Cookied\" -> (Json.Decode.succeed Protocol.Cookied |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n \"Protocol.Anonymous\" -> (Json.Decode.succeed Protocol.Anonymous)\n _ -> Json.Decode.fail (\"Unexpected Protocol.RequestContext: \" ++ word)\n )\n ","application":"module Application exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Task\nimport Protocol\nimport Protocol.Auto\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { application : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.application\n { application =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n , onUrlRequest = OnUrlRequest\n , onUrlChange = OnUrlChange\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.application\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { navKey : Browser.Navigation.Key\n , greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnUrlRequest Browser.UrlRequest\n | OnUrlChange Url.Url\n | OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> Url.Url -> Browser.Navigation.Key -> ( Model, Cmd Msg )\ninit flags url navKey =\n ( { navKey = navKey\n , greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Browser.Document Msg\nview model =\n Browser.Document \"Elm Webapp Client\"\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnUrlRequest urlRequest ->\n -- TODO\n ( model, Cmd.none )\n\n OnUrlChange urlUrl ->\n -- TODO\n ( model, Cmd.none )\n\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n","document":"module Document exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Task\nimport Protocol\nimport Protocol.Auto\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { document : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.document\n { document =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.document\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> ( Model, Cmd Msg )\ninit flags =\n ( { greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Browser.Document Msg\nview model =\n Browser.Document \"Elm Webapp Client\"\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n","element":"module Element exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Task\nimport Protocol\nimport Protocol.Auto\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { element : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.element\n { element =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.element\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> ( Model, Cmd Msg )\ninit flags =\n ( { greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Html.Html Msg\nview model =\n div []\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n"} +function clientContent(key) { const dict = {"index.js":"global.XMLHttpRequest = require('xhr2')\n\n//\nconst fs = require('fs')\nconst crypto = require('crypto')\nconst hash = crypto.createHash('sha256')\nconst jsData = fs.readFileSync('public/assets/client.js', { encoding: 'utf8' })\nhash.update(jsData)\nconst jsSha = hash.digest('hex')\n//\n\n// regular elm initialization\nconst { Elm } = require('./build/Server.js')\nvar app = Elm.Server.init({\n flags: {\n assetsHost: process.env.ASSETS_HOST || '',\n jsSha: jsSha\n }\n})\n\nfunction loggerWith (logger, ...context) {\n return function (...messages) {\n logger(context, ...messages)\n /* context is wrapped in [ square brackets ] */\n }\n}\nconst log = loggerWith(console.log, 'js')\nconst httpServer = (process.env.LAMBDA\n ? require('./lambda.js').lambdaHttpServer\n : require('./node.js').nodeHttpServer\n)\nlog('httpServer', httpServer)\nexports.handler = httpServer({\n log: loggerWith(log, 'http'),\n app: app\n})\n","node.js":"function nodeHttpServer ({ app, log }) {\n log('nodeHttpServer')\n\n const fs = require('fs')\n const http = require('http')\n const fullUrl = require('full-url')\n const nodeStatic = require('node-static')\n const fileServer = new nodeStatic.Server('./public')\n const WebSocketServer = require('websocket').server\n\n // Start http server, accept requests and pass to Elm\n const server = http.createServer((req, res) => {\n const body = [] // https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/#request-body\n req.on('data', (chunk) => {\n body.push(chunk)\n }).on('end', () => {\n fileServer.serve(req, res, function (e) {\n // if static file is found by fileServer, serve it\n if (!e || e.status !== 404) return\n\n // otherwise, Webapp.Server will handle the request\n const contentType = req.headers['content-type'] || ''\n const encoding = (contentType.match(/; charset=(\\S+)/) || ['', 'utf8'])[1] // charset or default to \"utf8\"\n const bodyString = Buffer.concat(body).toString(encoding)\n app.ports.onHttpRequest.send({\n response: res, // this value will be used by \"onHttpResponse\" below\n method: req.method,\n url: fullUrl(req),\n path: req.url,\n body: bodyString,\n headers: req.headers\n })\n })\n }).resume()\n })\n app.ports.onHttpResponse.subscribe(({ statusCode, body, headers, request }) => {\n log('onHttpResponse status=', statusCode, Buffer.byteLength(body), 'bytes')\n request.response.writeHead(statusCode, { ...headers, 'Content-Length': Buffer.byteLength(body) })\n request.response.end(body)\n })\n\n if (app.ports.writeWs) {\n app.ports.writeWs.subscribe(({ key, connection, body }) => {\n log('[ws] writeWs key=', key, body)\n connection.sendUTF(body)\n })\n }\n\n var wsServer\n if (app.ports.onWebsocketEvent) {\n wsServer = new WebSocketServer({\n // WebSocket server is tied to a HTTP server. WebSocket request is just\n // an enhanced HTTP request. For more info http://tools.ietf.org/html/rfc6455#page-6\n httpServer: server\n })\n\n // Start websocket server, accept requests and pass to Elm\n wsServer.on('request', function (request) {\n log('[ws] Connection from origin ' + request.origin + '.', request.key)\n\n // accept connection - you should check 'request.origin' to make sure that\n // client is connecting from your website\n // (http://en.wikipedia.org/wiki/Same_origin_policy)\n if (request.origin !== 'http://localhost:8000' && false) {\n request.reject()\n log('[ws]', (new Date()) + ' Connection from origin ' + request.origin + ' rejected.')\n return\n }\n\n var connection = request.accept(null, request.origin)\n app.ports.onWebsocketEvent.send({\n open: connection,\n key: request.key,\n headers: (request.httpRequest || {}).headers\n })\n\n // user sent some message\n connection.on('message', function (payload) {\n log('[ws] payload', payload)\n app.ports.onWebsocketEvent.send({\n message: connection,\n key: request.key,\n payload: payload\n })\n })\n // user disconnected\n // https://github.com/theturtle32/WebSocket-Node/blob/1f7ffba2f7a6f9473bcb39228264380ce2772ba7/docs/WebSocketConnection.md#close\n connection.on('close', function (reasonCode, description) {\n log('[ws] close', reasonCode, description)\n app.ports.onWebsocketEvent.send({\n reasonCode,\n description,\n close: connection,\n key: request.key\n })\n })\n })\n }\n\n var port = process.env.PORT || 8000\n var listener = server.listen(port)\n log('server listening at', port, '...')\n\n // https://stackoverflow.com/a/35480020\n var lastSocketKey = 0\n var socketMap = {}\n listener.on('connection', function (socket) {\n var socketKey = ++lastSocketKey\n socketMap[socketKey] = socket\n socket.on('close', function () { delete socketMap[socketKey] })\n })\n\n const shutdown = (signal) => {\n log('signal received.')\n if (app.ports.onWebsocketEvent) {\n wsServer.unmount()\n log('[ws] server unmounted.')\n }\n server.close(() => {\n log('server closed.')\n process.exit(signal ? 0 : 1) // not great, but there are Timers dangling and won't quit\n })\n if (app.ports.onWebsocketEvent) {\n wsServer.shutDown()\n log('[ws] server shutdown.')\n }\n Object.keys(socketMap).forEach(function (socketKey) { socketMap[socketKey].destroy() })\n }\n\n process.on('SIGTERM', shutdown)\n process.on('SIGINT', shutdown)\n\n if (process.argv.indexOf('--watch') > -1) {\n try {\n fs.watch('./src', { recursive: true }, function (eventType, filename) {\n log('[watch]', eventType, filename)\n setTimeout(shutdown, 100)\n })\n log('[watch] file changes in ./src')\n } catch (e) {\n // log('[watch] unsupported')\n }\n }\n}\nexports.nodeHttpServer = nodeHttpServer\n","lambda.js":"function lambdaHttpServer ({ app }) {\n const fs = require('fs')\n const mime = require('mime')\n const path = require('path')\n const zlib = require('zlib')\n const crypto = require('crypto')\n\n function toQueryString (kvpairs) {\n const result = []\n for (const k in kvpairs) {\n const values = kvpairs[k]\n for (const i in values) {\n result.push(encodeURIComponent(k) + '=' + encodeURIComponent(values[i]))\n }\n }\n if (result.length === 0) {\n return ''\n }\n return '?' + result.join('&')\n }\n\n function toLowerCaseKeys (kvpairs) {\n const result = {}\n for (const k in kvpairs) {\n result[k.toLowerCase()] = kvpairs[k]\n }\n return result\n }\n\n // when Elm tries to write http response\n // we find the {req,res} pair according to `requestid`, and res.write...\n app.ports.onHttpResponse.subscribe(({ statusCode, body, headers, request }) => {\n request.callback(null, {\n body: body,\n statusCode: statusCode,\n headers: {\n ...headers,\n 'Content-Length': '' + Buffer.byteLength(body)\n }\n })\n })\n\n return function (event, ctx, callback) {\n ctx.callbackWaitsForEmptyEventLoop = false\n\n event.headers = event.headers || {} // blank when `lambda > test` or other events\n\n // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html\n const requestPath = (event.path || event.rawPath || (event.requestContext && event.requestContext.http && event.requestContext.http.path))\n const queryString = (event.multiValueQueryStringParameters ? toQueryString(event.multiValueQueryStringParameters) : event.rawQueryString)\n const url =\n (event.headers['CloudFront-Forwarded-Proto'] || 'https') + '://' +\n (event.headers.Host || (event.requestContext && event.requestContext.domainName)) +\n requestPath + (queryString ? ('?' + queryString) : '')\n\n try {\n // try serving static file\n let staticContent = fs.readFileSync('./public/assets/' + path.basename(requestPath), { encoding: 'utf8', flag: 'r' })\n const staticContentLength = staticContent.length\n const etag = crypto.createHmac('SHA256', requestPath).update(staticContent).digest('base64')\n\n callback(null, {\n statusCode: 200,\n headers: {\n ETag: JSON.stringify(etag),\n 'Content-Type': mime.types[path.extname(requestPath).substring(1)] || mime.default_type,\n 'Content-Length': '' + staticContentLength\n },\n body: staticContent.toString('base64')\n })\n } catch (e) {\n // not static file, send to elm\n const contentType = event.headers['Content-Type'] || ''\n const encoding = (contentType.match(/; charset=(\\S+)/) || ['', 'utf8'])[1] // charset or default to `utf8`\n const bodyString = Buffer.from(event.body || '', event.isBase64Encoded ? 'base64' : null).toString(encoding)\n event.headers['x-request-id'] = ctx.awsRequestId\n\n app.ports.onHttpRequest.send({\n ctx: ctx,\n callback: callback,\n method: event.requestContext.httpMethod,\n url: url,\n path: requestPath,\n body: bodyString,\n headers: toLowerCaseKeys(event.headers)\n })\n }\n }\n}\n\nexports.lambdaHttpServer = lambdaHttpServer\n","Makefile":"# like `make run` but with a file-watching-recompile loop\nwatch:\n\t#\n\t# on ./src file changes, server will exit\n\t# once recompile succeeds (retry on file changes), server will start\n\t#\n\t# ctrl-c to shutdown gracefully\n\t#\n\tuntil make compile; do node scripts/wait-for-changes.js .; done\n\tuntil node index.js --watch; do until make compile; do node scripts/wait-for-changes.js .; done; done\n\n# like `make watch` but without file watching loop\nrun: compile\n\tnode index.js\n\ncompile: elm.json package.json src/Protocol/Auto.elm build/Server.js public/assets/client.js\n\nsrc/Protocol/Auto.elm: src/Protocol.elm\n\t#\n\t# for every type in `src/Protocol.elm`\n\t# generate a json encoder/decoder in `src/Protocol/Auto.elm`\n\t#\n\t# read more at https://github.com/choonkeat/elm-auto-encoder-decoder\n\t#\n\tWATCHING=false elm-auto-encoder-decoder src/Protocol.elm\n\nbuild/Server.js: src/*.elm src/**/*.elm\n\telm make src/Server.elm --output build/Server.js\n\npublic/assets/client.js: src/*.elm src/**/*.elm\n\telm make src/Client.elm --output public/assets/client.js\n\n#\n\ninstall: elm.json package.json\n\tyes | elm install elm/url > /dev/null\n\tyes | elm install elm/json > /dev/null\n\tyes | elm install elm/http > /dev/null\n\tyes | elm install elm/time > /dev/null\n\tyes | elm install choonkeat/elm-webapp > /dev/null\n\t@printf \"\\nready. type \\`make\\` to start server\\n\\n\"\n\nelm.json:\n\tyes | elm init > /dev/null\n\npackage.json:\n\tnpm init -y\n\tnpm install --save xhr2 full-url node-static websocket\n\tnpm install --save-dev elm-auto-encoder-decoder\n\n#\n\ndeploy-aws-lambda: FUNCTION_ZIP=$(FUNCTION_NAME).zip\ndeploy-aws-lambda: compile\n\ttest -n \"$(FUNCTION_NAME)\" || (echo Missing FUNCTION_NAME; exit 1)\n\ttest -n \"$(S3BUCKET)\" || (echo Missing S3BUCKET; exit 1)\n\t@echo Deploying $(FUNCTION_NAME) ...\n\tzip -q -r $(FUNCTION_ZIP) . -i *.js 'js/*' 'build/*' 'node_modules/*' 'public/*'\n\ttime aws s3 cp $(FUNCTION_ZIP) s3://$(S3BUCKET)/$(FUNCTION_ZIP)\n\ttime aws lambda update-function-code --function-name $(FUNCTION_NAME) --s3-bucket $(S3BUCKET) --s3-key $(FUNCTION_ZIP) --publish\n","scripts/wait-for-changes.js":"#!/usr/bin/env node\n\nconst fs = require('fs')\nconst target = process.argv.reverse()[0]\nfs.watch(target, { recursive: true }, function (eventType, filename) {\n console.log('[wait]', eventType, filename)\n this.close()\n})\nconsole.log('[wait] until file changes in', target)\n","src/Protocol.elm":"module Protocol exposing (..)\n\nimport Json.Decode\nimport Json.Encode\n\n\n{-| All messages that Client can send to Server\n-}\ntype MsgFromClient\n = SetGreeting String\n\n\n{-| All messages that Server can reply to Client\n-}\ntype MsgFromServer\n = CurrentGreeting String\n\n\n{-| Http headers will be parsed into a RequestContext\nFailure to parse means error; keep an always successful scenario, e.g. Anonymous\n-}\ntype RequestContext\n = Cookied String\n | Anonymous\n","src/Protocol/Auto.elm":"module Protocol.Auto exposing (..)\n\n\n{- this file is generated by do not modify manually -}\n\n\nimport Protocol exposing (..)\nimport Dict\nimport Json.Decode\nimport Json.Encode\nimport Platform\nimport Set\n\n\n\n-- HARDCODE\n\n\nencodeString : String -> Json.Encode.Value\nencodeString =\n Json.Encode.string\n\n\nencodeInt : Int -> Json.Encode.Value\nencodeInt =\n Json.Encode.int\n\n\nencodeFloat : Float -> Json.Encode.Value\nencodeFloat =\n Json.Encode.float\n\n\nencodeBool : Bool -> Json.Encode.Value\nencodeBool =\n Json.Encode.bool\n\n\nencodeList : (a -> Json.Encode.Value) -> List a -> Json.Encode.Value\nencodeList =\n Json.Encode.list\n\n\nencodeSetSet : (comparable -> Json.Encode.Value) -> Set.Set comparable -> Json.Encode.Value\nencodeSetSet encoder =\n Set.toList >> encodeList encoder\n\n\nencodeDictDict : (a -> Json.Encode.Value) -> (b -> Json.Encode.Value) -> Dict.Dict a b -> Json.Encode.Value\nencodeDictDict keyEncoder =\n Json.Encode.dict (\\k -> Json.Encode.encode 0 (keyEncoder k))\n\n\nencode_Unit : () -> Json.Encode.Value\nencode_Unit value =\n Json.Encode.list identity [ encodeString \"\" ]\n\n\nencodeJsonDecodeValue : Json.Decode.Value -> Json.Encode.Value\nencodeJsonDecodeValue a =\n a\n\n\nencodeJsonEncodeValue : Json.Encode.Value -> Json.Encode.Value\nencodeJsonEncodeValue a =\n a\n\n\n\n--\n\n\ndecodeJsonDecodeValue : Json.Decode.Decoder Json.Decode.Value\ndecodeJsonDecodeValue =\n Json.Decode.value\n\n\ndecodeJsonEncodeValue : Json.Decode.Decoder Json.Decode.Value\ndecodeJsonEncodeValue =\n Json.Decode.value\n\n\ndecodeString : Json.Decode.Decoder String\ndecodeString =\n Json.Decode.string\n\n\ndecodeInt : Json.Decode.Decoder Int\ndecodeInt =\n Json.Decode.int\n\n\ndecodeFloat : Json.Decode.Decoder Float\ndecodeFloat =\n Json.Decode.float\n\n\ndecodeBool : Json.Decode.Decoder Bool\ndecodeBool =\n Json.Decode.bool\n\n\ndecodeList : (Json.Decode.Decoder a) -> Json.Decode.Decoder (List a)\ndecodeList =\n Json.Decode.list\n\n\ndecodeSetSet : (Json.Decode.Decoder comparable) -> Json.Decode.Decoder (Set.Set comparable)\ndecodeSetSet =\n Json.Decode.list >> Json.Decode.map Set.fromList\n\n\ndecodeDictDict : (Json.Decode.Decoder comparable) -> (Json.Decode.Decoder b) -> Json.Decode.Decoder (Dict.Dict comparable b)\ndecodeDictDict keyDecoder valueDecoder =\n Json.Decode.dict valueDecoder\n |> Json.Decode.map (\\dict ->\n Dict.foldl (\\string v acc ->\n case Json.Decode.decodeString keyDecoder string of\n Ok k ->\n Dict.insert k v acc\n Err _ ->\n acc\n ) Dict.empty dict\n )\n\n\ndecode_Unit : Json.Decode.Decoder ()\ndecode_Unit =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"\" -> (Json.Decode.succeed ())\n _ -> Json.Decode.fail (\"Unexpected Unit: \" ++ word)\n )\n\n\n-- PRELUDE\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Nothing\") [],CustomTypeConstructor (TitleCaseDotPhrase \"Just\") [ConstructorTypeParam \"a\"]], name = TypeName \"Maybe\" [\"a\"] } -}\nencodeMaybe : (a -> Json.Encode.Value) -> Maybe a -> Json.Encode.Value\nencodeMaybe arga value =\n case value of\n (Nothing) -> (Json.Encode.list identity [ encodeString \"Nothing\" ])\n (Just m0) -> (Json.Encode.list identity [ encodeString \"Just\", (arga m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Err\") [ConstructorTypeParam \"x\"],CustomTypeConstructor (TitleCaseDotPhrase \"Ok\") [ConstructorTypeParam \"a\"]], name = TypeName \"Result\" [\"x\",\"a\"] } -}\nencodeResult : (x -> Json.Encode.Value) -> (a -> Json.Encode.Value) -> Result x a -> Json.Encode.Value\nencodeResult argx arga value =\n case value of\n (Err m0) -> (Json.Encode.list identity [ encodeString \"Err\", (argx m0) ])\n (Ok m0) -> (Json.Encode.list identity [ encodeString \"Ok\", (arga m0) ])\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Nothing\") [],CustomTypeConstructor (TitleCaseDotPhrase \"Just\") [ConstructorTypeParam \"a\"]], name = TypeName \"Maybe\" [\"a\"] } -}\ndecodeMaybe : (Json.Decode.Decoder (a)) -> Json.Decode.Decoder (Maybe a)\ndecodeMaybe arga =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Nothing\" -> (Json.Decode.succeed Nothing)\n \"Just\" -> (Json.Decode.succeed Just |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (arga))))\n _ -> Json.Decode.fail (\"Unexpected Maybe: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Err\") [ConstructorTypeParam \"x\"],CustomTypeConstructor (TitleCaseDotPhrase \"Ok\") [ConstructorTypeParam \"a\"]], name = TypeName \"Result\" [\"x\",\"a\"] } -}\ndecodeResult : (Json.Decode.Decoder (x)) -> (Json.Decode.Decoder (a)) -> Json.Decode.Decoder (Result x a)\ndecodeResult argx arga =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Err\" -> (Json.Decode.succeed Err |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (argx))))\n \"Ok\" -> (Json.Decode.succeed Ok |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (arga))))\n _ -> Json.Decode.fail (\"Unexpected Result: \" ++ word)\n )\n \n\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.SetGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromClient\" [] } -}\nencodeProtocolMsgFromClient : Protocol.MsgFromClient -> Json.Encode.Value\nencodeProtocolMsgFromClient value =\n case value of\n (Protocol.SetGreeting m0) -> (Json.Encode.list identity [ encodeString \"Protocol.SetGreeting\", (encodeString m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.CurrentGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromServer\" [] } -}\nencodeProtocolMsgFromServer : Protocol.MsgFromServer -> Json.Encode.Value\nencodeProtocolMsgFromServer value =\n case value of\n (Protocol.CurrentGreeting m0) -> (Json.Encode.list identity [ encodeString \"Protocol.CurrentGreeting\", (encodeString m0) ])\n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Cookied\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []],CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Anonymous\") []], name = TypeName \"Protocol.RequestContext\" [] } -}\nencodeProtocolRequestContext : Protocol.RequestContext -> Json.Encode.Value\nencodeProtocolRequestContext value =\n case value of\n (Protocol.Cookied m0) -> (Json.Encode.list identity [ encodeString \"Protocol.Cookied\", (encodeString m0) ])\n (Protocol.Anonymous) -> (Json.Encode.list identity [ encodeString \"Protocol.Anonymous\" ])\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.SetGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromClient\" [] } -}\ndecodeProtocolMsgFromClient : Json.Decode.Decoder (Protocol.MsgFromClient)\ndecodeProtocolMsgFromClient =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.SetGreeting\" -> (Json.Decode.succeed Protocol.SetGreeting |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n _ -> Json.Decode.fail (\"Unexpected Protocol.MsgFromClient: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.CurrentGreeting\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []]], name = TypeName \"Protocol.MsgFromServer\" [] } -}\ndecodeProtocolMsgFromServer : Json.Decode.Decoder (Protocol.MsgFromServer)\ndecodeProtocolMsgFromServer =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.CurrentGreeting\" -> (Json.Decode.succeed Protocol.CurrentGreeting |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n _ -> Json.Decode.fail (\"Unexpected Protocol.MsgFromServer: \" ++ word)\n )\n \n\n\n\n{-| CustomTypeDef { constructors = [CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Cookied\") [CustomTypeConstructor (TitleCaseDotPhrase \"String\") []],CustomTypeConstructor (TitleCaseDotPhrase \"Protocol.Anonymous\") []], name = TypeName \"Protocol.RequestContext\" [] } -}\ndecodeProtocolRequestContext : Json.Decode.Decoder (Protocol.RequestContext)\ndecodeProtocolRequestContext =\n Json.Decode.index 0 Json.Decode.string\n |> Json.Decode.andThen\n (\\word ->\n case word of\n \"Protocol.Cookied\" -> (Json.Decode.succeed Protocol.Cookied |> (Json.Decode.map2 (|>) (Json.Decode.index 1 (decodeString))))\n \"Protocol.Anonymous\" -> (Json.Decode.succeed Protocol.Anonymous)\n _ -> Json.Decode.fail (\"Unexpected Protocol.RequestContext: \" ++ word)\n )\n ","application":"module Application exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Protocol\nimport Protocol.Auto\nimport Task\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { application : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.application\n { application =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n , onUrlRequest = OnUrlRequest\n , onUrlChange = OnUrlChange\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.application\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { navKey : Browser.Navigation.Key\n , greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnUrlRequest Browser.UrlRequest\n | OnUrlChange Url.Url\n | OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> Url.Url -> Browser.Navigation.Key -> ( Model, Cmd Msg )\ninit flags url navKey =\n ( { navKey = navKey\n , greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Browser.Document Msg\nview model =\n Browser.Document \"Elm Webapp Client\"\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnUrlRequest urlRequest ->\n -- TODO\n ( model, Cmd.none )\n\n OnUrlChange urlUrl ->\n -- TODO\n ( model, Cmd.none )\n\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n","document":"module Document exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Protocol\nimport Protocol.Auto\nimport Task\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { document : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.document\n { document =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.document\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> ( Model, Cmd Msg )\ninit flags =\n ( { greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Browser.Document Msg\nview model =\n Browser.Document \"Elm Webapp Client\"\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n","element":"module Element exposing (..)\n\nimport Browser\nimport Browser.Navigation\nimport Html exposing (Html, button, div, form, input, text)\nimport Html.Attributes exposing (type_)\nimport Html.Events exposing (onClick, onInput, onSubmit)\nimport Http\nimport Json.Decode\nimport Json.Encode\nimport Platform exposing (Task)\nimport Protocol\nimport Protocol.Auto\nimport Task\nimport Url\nimport Webapp.Client\n\n\n\n-- port websocketConnected : (Int -> msg) -> Sub msg\n--\n--\n-- port websocketIn : (String -> msg) -> Sub msg\n--\n--\n-- port websocketOut : String -> Cmd msg\n\n\nwebapp :\n { element : Webapp.Client.Program Flags Model Msg\n , sendToServer : Protocol.MsgFromClient -> Task Http.Error (Result String Protocol.MsgFromServer)\n }\nwebapp =\n Webapp.Client.element\n { element =\n { init = init\n , view = view\n , update = update\n , subscriptions = subscriptions\n }\n , ports =\n { websocketConnected = \\_ -> Sub.none -- websocketConnected\n , websocketIn = \\_ -> Sub.none -- websocketIn\n }\n , protocol =\n { updateFromServer = updateFromServer\n , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n , serverMsgDecoder = Protocol.Auto.decodeProtocolMsgFromServer\n , errorDecoder = Json.Decode.string\n }\n }\n\n\nmain : Webapp.Client.Program Flags Model Msg\nmain =\n webapp.element\n\n\n{-| Clients send messages to Server with this\n-}\nsendToServer : Protocol.MsgFromClient -> Cmd Msg\nsendToServer =\n webapp.sendToServer >> Task.attempt OnMsgFromServer\n\n\ntype alias Flags =\n {}\n\n\ntype alias Model =\n { greeting : String\n , serverGreeting : String\n }\n\n\ntype Msg\n = OnMsgFromServer (Result Http.Error (Result String Protocol.MsgFromServer))\n | SendMessage Protocol.MsgFromClient\n | SetGreeting String\n\n\ninit : Flags -> ( Model, Cmd Msg )\ninit flags =\n ( { greeting = \"\"\n , serverGreeting = \"\"\n }\n , Cmd.none\n )\n\n\nview : Model -> Html.Html Msg\nview model =\n div []\n [ form [ onSubmit (SendMessage (Protocol.SetGreeting model.greeting)) ]\n [ input [ onInput SetGreeting ] []\n , button [ type_ \"submit\" ] [ text \"Send to server\" ]\n ]\n , if model.serverGreeting == \"\" then\n text \"\"\n\n else\n div []\n [ text \"Server reply: \"\n , text model.serverGreeting\n ]\n ]\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n case msg of\n OnMsgFromServer (Err err) ->\n -- http error\n ( { model | serverGreeting = Debug.toString err }, Cmd.none )\n\n OnMsgFromServer (Ok (Err err)) ->\n -- error from Server.elm\n ( { model | serverGreeting = \"app error: \" ++ err }, Cmd.none )\n\n OnMsgFromServer (Ok (Ok serverMsg)) ->\n updateFromServer serverMsg model\n\n SendMessage clientMsg ->\n -- ( model, websocketOut (Json.Encode.encode 0 (Protocol.encodeProtocolMsgFromClient clientMsg)) )\n ( model, sendToServer clientMsg )\n\n SetGreeting s ->\n ( { model | greeting = s }, Cmd.none )\n\n\nupdateFromServer : Protocol.MsgFromServer -> Model -> ( Model, Cmd Msg )\nupdateFromServer serverMsg model =\n case serverMsg of\n Protocol.CurrentGreeting s ->\n ( { model | serverGreeting = s }, Cmd.none )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n Sub.none\n"} ; return dict[key] } diff --git a/docs.json b/docs.json index 9b5d67d..546acd5 100644 --- a/docs.json +++ b/docs.json @@ -1 +1 @@ -[{"name":"Webapp.Client","comment":" The README is better for getting an understanding and for getting started. In general\nwe'd just use the code generated by `elm-webapp` cli without the need to reference here.\n\n\n# Definition\n\n@docs Ports, Protocol, Program\n\n\n# Common Helpers\n\n@docs element, document, application\n\n","unions":[],"aliases":[{"name":"Ports","comment":" Hook up ports to use websockets for passing serverMsg and clientMsg\n\n`websocketConnected` port receives an Int for time now in milliseconds. Currently `Webapp.Client` does\nnot use this information for anything.\n\n`websocketIn` port receives the raw string from websocket `event.data` which will be parsed as a\n`Result x serverMsg` (see `sendToServer`)\n\n","args":["msg"],"type":"{ websocketConnected : (Basics.Int -> Webapp.Client.FrameworkMsg msg) -> Platform.Sub.Sub (Webapp.Client.FrameworkMsg msg), websocketIn : (String.String -> Webapp.Client.FrameworkMsg msg) -> Platform.Sub.Sub (Webapp.Client.FrameworkMsg msg) }"},{"name":"Program","comment":" Exported type to enable apps to write their type signature of `main`, e.g.\n\n main : Webapp.Client.Program Flags Model Msg\n main =\n webapp.element\n\n","args":["flags","model","msg"],"type":"Platform.Program flags model (Webapp.Client.FrameworkMsg msg)"},{"name":"Protocol","comment":" A set of required protocols.\n\n - `updateFromServer` is called when `serverMsg` is received, e.g. as response of `sendToServer`\n - `clientMsgEncoder` encodes outgoing `clientMsg`, e.g. via `sendToServer`\n - `serverMsgDecoder` decodes replies from Server\n - `errorDecoder` decodes error replies from Server\n\n","args":["serverMsg","clientMsg","model","msg","x"],"type":"{ updateFromServer : serverMsg -> model -> ( model, Platform.Cmd.Cmd msg ), clientMsgEncoder : clientMsg -> Json.Encode.Value, serverMsgDecoder : Json.Decode.Decoder serverMsg, errorDecoder : Json.Decode.Decoder x }"}],"values":[{"name":"application","comment":" Returns a `Browser.application` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.application\n\n","type":"{ application : { init : flags -> Url.Url -> Browser.Navigation.Key -> ( model, Platform.Cmd.Cmd msg ), view : model -> Browser.Document msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg, onUrlRequest : Browser.UrlRequest -> msg, onUrlChange : Url.Url -> msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { application : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"},{"name":"document","comment":" Returns a `Browser.document` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.document\n\n","type":"{ document : { init : flags -> ( model, Platform.Cmd.Cmd msg ), view : model -> Browser.Document msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { document : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"},{"name":"element","comment":" Returns a `Browser.element` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.element\n\n","type":"{ element : { init : flags -> ( model, Platform.Cmd.Cmd msg ), view : model -> Html.Html msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { element : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"}],"binops":[]},{"name":"Webapp.Server","comment":" The README is better for getting an understanding and for getting started. In general\nwe'd just use the code generated by `elm-webapp` cli without the need to reference here.\n\nHowever, the documentation for `Webapp.Server.HTTP` is useful esp for functions available\nto work with `Request` values.\n\n\n# Definition\n\n@docs PlatformWorker, Ports, Protocol, Program\n\n\n# Common Helpers\n\n@docs worker, writeResponse, writeWebsocketMessage\n\n","unions":[],"aliases":[{"name":"PlatformWorker","comment":" This is the input to create a [`Platform.worker`](https://package.elm-lang.org/packages/elm/core/latest/Platform#worker)\n","args":["flags","model","msg"],"type":"{ init : flags -> ( model, Platform.Cmd.Cmd msg ), update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }"},{"name":"Ports","comment":" A set of required ports\n\n - `writeResponse` is your function created with your port `onHttpResponse`\n\n port onHttpResponse : Json.Encode.Value -> Cmd msg\n\n writeResponse =\n Webapp.Server.writeResponse onHttpResponse\n\n\n\n - `onHttpRequest` is a port defined in your `Server.elm`\n\n port onHttpRequest : (Json.Encode.Value -> msg) -> Sub msg\n\n","args":["msg","x","serverMsg"],"type":"{ writeResponse : Webapp.Server.HTTP.Request -> { statusCode : Webapp.Server.HTTP.StatusCode, headers : List.List ( String.String, Json.Encode.Value ), body : String.String } -> Platform.Cmd.Cmd (Webapp.Server.FrameworkMsg msg x serverMsg), onHttpRequest : (Json.Encode.Value -> Webapp.Server.FrameworkMsg msg x serverMsg) -> Platform.Sub.Sub (Webapp.Server.FrameworkMsg msg x serverMsg), onWebsocketEvent : (Json.Encode.Value -> Webapp.Server.FrameworkMsg msg x serverMsg) -> Platform.Sub.Sub (Webapp.Server.FrameworkMsg msg x serverMsg), writeWebsocketMessage : Webapp.Server.Websocket.Connection -> Webapp.Server.Websocket.Key -> String.String -> Platform.Cmd.Cmd (Webapp.Server.FrameworkMsg msg x serverMsg) }"},{"name":"Program","comment":" Exported type to enable apps to write their type signature of `main`, e.g.\n\n main : Webapp.Server.Program Flags ServerState RequestContext Msg Error MsgFromServer\n main =\n Webapp.Server.worker { ... }\n\n","args":["flags","model","header","msg","x","serverMsg"],"type":"Platform.Program flags (Webapp.Server.FrameworkModel model header) (Webapp.Server.FrameworkMsg msg x serverMsg)"},{"name":"Protocol","comment":" A set of required protocols.\n\n - `headerDecoder` decoded value will be made available to `updateFromRoute` and `updateFromClient`\n - `clientMsgDecoder` decodes the request body IF request was sent from Client `sendToServer`\n - `updateFromClient` is called if `clientMsgDecoder` succeeds\n - `serverMsgEncoder` encodes the response body for a successful `clientMsgDecoder`\n - `errorEncoder` encodes the response body for a failed `clientMsgDecoder`\n - `routeDecoder` decodes a `Url.Url`; if successful, `updateFromRoute` will be called\n - `updateFromRoute` is called as long as `headerDecoder` succeeds\n - otherwise Webapp.Server will respond with error 500\n\n","args":["msg","x","serverMsg","clientMsg","route","header","model"],"type":"{ headerDecoder : model -> Json.Decode.Decoder header, clientMsgDecoder : Json.Decode.Decoder clientMsg, updateFromClient : header -> Time.Posix -> clientMsg -> model -> ( model, Platform.Task x serverMsg ), serverMsgEncoder : serverMsg -> Json.Encode.Value, errorEncoder : x -> Json.Encode.Value, routeDecoder : Url.Url -> Maybe.Maybe route, updateFromRoute : ( Webapp.Server.HTTP.Method, header, Maybe.Maybe route ) -> Time.Posix -> Webapp.Server.HTTP.Request -> model -> ( model, Platform.Cmd.Cmd msg ) }"}],"values":[{"name":"worker","comment":" Returns a Webapp.Server program, capable of communicating with Webapp.Client program\n","type":"{ worker : Webapp.Server.PlatformWorker flags model msg, ports : Webapp.Server.Ports msg x serverMsg, protocol : Webapp.Server.Protocol msg x serverMsg clientMsg endpoint header model } -> Platform.Program flags (Webapp.Server.FrameworkModel model header) (Webapp.Server.FrameworkMsg msg x serverMsg)"},{"name":"writeResponse","comment":" Use this to wire up your port in your own `Server.elm`\n\n port onHttpResponse : Json.Encode.Value -> Cmd msg\n\n writeResponse =\n Webapp.Server.writeResponse onHttpResponse\n\n","type":"(Json.Encode.Value -> Platform.Cmd.Cmd msg) -> Webapp.Server.HTTP.Request -> { statusCode : Webapp.Server.HTTP.StatusCode, headers : List.List ( String.String, Json.Encode.Value ), body : String.String } -> Platform.Cmd.Cmd msg"},{"name":"writeWebsocketMessage","comment":" Use this to wire up your port in your own `Server.elm` via websocket\n\n port writeWs : Json.Encode.Value -> Cmd msg\n\n writeWebsocketMessage =\n Webapp.Server.writeWebsocketMessage writeWs\n\n","type":"(Json.Encode.Value -> Platform.Cmd.Cmd msg) -> Webapp.Server.Websocket.Connection -> Webapp.Server.Websocket.Key -> String.String -> Platform.Cmd.Cmd msg"}],"binops":[]},{"name":"Webapp.Server.HTTP","comment":" Data types and their helper functions to work with HTTP handlers\n\n\n# Definition\n\n@docs Body, Headers, Method, Request, StatusCode, Url\n\n\n# Common Helpers\n\n@docs bodyOf, headersOf, methodFromString, methodOf, methodString, pathOf, statusInt, urlOf\n\n","unions":[{"name":"Method","comment":" Custom type representing all http methods\n","args":[],"cases":[["GET",[]],["HEAD",[]],["POST",[]],["PUT",[]],["DELETE",[]],["CONNECT",[]],["OPTIONS",[]],["TRACE",[]],["PATCH",[]]]},{"name":"StatusCode","comment":" Custom type representing all http `StatusCode`\n","args":[],"cases":[["StatusContinue",[]],["StatusSwitchingProtocols",[]],["StatusProcessing",[]],["StatusEarlyHints",[]],["StatusOK",[]],["StatusCreated",[]],["StatusAccepted",[]],["StatusNonAuthoritativeInformation",[]],["StatusNoContent",[]],["StatusResetContent",[]],["StatusPartialContent",[]],["StatusMultiStatus",[]],["StatusAlreadyReported",[]],["StatusIMUsed",[]],["StatusMultipleChoices",[]],["StatusMovedPermanently",[]],["StatusFound",[]],["StatusSeeOther",[]],["StatusNotModified",[]],["StatusUseProxy",[]],["StatusTemporaryRedirect",[]],["StatusPermanentRedirect",[]],["StatusBadRequest",[]],["StatusUnauthorized",[]],["StatusPaymentRequired",[]],["StatusForbidden",[]],["StatusNotFound",[]],["StatusMethodNotAllowed",[]],["StatusNotAcceptable",[]],["StatusProxyAuthenticationRequired",[]],["StatusRequestTimeout",[]],["StatusConflict",[]],["StatusGone",[]],["StatusLengthRequired",[]],["StatusPreconditionFailed",[]],["StatusPayloadTooLarge",[]],["StatusURITooLong",[]],["StatusUnsupportedMediaType",[]],["StatusRangeNotSatisfiable",[]],["StatusExpectationFailed",[]],["StatusMisdirectedRequest",[]],["StatusUnprocessableEntity",[]],["StatusLocked",[]],["StatusFailedDependency",[]],["StatusTooEarly",[]],["StatusUpgradeRequired",[]],["StatusPreconditionRequired",[]],["StatusTooManyRequests",[]],["StatusRequestHeaderFieldsTooLarge",[]],["StatusUnavailableForLegalReasons",[]],["StatusInternalServerError",[]],["StatusNotImplemented",[]],["StatusBadGateway",[]],["StatusServiceUnavailable",[]],["StatusGatewayTimeout",[]],["StatusHTTPVersionNotSupported",[]],["StatusVariantAlsoNegotiates",[]],["StatusInsufficientStorage",[]],["StatusLoopDetected",[]],["StatusNotExtended",[]],["StatusNetworkAuthenticationRequired",[]]]}],"aliases":[{"name":"Body","comment":" Alias for String\n","args":[],"type":"String.String"},{"name":"Headers","comment":" Alias for opaque Json.Encode.Value\n","args":[],"type":"Json.Decode.Value"},{"name":"Request","comment":" Alias for opaque Json.Encode.Value\n","args":[],"type":"Json.Decode.Value"},{"name":"Url","comment":" Alias for String\n","args":[],"type":"String.String"}],"values":[{"name":"bodyOf","comment":" Returns request body from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"},{"name":"headersOf","comment":" Returns request headers from `Request`\n","type":"Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Headers"},{"name":"methodFromString","comment":" Parse a String and return as http `Method`\n","type":"String.String -> Webapp.Server.HTTP.Method"},{"name":"methodOf","comment":" Returns http `method` from `Request`\n","type":"Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Method"},{"name":"methodString","comment":" Returns http `Method` as String\n","type":"Webapp.Server.HTTP.Method -> String.String"},{"name":"pathOf","comment":" Returns request path from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"},{"name":"statusInt","comment":" Returns http StatusCode as integer\n","type":"Webapp.Server.HTTP.StatusCode -> Basics.Int"},{"name":"urlOf","comment":" Returns `url` from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"}],"binops":[]}] \ No newline at end of file +[{"name":"Webapp.Client","comment":" The README is better for getting an understanding and for getting started. In general\nwe'd just use the code generated by `elm-webapp` cli without the need to reference here.\n\n\n# Definition\n\n@docs Ports, Protocol, Program\n\n\n# Common Helpers\n\n@docs element, document, application\n\n","unions":[],"aliases":[{"name":"Ports","comment":" Hook up ports to use websockets for passing serverMsg and clientMsg\n\n`websocketConnected` port receives an Int for time now in milliseconds. Currently `Webapp.Client` does\nnot use this information for anything.\n\n`websocketIn` port receives the raw string from websocket `event.data` which will be parsed as a\n`Result x serverMsg` (see `sendToServer`)\n\n","args":["msg"],"type":"{ websocketConnected : (Basics.Int -> Webapp.Client.FrameworkMsg msg) -> Platform.Sub.Sub (Webapp.Client.FrameworkMsg msg), websocketIn : (String.String -> Webapp.Client.FrameworkMsg msg) -> Platform.Sub.Sub (Webapp.Client.FrameworkMsg msg) }"},{"name":"Program","comment":" Exported type to enable apps to write their type signature of `main`, e.g.\n\n main : Webapp.Client.Program Flags Model Msg\n main =\n webapp.element\n\n","args":["flags","model","msg"],"type":"Platform.Program flags model (Webapp.Client.FrameworkMsg msg)"},{"name":"Protocol","comment":" A set of required protocols.\n\n - `updateFromServer` is called when `serverMsg` is received, e.g. as response of `sendToServer`\n - `clientMsgEncoder` encodes outgoing `clientMsg`, e.g. via `sendToServer`\n - `serverMsgDecoder` decodes replies from Server\n - `errorDecoder` decodes error replies from Server\n\n","args":["serverMsg","clientMsg","model","msg","x"],"type":"{ updateFromServer : serverMsg -> model -> ( model, Platform.Cmd.Cmd msg ), clientMsgEncoder : clientMsg -> Json.Encode.Value, serverMsgDecoder : Json.Decode.Decoder serverMsg, errorDecoder : Json.Decode.Decoder x }"}],"values":[{"name":"application","comment":" Returns a `Browser.application` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.application\n\n","type":"{ application : { init : flags -> Url.Url -> Browser.Navigation.Key -> ( model, Platform.Cmd.Cmd msg ), view : model -> Browser.Document msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg, onUrlRequest : Browser.UrlRequest -> msg, onUrlChange : Url.Url -> msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { application : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"},{"name":"document","comment":" Returns a `Browser.document` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.document\n\n","type":"{ document : { init : flags -> ( model, Platform.Cmd.Cmd msg ), view : model -> Browser.Document msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { document : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"},{"name":"element","comment":" Returns a `Browser.element` to use as `main` in your client app\nand a `sendToServer` function to send `clientMsg` with\n\n main =\n webapp.element\n\n","type":"{ element : { init : flags -> ( model, Platform.Cmd.Cmd msg ), view : model -> Html.Html msg, update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }, ports : Webapp.Client.Ports msg, protocol : Webapp.Client.Protocol serverMsg clientMsg model msg x } -> { element : Platform.Program flags model (Webapp.Client.FrameworkMsg msg), sendToServer : clientMsg -> Platform.Task Http.Error (Result.Result x serverMsg) }"}],"binops":[]},{"name":"Webapp.Server","comment":" The README is better for getting an understanding and for getting started. In general\nwe'd just use the code generated by `elm-webapp` cli without the need to reference here.\n\nHowever, the documentation for `Webapp.Server.HTTP` is useful esp for functions available\nto work with `Request` values.\n\n\n# Definition\n\n@docs PlatformWorker, Ports, Protocol, Program\n\n\n# Common Helpers\n\n@docs worker, writeResponse, writeWebsocketMessage\n\n","unions":[],"aliases":[{"name":"PlatformWorker","comment":" This is the input to create a [`Platform.worker`](https://package.elm-lang.org/packages/elm/core/latest/Platform#worker)\n","args":["flags","model","msg"],"type":"{ init : flags -> ( model, Platform.Cmd.Cmd msg ), update : msg -> model -> ( model, Platform.Cmd.Cmd msg ), subscriptions : model -> Platform.Sub.Sub msg }"},{"name":"Ports","comment":" A set of required ports\n\n - `writeResponse` is your function created with your port `onHttpResponse`\n\n port onHttpResponse : Json.Encode.Value -> Cmd msg\n\n writeResponse =\n Webapp.Server.writeResponse onHttpResponse\n\n\n\n - `onHttpRequest` is a port defined in your `Server.elm`\n\n port onHttpRequest : (Json.Encode.Value -> msg) -> Sub msg\n\n","args":["msg","x","serverMsg"],"type":"{ writeResponse : Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Response -> Platform.Cmd.Cmd (Webapp.Server.FrameworkMsg msg x serverMsg), onHttpRequest : (Json.Encode.Value -> Webapp.Server.FrameworkMsg msg x serverMsg) -> Platform.Sub.Sub (Webapp.Server.FrameworkMsg msg x serverMsg), onWebsocketEvent : (Json.Encode.Value -> Webapp.Server.FrameworkMsg msg x serverMsg) -> Platform.Sub.Sub (Webapp.Server.FrameworkMsg msg x serverMsg), writeWebsocketMessage : Webapp.Server.Websocket.Connection -> Webapp.Server.Websocket.Key -> String.String -> Platform.Cmd.Cmd (Webapp.Server.FrameworkMsg msg x serverMsg) }"},{"name":"Program","comment":" Exported type to enable apps to write their type signature of `main`, e.g.\n\n main : Webapp.Server.Program Flags ServerState RequestContext Msg Error MsgFromServer\n main =\n Webapp.Server.worker { ... }\n\n","args":["flags","model","header","msg","x","serverMsg"],"type":"Platform.Program flags (Webapp.Server.FrameworkModel model header) (Webapp.Server.FrameworkMsg msg x serverMsg)"},{"name":"Protocol","comment":" A set of required protocols.\n\n - `headerDecoder` decoded value will be made available to `updateFromRoute` and `updateFromClient`\n - `clientMsgDecoder` decodes the request body IF request was sent from Client `sendToServer`\n - `updateFromClient` is called if `clientMsgDecoder` succeeds\n - `serverMsgEncoder` encodes the response body for a successful `clientMsgDecoder`\n - `errorEncoder` encodes the response body for a failed `clientMsgDecoder`\n - `routeDecoder` decodes a `Url.Url`; if successful, `updateFromRoute` will be called\n - `updateFromRoute` is called as long as `headerDecoder` succeeds\n - otherwise Webapp.Server will respond with error 500\n\n","args":["msg","x","serverMsg","clientMsg","route","header","model"],"type":"{ headerDecoder : model -> Json.Decode.Decoder header, clientMsgDecoder : Json.Decode.Decoder clientMsg, updateFromClient : header -> Time.Posix -> clientMsg -> model -> ( model, Platform.Task x serverMsg ), serverMsgEncoder : serverMsg -> Json.Encode.Value, errorEncoder : x -> Json.Encode.Value, routeDecoder : Url.Url -> Maybe.Maybe route, updateFromRoute : ( Webapp.Server.HTTP.Method, header, Maybe.Maybe route ) -> Time.Posix -> Webapp.Server.HTTP.Request -> model -> ( model, Platform.Cmd.Cmd msg ) }"}],"values":[{"name":"worker","comment":" Returns a Webapp.Server program, capable of communicating with Webapp.Client program\n","type":"{ worker : Webapp.Server.PlatformWorker flags model msg, ports : Webapp.Server.Ports msg x serverMsg, protocol : Webapp.Server.Protocol msg x serverMsg clientMsg endpoint header model } -> Platform.Program flags (Webapp.Server.FrameworkModel model header) (Webapp.Server.FrameworkMsg msg x serverMsg)"},{"name":"writeResponse","comment":" Use this to wire up your port in your own `Server.elm`\n\n port onHttpResponse : Json.Encode.Value -> Cmd msg\n\n writeResponse =\n Webapp.Server.writeResponse onHttpResponse\n\n","type":"(Json.Encode.Value -> Platform.Cmd.Cmd msg) -> Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Response -> Platform.Cmd.Cmd msg"},{"name":"writeWebsocketMessage","comment":" Use this to wire up your port in your own `Server.elm` via websocket\n\n port writeWs : Json.Encode.Value -> Cmd msg\n\n writeWebsocketMessage =\n Webapp.Server.writeWebsocketMessage writeWs\n\n","type":"(Json.Encode.Value -> Platform.Cmd.Cmd msg) -> Webapp.Server.Websocket.Connection -> Webapp.Server.Websocket.Key -> String.String -> Platform.Cmd.Cmd msg"}],"binops":[]},{"name":"Webapp.Server.HTTP","comment":" Data types and their helper functions to work with HTTP handlers\n\n\n# Definition\n\n@docs Body, Headers, Method, Request, Response, StatusCode, Url\n\n\n# Common Helpers\n\n@docs bodyOf, headersOf, methodFromString, methodOf, methodString, pathOf, statusInt, urlOf\n\n","unions":[{"name":"Method","comment":" Custom type representing all http methods\n","args":[],"cases":[["GET",[]],["HEAD",[]],["POST",[]],["PUT",[]],["DELETE",[]],["CONNECT",[]],["OPTIONS",[]],["TRACE",[]],["PATCH",[]]]},{"name":"StatusCode","comment":" Custom type representing all http `StatusCode`\n","args":[],"cases":[["StatusContinue",[]],["StatusSwitchingProtocols",[]],["StatusProcessing",[]],["StatusEarlyHints",[]],["StatusOK",[]],["StatusCreated",[]],["StatusAccepted",[]],["StatusNonAuthoritativeInformation",[]],["StatusNoContent",[]],["StatusResetContent",[]],["StatusPartialContent",[]],["StatusMultiStatus",[]],["StatusAlreadyReported",[]],["StatusIMUsed",[]],["StatusMultipleChoices",[]],["StatusMovedPermanently",[]],["StatusFound",[]],["StatusSeeOther",[]],["StatusNotModified",[]],["StatusUseProxy",[]],["StatusTemporaryRedirect",[]],["StatusPermanentRedirect",[]],["StatusBadRequest",[]],["StatusUnauthorized",[]],["StatusPaymentRequired",[]],["StatusForbidden",[]],["StatusNotFound",[]],["StatusMethodNotAllowed",[]],["StatusNotAcceptable",[]],["StatusProxyAuthenticationRequired",[]],["StatusRequestTimeout",[]],["StatusConflict",[]],["StatusGone",[]],["StatusLengthRequired",[]],["StatusPreconditionFailed",[]],["StatusPayloadTooLarge",[]],["StatusURITooLong",[]],["StatusUnsupportedMediaType",[]],["StatusRangeNotSatisfiable",[]],["StatusExpectationFailed",[]],["StatusMisdirectedRequest",[]],["StatusUnprocessableEntity",[]],["StatusLocked",[]],["StatusFailedDependency",[]],["StatusTooEarly",[]],["StatusUpgradeRequired",[]],["StatusPreconditionRequired",[]],["StatusTooManyRequests",[]],["StatusRequestHeaderFieldsTooLarge",[]],["StatusUnavailableForLegalReasons",[]],["StatusInternalServerError",[]],["StatusNotImplemented",[]],["StatusBadGateway",[]],["StatusServiceUnavailable",[]],["StatusGatewayTimeout",[]],["StatusHTTPVersionNotSupported",[]],["StatusVariantAlsoNegotiates",[]],["StatusInsufficientStorage",[]],["StatusLoopDetected",[]],["StatusNotExtended",[]],["StatusNetworkAuthenticationRequired",[]]]}],"aliases":[{"name":"Body","comment":" Alias for String\n","args":[],"type":"String.String"},{"name":"Headers","comment":" Alias for opaque Json.Encode.Value\n","args":[],"type":"Json.Decode.Value"},{"name":"Request","comment":" Alias for opaque Json.Encode.Value\n","args":[],"type":"Json.Decode.Value"},{"name":"Response","comment":" ","args":[],"type":"{ statusCode : Webapp.Server.HTTP.StatusCode, headers : List.List ( String.String, Json.Encode.Value ), body : String.String }"},{"name":"Url","comment":" Alias for String\n","args":[],"type":"String.String"}],"values":[{"name":"bodyOf","comment":" Returns request body from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"},{"name":"headersOf","comment":" Returns request headers from `Request`\n","type":"Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Headers"},{"name":"methodFromString","comment":" Parse a String and return as http `Method`\n","type":"String.String -> Webapp.Server.HTTP.Method"},{"name":"methodOf","comment":" Returns http `method` from `Request`\n","type":"Webapp.Server.HTTP.Request -> Webapp.Server.HTTP.Method"},{"name":"methodString","comment":" Returns http `Method` as String\n","type":"Webapp.Server.HTTP.Method -> String.String"},{"name":"pathOf","comment":" Returns request path from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"},{"name":"statusInt","comment":" Returns http StatusCode as integer\n","type":"Webapp.Server.HTTP.StatusCode -> Basics.Int"},{"name":"urlOf","comment":" Returns `url` from `Request`\n","type":"Webapp.Server.HTTP.Request -> String.String"}],"binops":[]}] \ No newline at end of file diff --git a/elm.json b/elm.json index 7e7678f..8186fe4 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "choonkeat/elm-webapp", "summary": "Small framework for writing fullstack HTTP webapp in Elm. Try `npx elm-webapp`", "license": "MIT", - "version": "1.0.2", + "version": "2.0.0", "exposed-modules": [ "Webapp.Server.HTTP", "Webapp.Server", diff --git a/package.json b/package.json index fe70195..d4499cc 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "elm-webapp": "./bin/elm-webapp" }, "name": "elm-webapp", - "version": "1.2.2", + "version": "2.0.0", "description": "cli to initialize an elm-webapp app", "main": "bin/elm-webapp", "scripts": { diff --git a/src/Application.elm b/src/Application.elm index 6298486..65ceaeb 100644 --- a/src/Application.elm +++ b/src/Application.elm @@ -9,9 +9,9 @@ import Http import Json.Decode import Json.Encode import Platform exposing (Task) -import Task import Protocol import Protocol.Auto +import Task import Url import Webapp.Client diff --git a/src/Document.elm b/src/Document.elm index 5779d9d..1a79db1 100644 --- a/src/Document.elm +++ b/src/Document.elm @@ -9,9 +9,9 @@ import Http import Json.Decode import Json.Encode import Platform exposing (Task) -import Task import Protocol import Protocol.Auto +import Task import Url import Webapp.Client diff --git a/src/Element.elm b/src/Element.elm index a005fbf..18678c9 100644 --- a/src/Element.elm +++ b/src/Element.elm @@ -9,9 +9,9 @@ import Http import Json.Decode import Json.Encode import Platform exposing (Task) -import Task import Protocol import Protocol.Auto +import Task import Url import Webapp.Client diff --git a/src/Webapp/Server.elm b/src/Webapp/Server.elm index 8098e41..67b40c2 100644 --- a/src/Webapp/Server.elm +++ b/src/Webapp/Server.elm @@ -25,11 +25,11 @@ import Dict exposing (Dict) import Json.Decode import Json.Encode import Platform exposing (Task) +import Protocol import Task import Time -import Protocol import Url -import Webapp.Server.HTTP exposing (Body, Headers, Method, Request, StatusCode(..), bodyOf, headersOf, methodOf, pathOf, urlOf) +import Webapp.Server.HTTP exposing (Body, Headers, Method, Request, Response, StatusCode(..), bodyOf, headersOf, methodOf, pathOf, urlOf) import Webapp.Server.Websocket import Webapp.Shared @@ -81,11 +81,7 @@ type alias PlatformWorker flags model msg = writeResponse : (Json.Encode.Value -> Cmd msg) -> Request - -> - { statusCode : StatusCode - , headers : List ( String, Json.Encode.Value ) - , body : String - } + -> Response -> Cmd msg writeResponse onHttpResponse request { statusCode, body, headers } = let @@ -138,14 +134,7 @@ writeWebsocketMessage writeWs connection key body = -} type alias Ports msg x serverMsg = - { writeResponse : - Request - -> - { statusCode : StatusCode - , headers : List ( String, Json.Encode.Value ) - , body : String - } - -> Cmd (FrameworkMsg msg x serverMsg) + { writeResponse : Request -> Response -> Cmd (FrameworkMsg msg x serverMsg) , onHttpRequest : (Json.Encode.Value -> FrameworkMsg msg x serverMsg) -> Sub (FrameworkMsg msg x serverMsg) , onWebsocketEvent : (Json.Encode.Value -> FrameworkMsg msg x serverMsg) -> Sub (FrameworkMsg msg x serverMsg) , writeWebsocketMessage : Webapp.Server.Websocket.Connection -> Webapp.Server.Websocket.Key -> String -> Cmd (FrameworkMsg msg x serverMsg) diff --git a/src/Webapp/Server/HTTP.elm b/src/Webapp/Server/HTTP.elm index dc0fdb9..2865518 100644 --- a/src/Webapp/Server/HTTP.elm +++ b/src/Webapp/Server/HTTP.elm @@ -1,5 +1,5 @@ module Webapp.Server.HTTP exposing - ( Body, Headers, Method(..), Request, StatusCode(..), Url + ( Body, Headers, Method(..), Request, Response, StatusCode(..), Url , bodyOf, headersOf, methodFromString, methodOf, methodString, pathOf, statusInt, urlOf ) @@ -8,7 +8,7 @@ module Webapp.Server.HTTP exposing # Definition -@docs Body, Headers, Method, Request, StatusCode, Url +@docs Body, Headers, Method, Request, Response, StatusCode, Url # Common Helpers @@ -122,6 +122,14 @@ type alias Request = Json.Decode.Value +{-| -} +type alias Response = + { statusCode : StatusCode + , headers : List ( String, Json.Encode.Value ) + , body : String + } + + {-| Alias for opaque Json.Encode.Value -} type alias Headers = diff --git a/templates/Makefile b/templates/Makefile index bd91722..596c0ff 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -24,10 +24,10 @@ src/Protocol/Auto.elm: src/Protocol.elm # WATCHING=false elm-auto-encoder-decoder src/Protocol.elm -build/Server.js: src/**/*.elm +build/Server.js: src/*.elm src/**/*.elm elm make src/Server.elm --output build/Server.js -public/assets/client.js: src/**/*.elm +public/assets/client.js: src/*.elm src/**/*.elm elm make src/Client.elm --output public/assets/client.js #