diff --git a/bin/elm-webapp b/bin/elm-webapp index c9477f5..8c86739 100755 --- a/bin/elm-webapp +++ b/bin/elm-webapp @@ -199,8 +199,8 @@ updateFromClient ctx now clientMsg serverState = -- -headerDecoder : ServerState -> Json.Decode.Decoder Protocol.RequestContext -headerDecoder serverState = +headerDecoder : Time.Posix -> ServerState -> Json.Decode.Decoder Protocol.RequestContext +headerDecoder now serverState = Json.Decode.oneOf [ Json.Decode.map Protocol.Cookied (Json.Decode.field "cookie" Json.Decode.string) , Json.Decode.succeed Protocol.Anonymous @@ -283,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 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"} +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 Array\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\nencodeArrayArray : (a -> Json.Encode.Value) -> Array.Array a -> Json.Encode.Value\nencodeArrayArray =\n Json.Encode.array\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\ndecodeArrayArray : (Json.Decode.Decoder a) -> Json.Decode.Decoder (Array.Array a)\ndecodeArrayArray =\n Json.Decode.array\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/elm.json b/elm.json index 8186fe4..368fc12 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": "2.0.0", + "version": "3.0.0", "exposed-modules": [ "Webapp.Server.HTTP", "Webapp.Server", diff --git a/src/Protocol/Auto.elm b/src/Protocol/Auto.elm index 4b9b5d4..b453333 100644 --- a/src/Protocol/Auto.elm +++ b/src/Protocol/Auto.elm @@ -5,6 +5,7 @@ module Protocol.Auto exposing (..) import Protocol exposing (..) +import Array import Dict import Json.Decode import Json.Encode @@ -41,6 +42,11 @@ encodeList = Json.Encode.list +encodeArrayArray : (a -> Json.Encode.Value) -> Array.Array a -> Json.Encode.Value +encodeArrayArray = + Json.Encode.array + + encodeSetSet : (comparable -> Json.Encode.Value) -> Set.Set comparable -> Json.Encode.Value encodeSetSet encoder = Set.toList >> encodeList encoder @@ -105,6 +111,11 @@ decodeList = Json.Decode.list +decodeArrayArray : (Json.Decode.Decoder a) -> Json.Decode.Decoder (Array.Array a) +decodeArrayArray = + Json.Decode.array + + decodeSetSet : (Json.Decode.Decoder comparable) -> Json.Decode.Decoder (Set.Set comparable) decodeSetSet = Json.Decode.list >> Json.Decode.map Set.fromList diff --git a/src/Webapp/Server.elm b/src/Webapp/Server.elm index 67b40c2..6311134 100644 --- a/src/Webapp/Server.elm +++ b/src/Webapp/Server.elm @@ -154,7 +154,7 @@ type alias Ports msg x serverMsg = -} type alias Protocol msg x serverMsg clientMsg route header model = - { headerDecoder : model -> Json.Decode.Decoder header + { headerDecoder : Time.Posix -> model -> Json.Decode.Decoder header , clientMsgDecoder : Json.Decode.Decoder clientMsg , updateFromClient : header -> Time.Posix -> clientMsg -> model -> ( model, Task x serverMsg ) , serverMsgEncoder : serverMsg -> Json.Encode.Value @@ -243,7 +243,7 @@ update appUpdate ports protocol msg model = maybeHeader = headersOf request - |> Json.Decode.decodeValue (protocol.headerDecoder model.appModel) + |> Json.Decode.decodeValue (protocol.headerDecoder now model.appModel) |> Result.toMaybe clientMsgResult = @@ -324,14 +324,14 @@ routeWebsocketRequest : Time.Posix -> (header -> Time.Posix -> clientMsg -> model -> ( model, Task x serverMsg )) -> Json.Decode.Decoder clientMsg - -> (model -> Json.Decode.Decoder header) + -> (Time.Posix -> model -> Json.Decode.Decoder header) -> FrameworkModel model header -> Webapp.Server.Websocket.WebsocketEvent -> ( FrameworkModel model header, Cmd (FrameworkMsg msg x serverMsg) ) routeWebsocketRequest now updateFromClient clientMsgDecoder headerDecoder model event = case event of Webapp.Server.Websocket.Open conn key rawHeaders -> - case Json.Decode.decodeValue (headerDecoder model.appModel) rawHeaders of + case Json.Decode.decodeValue (headerDecoder now model.appModel) rawHeaders of Ok headers -> let newWebsockets =