Initial Commit
This commit is contained in:
commit
62e7f5e6d2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
elm-stuff/
|
||||||
|
build/
|
32
Makefile
Normal file
32
Makefile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
BACKGROUND_ELM_FILES=$(shell find src/Background/ -name '*.elm')
|
||||||
|
FOREGROUND_ELM_FILES=$(shell find src/Foreground/ -name '*.elm')
|
||||||
|
BACKGROUND_JS_FILES=$(patsubst src/%.elm,build/%.js,$(BACKGROUND_ELM_FILES))
|
||||||
|
FOREGROUND_JS_FILES=$(patsubst src/%.elm,build/%.js,$(FOREGROUND_ELM_FILES))
|
||||||
|
|
||||||
|
DEPENDS_BACKGROUND := background.js
|
||||||
|
DEPENDS_FOREGROUND := app.js
|
||||||
|
DEPENDS := build/background.js build/foreground.js
|
||||||
|
COPY := manifest.json app.html
|
||||||
|
|
||||||
|
package: all
|
||||||
|
cd build && zip -FS extension.xpi *
|
||||||
|
|
||||||
|
all: clean prepare $(COPY) $(DEPENDS)
|
||||||
|
cp $(COPY) build
|
||||||
|
|
||||||
|
build/foreground.js: $(FOREGROUND_JS_FILES) $(DEPENDS_FOREGROUND)
|
||||||
|
cat $^ > $@
|
||||||
|
|
||||||
|
build/background.js: $(BACKGROUND_JS_FILES) $(DEPENDS_BACKGROUND)
|
||||||
|
cat $^ > $@
|
||||||
|
|
||||||
|
build/%.js: src/%.elm
|
||||||
|
elm make --optimize $^ --output=$@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
[ -e build ] && rm -r build || /bin/true
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
[ ! -e build ] && mkdir build || /bin/true
|
||||||
|
|
||||||
|
.PHONY: package all clean prepare
|
9
app.html
Normal file
9
app.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<script src="foreground.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
app.js
Normal file
13
app.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
let app;
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
app = Elm.Foreground.App.init();
|
||||||
|
|
||||||
|
app.ports.consoleLog.subscribe(message => console.log(message));
|
||||||
|
|
||||||
|
app.ports.localStorageGet.subscribe(key => app.ports.localStorageRecv.send([key, window.localStorage.getItem(key) ?? "{}"]));
|
||||||
|
|
||||||
|
app.ports.localStorageSet?.subscribe(([key, value]) => {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
browser.runtime.sendMessage("");
|
||||||
|
});
|
||||||
|
});
|
57
background.js
Normal file
57
background.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
let app = Elm.Background.Background.init();
|
||||||
|
|
||||||
|
app.ports.consoleLog.subscribe(message => console.log(message));
|
||||||
|
|
||||||
|
app.ports.localStorageGet.subscribe(key => app.ports.localStorageRecv.send([key, window.localStorage.getItem(key) ?? "{}"]));
|
||||||
|
|
||||||
|
let requestResolveMap = {};
|
||||||
|
let responseResolveMap = {};
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
requestDetails => {
|
||||||
|
let resolve;
|
||||||
|
let p = new Promise((r, _) => resolve = r);
|
||||||
|
|
||||||
|
requestResolveMap[requestDetails.requestId] = resolve;
|
||||||
|
app.ports.incomingRequest.send([requestDetails.requestId, JSON.stringify(requestDetails)]);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
{urls: ["<all_urls>"]},
|
||||||
|
["blocking", "requestHeaders"]
|
||||||
|
);
|
||||||
|
|
||||||
|
browser.webRequest.onHeadersReceived.addListener(
|
||||||
|
responseDetails => {
|
||||||
|
let resolve;
|
||||||
|
let p = new Promise((r, _) => resolve = r);
|
||||||
|
|
||||||
|
responseResolveMap[responseDetails.requestId] = resolve;
|
||||||
|
app.ports.incomingResponse.send([responseDetails.requestId, JSON.stringify(responseDetails)]);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
{urls: ["<all_urls>"]},
|
||||||
|
["blocking", "responseHeaders"]
|
||||||
|
);
|
||||||
|
|
||||||
|
app.ports.modifyRequest.subscribe(([id, body]) => {
|
||||||
|
requestResolveMap[id](body)
|
||||||
|
delete requestResolveMap[id];
|
||||||
|
});
|
||||||
|
|
||||||
|
app.ports.modifyResponse.subscribe(([id, body]) => {
|
||||||
|
responseResolveMap[id](body)
|
||||||
|
delete responseResolveMap[id];
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(() => app.ports.refresh.send(""));
|
||||||
|
|
||||||
|
browser.browserAction.onClicked.addListener(() => {
|
||||||
|
let createProperties = {
|
||||||
|
active: true,
|
||||||
|
url: "app.html",
|
||||||
|
};
|
||||||
|
|
||||||
|
browser.tabs.create(createProperties);
|
||||||
|
});
|
26
elm.json
Normal file
26
elm.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "application",
|
||||||
|
"source-directories": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"elm-version": "0.19.1",
|
||||||
|
"dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"elm/browser": "1.0.2",
|
||||||
|
"elm/core": "1.0.5",
|
||||||
|
"elm/html": "1.0.0",
|
||||||
|
"elm/json": "1.1.3",
|
||||||
|
"NoRedInk/elm-json-decode-pipeline": "1.0.1",
|
||||||
|
"elm/regex": "1.0.0"
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/url": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
21
manifest.json
Normal file
21
manifest.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Request Manipulator",
|
||||||
|
"version": "1.0",
|
||||||
|
|
||||||
|
"description": "Allows manipulating requests",
|
||||||
|
|
||||||
|
"permissions": [
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"browser_action": {
|
||||||
|
"default_title": "Request Manipulator"
|
||||||
|
}
|
||||||
|
}
|
205
src/Background/Background.elm
Normal file
205
src/Background/Background.elm
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
port module Background.Background exposing (..)
|
||||||
|
|
||||||
|
import Common exposing (..)
|
||||||
|
|
||||||
|
import Platform
|
||||||
|
|
||||||
|
import Json.Encode as E
|
||||||
|
import Json.Decode as D
|
||||||
|
import Json.Decode.Pipeline as P
|
||||||
|
import Regex
|
||||||
|
|
||||||
|
port consoleLog : String -> Cmd msg
|
||||||
|
port localStorageRecv : ((String, String) -> msg) -> Sub msg
|
||||||
|
|
||||||
|
port incomingRequest : ((String, String) -> msg) -> Sub msg
|
||||||
|
port modifyRequest : (String, E.Value) -> Cmd msg
|
||||||
|
|
||||||
|
port incomingResponse : ((String, String) -> msg) -> Sub msg
|
||||||
|
port modifyResponse : (String, E.Value) -> Cmd msg
|
||||||
|
|
||||||
|
port refresh : (String -> msg) -> Sub msg
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ enabled : Bool
|
||||||
|
, requestActions : List (Regex.Regex, Action)
|
||||||
|
, responseActions : List (Regex.Regex, Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
initial_model : Model
|
||||||
|
initial_model =
|
||||||
|
{ enabled = False
|
||||||
|
, requestActions = []
|
||||||
|
, responseActions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
toModel : PreModel -> Model
|
||||||
|
toModel pm =
|
||||||
|
let
|
||||||
|
try_regex (s, a) =
|
||||||
|
Maybe.map (\r -> (r, a)) (Regex.fromString ("^" ++ s ++ "$"))
|
||||||
|
in
|
||||||
|
{ enabled = pm.enabled
|
||||||
|
, requestActions = List.filterMap try_regex pm.requestActions
|
||||||
|
, responseActions = List.filterMap try_regex pm.responseActions
|
||||||
|
}
|
||||||
|
|
||||||
|
init : () -> (Model, Cmd msg)
|
||||||
|
init _ =
|
||||||
|
( initial_model
|
||||||
|
, localStorageGet "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= IncomingRequest (String, String)
|
||||||
|
| IncomingResponse (String, String)
|
||||||
|
| LocalStorageRecv (String, String)
|
||||||
|
| Refresh String
|
||||||
|
|
||||||
|
assocListFind : List (a, b) -> a -> Bool
|
||||||
|
assocListFind list a =
|
||||||
|
List.any (\x -> Tuple.first x == a) list
|
||||||
|
|
||||||
|
assocListSet : List (a, b) -> a -> b -> List (a, b)
|
||||||
|
assocListSet list a b =
|
||||||
|
List.map (\(l, r) -> if l == a then (l, b) else (l, r)) list
|
||||||
|
|
||||||
|
assocListGetJson : List (Regex.Regex, b) -> String -> List b
|
||||||
|
assocListGetJson list a =
|
||||||
|
let
|
||||||
|
assocListGetJson_i acc list_ =
|
||||||
|
case list_ of
|
||||||
|
[] -> acc
|
||||||
|
((l,r)::t) -> if Regex.contains l a then assocListGetJson_i (r::acc) t else assocListGetJson_i acc t
|
||||||
|
in assocListGetJson_i [] list
|
||||||
|
|
||||||
|
type alias HttpHeader =
|
||||||
|
{ name : String
|
||||||
|
, value : String
|
||||||
|
}
|
||||||
|
|
||||||
|
httpHeaderDecoder : D.Decoder HttpHeader
|
||||||
|
httpHeaderDecoder =
|
||||||
|
D.succeed HttpHeader
|
||||||
|
|> P.required "name" D.string
|
||||||
|
|> P.optional "value" D.string ""
|
||||||
|
|
||||||
|
type alias Request =
|
||||||
|
{ incognito : Bool
|
||||||
|
, method : String
|
||||||
|
, requestId : String
|
||||||
|
, timeStamp : Int
|
||||||
|
, resource_type : String
|
||||||
|
, url : String
|
||||||
|
, requestHeaders : List HttpHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDecoder : D.Decoder Request
|
||||||
|
requestDecoder =
|
||||||
|
D.succeed Request
|
||||||
|
|> P.required "incognito" D.bool
|
||||||
|
|> P.required "method" D.string
|
||||||
|
|> P.required "requestId" D.string
|
||||||
|
|> P.required "timeStamp" D.int
|
||||||
|
|> P.required "type" D.string
|
||||||
|
|> P.required "url" D.string
|
||||||
|
|> P.optional "requestHeaders" (D.list httpHeaderDecoder) []
|
||||||
|
|
||||||
|
type alias Response =
|
||||||
|
{ incognito : Bool
|
||||||
|
, method : String
|
||||||
|
, requestId : String
|
||||||
|
, timeStamp : Int
|
||||||
|
, resource_type : String
|
||||||
|
, url : String
|
||||||
|
, responseHeaders : List HttpHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
responseDecoder : D.Decoder Response
|
||||||
|
responseDecoder =
|
||||||
|
D.succeed Response
|
||||||
|
|> P.required "incognito" D.bool
|
||||||
|
|> P.required "method" D.string
|
||||||
|
|> P.required "requestId" D.string
|
||||||
|
|> P.required "timeStamp" D.int
|
||||||
|
|> P.required "type" D.string
|
||||||
|
|> P.required "url" D.string
|
||||||
|
|> P.optional "responseHeaders" (D.list httpHeaderDecoder) []
|
||||||
|
|
||||||
|
doNothingReq : String -> Cmd msg
|
||||||
|
doNothingReq id = modifyRequest (id, E.object [])
|
||||||
|
|
||||||
|
doNothingRes : String -> Cmd msg
|
||||||
|
doNothingRes id = modifyResponse (id, E.object [])
|
||||||
|
|
||||||
|
commandOfActions : Bool -> String -> ((String, E.Value) -> Cmd msg) -> List HttpHeader -> List Action -> Cmd msg
|
||||||
|
commandOfActions response id makeCmd headers actions =
|
||||||
|
let
|
||||||
|
commandOfActions_i headers_ l =
|
||||||
|
case l of
|
||||||
|
[] -> makeCmd (id, E.object [(if response then "responseHeaders" else "requestHeaders", E.list (\x -> E.object [("name", E.string x.name), ("value", E.string x.value)]) headers_)])
|
||||||
|
(Redirect dest :: _) -> makeCmd (id, E.object [("redirectUrl", E.string dest)])
|
||||||
|
(ModifyHeader (h, v) :: t) -> commandOfActions_i (List.map (\p -> if p.name == h then {p | value = v} else p) headers_) t
|
||||||
|
(AddHeader (h, v) :: t) -> commandOfActions_i ({name = h, value = v} :: headers_) t
|
||||||
|
(RemoveHeader h :: t) -> commandOfActions_i (List.filterMap (\p -> if p.name == h then Nothing else Just p) headers_) t
|
||||||
|
in
|
||||||
|
commandOfActions_i headers actions
|
||||||
|
|
||||||
|
update : Msg -> Model -> (Model, Cmd msg)
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
IncomingRequest (id, req) ->
|
||||||
|
if model.enabled then
|
||||||
|
case D.decodeString requestDecoder req of
|
||||||
|
Ok r ->
|
||||||
|
let actions = assocListGetJson model.requestActions r.url in
|
||||||
|
(model, commandOfActions False id modifyRequest r.requestHeaders actions)
|
||||||
|
Err _ -> (model, doNothingReq id)
|
||||||
|
else
|
||||||
|
(model, doNothingReq id)
|
||||||
|
|
||||||
|
IncomingResponse (id, res) ->
|
||||||
|
if model.enabled then
|
||||||
|
case D.decodeString responseDecoder res of
|
||||||
|
Ok r ->
|
||||||
|
let actions = assocListGetJson model.responseActions r.url in
|
||||||
|
(model, Cmd.batch [commandOfActions True id modifyResponse r.responseHeaders actions, consoleLog r.url])
|
||||||
|
Err _ -> (model, doNothingRes id)
|
||||||
|
else
|
||||||
|
(model, doNothingRes id)
|
||||||
|
|
||||||
|
LocalStorageRecv (k, v) ->
|
||||||
|
case k of
|
||||||
|
"config" ->
|
||||||
|
case D.decodeString preModelDecoder v of
|
||||||
|
Ok m ->
|
||||||
|
( m |> toModel
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
( initial_model
|
||||||
|
, consoleLog v
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
( model
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
Refresh _ ->
|
||||||
|
( model
|
||||||
|
, localStorageGet "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
main : Program () Model Msg
|
||||||
|
main = Platform.worker
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.batch
|
||||||
|
[ incomingRequest IncomingRequest
|
||||||
|
, incomingResponse IncomingResponse
|
||||||
|
, localStorageRecv LocalStorageRecv
|
||||||
|
, refresh Refresh
|
||||||
|
]
|
||||||
|
}
|
37
src/Common.elm
Normal file
37
src/Common.elm
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
port module Common exposing (..)
|
||||||
|
|
||||||
|
import Json.Decode as D
|
||||||
|
|
||||||
|
port localStorageGet : String -> Cmd msg
|
||||||
|
|
||||||
|
type Action
|
||||||
|
= Redirect String
|
||||||
|
| ModifyHeader (String, String)
|
||||||
|
| AddHeader (String, String)
|
||||||
|
| RemoveHeader String
|
||||||
|
|
||||||
|
actionDecoder : D.Decoder Action
|
||||||
|
actionDecoder =
|
||||||
|
D.field "type" D.string
|
||||||
|
|> D.andThen
|
||||||
|
(\actionType ->
|
||||||
|
case actionType of
|
||||||
|
"redirect" -> D.map Redirect (D.field "destination" D.string)
|
||||||
|
"modify" -> D.map2 (\a b -> ModifyHeader (a,b)) (D.field "header" D.string) (D.field "value" D.string)
|
||||||
|
"add" -> D.map2 (\a b -> AddHeader (a,b)) (D.field "header" D.string) (D.field "value" D.string)
|
||||||
|
"remove" -> D.map RemoveHeader (D.field "header" D.string)
|
||||||
|
_ -> D.fail "Failed to decode action"
|
||||||
|
)
|
||||||
|
|
||||||
|
type alias PreModel =
|
||||||
|
{ enabled : Bool
|
||||||
|
, requestActions : List (String, Action)
|
||||||
|
, responseActions : List (String, Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
preModelDecoder : D.Decoder PreModel
|
||||||
|
preModelDecoder =
|
||||||
|
D.map3 PreModel
|
||||||
|
(D.field "enabled" D.bool)
|
||||||
|
(D.field "request_actions" (D.list (D.map2 (\a b -> (a, b)) (D.field "target" D.string) (D.field "action" actionDecoder))))
|
||||||
|
(D.field "response_actions" (D.list (D.map2 (\a b -> (a, b)) (D.field "target" D.string) (D.field "action" actionDecoder))))
|
364
src/Foreground/App.elm
Normal file
364
src/Foreground/App.elm
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
port module Foreground.App exposing (..)
|
||||||
|
|
||||||
|
import Common exposing (..)
|
||||||
|
|
||||||
|
import Browser exposing (Document)
|
||||||
|
import Json.Decode as D
|
||||||
|
import Json.Encode as E
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Html.Attributes exposing (action)
|
||||||
|
import Html.Events exposing (onInput)
|
||||||
|
import Html.Events exposing (onClick)
|
||||||
|
import Background.Background exposing (Request)
|
||||||
|
|
||||||
|
port localStorageSet : (String, String) -> Cmd msg
|
||||||
|
|
||||||
|
port localStorageRecv : ((String, String) -> msg) -> Sub msg
|
||||||
|
port consoleLog : String -> Cmd msg
|
||||||
|
|
||||||
|
type Type
|
||||||
|
= Request
|
||||||
|
| Response
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= LocalStorageRecv (String, String)
|
||||||
|
| TypeSelectChanged (Type, Int, String)
|
||||||
|
| UpdateTarget (Type, Int, String)
|
||||||
|
| UpdateV1 (Type, Int, String)
|
||||||
|
| UpdateV2 (Type, Int, String)
|
||||||
|
| RemoveRule (Type, Int)
|
||||||
|
| AddRule Type
|
||||||
|
| Save
|
||||||
|
| Toggle
|
||||||
|
|
||||||
|
valueOfAction : Action -> String
|
||||||
|
valueOfAction action =
|
||||||
|
case action of
|
||||||
|
Redirect _ -> "redirect"
|
||||||
|
AddHeader _ -> "add"
|
||||||
|
ModifyHeader _ -> "modify"
|
||||||
|
RemoveHeader _ -> "remove"
|
||||||
|
|
||||||
|
type ActionType
|
||||||
|
= RedirectT
|
||||||
|
| AddT
|
||||||
|
| ModifyT
|
||||||
|
| RemoveT
|
||||||
|
|
||||||
|
stringOfActionType : ActionType -> String
|
||||||
|
stringOfActionType a =
|
||||||
|
case a of
|
||||||
|
RedirectT -> "Redirect"
|
||||||
|
AddT -> "Add Header"
|
||||||
|
ModifyT -> "Modify Header"
|
||||||
|
RemoveT -> "Remove Header"
|
||||||
|
|
||||||
|
valueOfActionType : ActionType -> String
|
||||||
|
valueOfActionType a =
|
||||||
|
case a of
|
||||||
|
RedirectT -> "redirect"
|
||||||
|
AddT -> "add"
|
||||||
|
ModifyT -> "modify"
|
||||||
|
RemoveT -> "remove"
|
||||||
|
|
||||||
|
actionTypeOfValue : String -> Maybe ActionType
|
||||||
|
actionTypeOfValue v =
|
||||||
|
case v of
|
||||||
|
"redirect" -> Just RedirectT
|
||||||
|
"add" -> Just AddT
|
||||||
|
"modify" -> Just ModifyT
|
||||||
|
"remove" -> Just RemoveT
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
viewOfEntry : Type -> Int -> (String, Action) -> Html Msg
|
||||||
|
viewOfEntry t index (url, action) =
|
||||||
|
Html.div
|
||||||
|
[]
|
||||||
|
(
|
||||||
|
[ Html.text "Target: "
|
||||||
|
, Html.input [Html.Attributes.value url, onInput (\a -> UpdateTarget (t, index, a))] []
|
||||||
|
, let
|
||||||
|
valid_values =
|
||||||
|
if t == Request then
|
||||||
|
[RedirectT, AddT, ModifyT, RedirectT]
|
||||||
|
else
|
||||||
|
[AddT, ModifyT, RedirectT]
|
||||||
|
options =
|
||||||
|
valid_values
|
||||||
|
|> List.map (\v -> Html.option [Html.Attributes.value <| valueOfActionType v, Html.Attributes.selected (valueOfAction action == valueOfActionType v)] [Html.text <| stringOfActionType v])
|
||||||
|
in
|
||||||
|
Html.select [onInput (\a -> TypeSelectChanged (t, index, a))] options
|
||||||
|
]
|
||||||
|
++
|
||||||
|
(
|
||||||
|
case action of
|
||||||
|
Redirect d ->
|
||||||
|
[ Html.text "Destination: "
|
||||||
|
, Html.input [Html.Attributes.value d, onInput (\a -> UpdateV1 (t, index, a))] []
|
||||||
|
]
|
||||||
|
AddHeader (h,v) ->
|
||||||
|
[ Html.text "Header Name: "
|
||||||
|
, Html.input [Html.Attributes.value h, onInput (\a -> UpdateV1 (t, index, a))] []
|
||||||
|
, Html.text "Header Value: "
|
||||||
|
, Html.input [Html.Attributes.value v, onInput (\a -> UpdateV2 (t, index, a))] []
|
||||||
|
]
|
||||||
|
ModifyHeader (h,v) ->
|
||||||
|
[ Html.text "Header Name: "
|
||||||
|
, Html.input [Html.Attributes.value h, onInput (\a -> UpdateV1 (t, index, a))] []
|
||||||
|
, Html.text "Header Value: "
|
||||||
|
, Html.input [Html.Attributes.value v, onInput (\a -> UpdateV2 (t, index, a))] []
|
||||||
|
]
|
||||||
|
RemoveHeader h ->
|
||||||
|
[ Html.text "Header Name: "
|
||||||
|
, Html.input [Html.Attributes.value h, onInput (\a -> UpdateV1 (t, index, a))] []
|
||||||
|
]
|
||||||
|
)
|
||||||
|
++
|
||||||
|
[ Html.button [onClick (RemoveRule (t, index))] [Html.text "x"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ enabled : Bool
|
||||||
|
, requestActions : List (String, Action)
|
||||||
|
, responseActions : List (String, Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeAction : Action -> E.Value
|
||||||
|
encodeAction a =
|
||||||
|
case a of
|
||||||
|
Redirect d -> E.object
|
||||||
|
[ ("type", E.string "redirect")
|
||||||
|
, ("destination", E.string d)
|
||||||
|
]
|
||||||
|
ModifyHeader (h,v) -> E.object
|
||||||
|
[ ("type", E.string "modify")
|
||||||
|
, ("header", E.string h)
|
||||||
|
, ("value", E.string v)
|
||||||
|
]
|
||||||
|
AddHeader (h,v) -> E.object
|
||||||
|
[ ("type", E.string "add")
|
||||||
|
, ("header", E.string h)
|
||||||
|
, ("value", E.string v)
|
||||||
|
]
|
||||||
|
RemoveHeader h -> E.object
|
||||||
|
[ ("type", E.string "remove")
|
||||||
|
, ("header", E.string h)
|
||||||
|
]
|
||||||
|
|
||||||
|
encodeRule : (String, Action) -> E.Value
|
||||||
|
encodeRule (t, a) =
|
||||||
|
E.object
|
||||||
|
[ ("target", E.string t)
|
||||||
|
, ("action", encodeAction a)
|
||||||
|
]
|
||||||
|
|
||||||
|
encodeModel : Model -> E.Value
|
||||||
|
encodeModel model =
|
||||||
|
E.object
|
||||||
|
[ ("enabled", E.bool model.enabled)
|
||||||
|
, ("request_actions", E.list encodeRule model.requestActions)
|
||||||
|
, ("response_actions", E.list encodeRule model.responseActions)
|
||||||
|
]
|
||||||
|
|
||||||
|
initial_model : Model
|
||||||
|
initial_model =
|
||||||
|
{ enabled = False
|
||||||
|
, requestActions = []
|
||||||
|
, responseActions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
toModel : PreModel -> Model
|
||||||
|
toModel pm = pm
|
||||||
|
|
||||||
|
init : () -> (Model, Cmd msg)
|
||||||
|
init _ =
|
||||||
|
( initial_model
|
||||||
|
, localStorageGet "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
view : Model -> Document Msg
|
||||||
|
view model =
|
||||||
|
{ title = "Elm Header Modification Extension"
|
||||||
|
, body =
|
||||||
|
[ Html.button [onClick Toggle] [Html.text <| if model.enabled then "Disable" else "Enable"]
|
||||||
|
, Html.button [onClick Save] [Html.text "Save"]
|
||||||
|
, Html.h3 [] [Html.text "Request Rules"]
|
||||||
|
, Html.div [] (model.requestActions |> (List.indexedMap <| viewOfEntry Request))
|
||||||
|
, Html.button [onClick <| AddRule Request] [Html.text "+"]
|
||||||
|
, Html.h3 [] [Html.text "Response Rules"]
|
||||||
|
, Html.div [] (model.responseActions |> (List.indexedMap <| viewOfEntry Response))
|
||||||
|
, Html.button [onClick <| AddRule Response] [Html.text "+"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceAction : Type -> Int -> Action -> Model -> Model
|
||||||
|
replaceAction t index v model =
|
||||||
|
case t of
|
||||||
|
Request -> {model | requestActions = model.requestActions |> List.indexedMap (\index_ ((url,_) as e)-> if index_ == index then (url,v) else e)}
|
||||||
|
Response -> {model | responseActions = model.responseActions |> List.indexedMap (\index_ ((url,_) as e)-> if index_ == index then (url,v) else e)}
|
||||||
|
|
||||||
|
update : Msg -> Model -> (Model, Cmd msg)
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
LocalStorageRecv (k, v) ->
|
||||||
|
case k of
|
||||||
|
"config" ->
|
||||||
|
case D.decodeString preModelDecoder v of
|
||||||
|
Ok m ->
|
||||||
|
( m |> toModel
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
( initial_model
|
||||||
|
, consoleLog "Failed to decode config!"
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
( model
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
TypeSelectChanged (t, index, kind) ->
|
||||||
|
let
|
||||||
|
handleChange a = case a of
|
||||||
|
RedirectT ->
|
||||||
|
if t == Request then
|
||||||
|
replaceAction t index (Redirect "") model
|
||||||
|
else
|
||||||
|
model
|
||||||
|
AddT -> replaceAction t index (AddHeader ("", "")) model
|
||||||
|
ModifyT -> replaceAction t index (ModifyHeader ("", "")) model
|
||||||
|
RemoveT -> replaceAction t index (RemoveHeader "") model
|
||||||
|
in
|
||||||
|
( Maybe.map handleChange (actionTypeOfValue kind) |> Maybe.withDefault model
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
UpdateTarget (t, index, v) ->
|
||||||
|
let
|
||||||
|
updateTarget = List.indexedMap
|
||||||
|
(
|
||||||
|
\i ((_,b) as e) ->
|
||||||
|
if i == index then
|
||||||
|
(v, b)
|
||||||
|
else
|
||||||
|
e
|
||||||
|
)
|
||||||
|
in
|
||||||
|
( case t of
|
||||||
|
Request ->
|
||||||
|
{ model |
|
||||||
|
requestActions = model.requestActions |> updateTarget
|
||||||
|
}
|
||||||
|
Response ->
|
||||||
|
{ model |
|
||||||
|
responseActions = model.responseActions |> updateTarget
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
UpdateV1 (t, index, v) ->
|
||||||
|
let
|
||||||
|
updateV1 = List.indexedMap
|
||||||
|
(
|
||||||
|
\index_ ((url,action) as entry) ->
|
||||||
|
if index_ == index then
|
||||||
|
case action of
|
||||||
|
Redirect _ -> (url, Redirect v)
|
||||||
|
AddHeader (_, r) -> (url, AddHeader (v, r))
|
||||||
|
ModifyHeader (_, r) -> (url, ModifyHeader (v, r))
|
||||||
|
RemoveHeader _ -> (url, RemoveHeader v)
|
||||||
|
else
|
||||||
|
entry
|
||||||
|
)
|
||||||
|
in
|
||||||
|
( case t of
|
||||||
|
Request ->
|
||||||
|
{ model |
|
||||||
|
requestActions = model.requestActions |> updateV1
|
||||||
|
}
|
||||||
|
Response ->
|
||||||
|
{ model |
|
||||||
|
responseActions = model.responseActions |> updateV1
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
UpdateV2 (t, index, v) ->
|
||||||
|
let
|
||||||
|
updateV2 = List.indexedMap
|
||||||
|
(
|
||||||
|
\index_ ((url,action) as entry) ->
|
||||||
|
if index_ == index then
|
||||||
|
case action of
|
||||||
|
AddHeader (l, _) -> (url, AddHeader (l, v))
|
||||||
|
ModifyHeader (l, _) -> (url, ModifyHeader (l, v))
|
||||||
|
_ -> entry
|
||||||
|
else
|
||||||
|
entry
|
||||||
|
)
|
||||||
|
in
|
||||||
|
( case t of
|
||||||
|
Request ->
|
||||||
|
{ model |
|
||||||
|
requestActions = model.requestActions |> updateV2
|
||||||
|
}
|
||||||
|
Response ->
|
||||||
|
{ model |
|
||||||
|
responseActions = model.responseActions |> updateV2
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
RemoveRule (t, index) ->
|
||||||
|
( case t of
|
||||||
|
Request ->
|
||||||
|
{ model |
|
||||||
|
requestActions = model.requestActions |> List.indexedMap (\i e -> if i == index then Nothing else Just e) |> List.filterMap identity
|
||||||
|
}
|
||||||
|
Response ->
|
||||||
|
{ model |
|
||||||
|
responseActions = model.responseActions |> List.indexedMap (\i e -> if i == index then Nothing else Just e) |> List.filterMap identity
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
AddRule t ->
|
||||||
|
( case t of
|
||||||
|
Request ->
|
||||||
|
{ model |
|
||||||
|
requestActions = model.requestActions ++ [("", Redirect "")]
|
||||||
|
}
|
||||||
|
Response ->
|
||||||
|
{ model |
|
||||||
|
responseActions = model.responseActions ++ [("", AddHeader ("", ""))]
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
Save ->
|
||||||
|
( model
|
||||||
|
, localStorageSet ("config", E.encode 0 <| encodeModel model)
|
||||||
|
)
|
||||||
|
|
||||||
|
Toggle ->
|
||||||
|
let
|
||||||
|
new_model =
|
||||||
|
{ model |
|
||||||
|
enabled = not model.enabled
|
||||||
|
}
|
||||||
|
in
|
||||||
|
( new_model
|
||||||
|
, localStorageSet ("config", E.encode 0 <| encodeModel new_model)
|
||||||
|
)
|
||||||
|
|
||||||
|
main : Program () Model Msg
|
||||||
|
main = Browser.document
|
||||||
|
{ init = init
|
||||||
|
, view = view
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.batch
|
||||||
|
[localStorageRecv LocalStorageRecv]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user