From 07b6a49223b061904c62c01b1746948489feab41 Mon Sep 17 00:00:00 2001
From: Markin Igor <markin.io210@gmail.com>
Date: Tue, 6 Nov 2018 10:17:49 +0300
Subject: [PATCH] Add iframe generation code.

---
 .gitignore |   1 +
 Gopkg.toml |   3 +
 main.go    | 774 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 778 insertions(+)

diff --git a/.gitignore b/.gitignore
index b200425..b01ed7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .idea
 *.iml
 bin/
+Gopkg.lock
diff --git a/Gopkg.toml b/Gopkg.toml
index d7072c2..06f9de1 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -24,6 +24,9 @@
 #   go-tests = true
 #   unused-packages = true
 
+[[constraint]]
+  branch = "feature-transpile-js-libs"
+  name = "code.vereign.com/code/restful-api"
 
 [prune]
   go-tests = true
diff --git a/main.go b/main.go
index 4473a77..5eb0421 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,17 @@
 package jslibs
 
 import (
+	"bytes"
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
+	"reflect"
+	"sort"
 	"strings"
+	"unicode"
+	"unicode/utf8"
+
+	"code.vereign.com/code/restful-api/types"
 )
 
 func GetClientJsLibrary(iframeUrl string) string {
@@ -22,3 +29,770 @@ func GetClientJsLibrary(iframeUrl string) string {
 
 	return strings.Replace(string(dat), "{{urlArg}}", iframeUrl, 1) + "\n"
 }
+
+func GetIframeJsLibrary(host string,
+	endPoints map[string]*types.EndPoint) string {
+	result := "<script>\n\n"
+
+	var keys []string
+	for k := range endPoints {
+		keys = append(keys, k)
+	}
+
+	sort.Strings(keys)
+
+	keysLen := len(keys)
+
+	dat, err := ioutil.ReadFile("../vcl/javascript/dist/viamapi-iframe.js")
+
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+
+	methods := generatePenpalRemoteMethods(host, endPoints)
+	methods += getWopiAPIPenpalMethods()
+
+	viamApi := strings.Replace(string(dat), "placeholderForExternalMethods:1", methods, 1)
+
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+
+	result += viamApi + "\n\n"
+
+	result += getWopiAPI(host)
+
+	result += "\n\nfunction ViamAPI() {\n" +
+		"    this.config = {\n" +
+		"        headers: {\n" +
+		"            'publicKey': '',\n" +
+		"            'uuid': '',\n" +
+		"            'token': ''\n" +
+		"        }\n" +
+		"    }\n" +
+		"}\n\n"
+
+	result += "ViamAPI.prototype.setSessionData = function(uuid, token) {\n" +
+		"    this.config.headers.uuid = \" \" + uuid;\n" +
+		"    this.config.headers.token = \" \" + token;\n" +
+		"}\n\n"
+
+	result += "ViamAPI.prototype.setIdentity = function(authenticationPublicKey) {\n" +
+		"    this.config.headers.publicKey = \" \" + window.btoa(authenticationPublicKey);\n" +
+		"}\n\n"
+
+	result += "ViamAPI.prototype.getConfig = function() {\n" +
+		"    return this.config;\n" +
+		"}\n\n"
+
+	for i := 0; i < keysLen; i++ {
+		url := keys[i]
+		if endPoints[url].Form != nil {
+			splits := strings.Split(url, "/")
+
+			packageStr := splits[len(splits)-2]
+
+			/*if !packageCreated[packageStr] {
+				result += "ViamAPI.prototype.\"" + packageStr + "\" = {}\n\n"
+				packageCreated[packageStr] = true
+			}*/
+
+			methodStr := splits[len(splits)-1]
+
+			form := endPoints[url].Form
+			s := reflect.ValueOf(form)
+
+			typeOfT := s.Type()
+
+			fields := typeFields(typeOfT)
+
+			lenFields := len(fields)
+
+			args := ""
+
+			for i := 0; i < lenFields; i++ {
+				if i != lenFields-1 {
+					args += fmt.Sprintf("%sArg,", fields[i].name)
+				} else {
+					args += fmt.Sprintf("%sArg", fields[i].name)
+				}
+			}
+
+			result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function(" + args + ") {\n" +
+				"    return axios.post('" + host + packageStr + "/" + methodStr + "', {\n"
+
+			for i := 0; i < lenFields; i++ {
+				result += "        " + fields[i].name + ": " + fields[i].name + "Arg"
+				if i != lenFields-1 {
+					result += ","
+				}
+				result += "\n"
+			}
+
+			result += "    }, this.config);\n" +
+				"}\n\n"
+		} else {
+			splits := strings.Split(url, "/")
+
+			packageStr := splits[len(splits)-2]
+
+			/*if !packageCreated[packageStr] {
+				result += "api[\"" + packageStr + "\"] = {}\n"
+				packageCreated[packageStr] = true
+			}*/
+
+			methodStr := splits[len(splits)-1]
+
+			result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function() {\n" +
+				"    return axios.post('" + host + "/" + packageStr + "/" + methodStr + "', {}, this.config);\n" +
+				"}\n\n"
+		}
+	}
+
+	result += "\n\n</script>"
+
+	return result
+}
+
+const (
+	caseMask     = ^byte(0x20) // Mask to ignore case in ASCII.
+	kelvin       = '\u212a'
+	smallLongEss = '\u017f'
+)
+
+// equalFoldRight is a specialization of bytes.EqualFold when s is
+// known to be all ASCII (including punctuation), but contains an 's',
+// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
+// See comments on foldFunc.
+func equalFoldRight(s, t []byte) bool {
+	for _, sb := range s {
+		if len(t) == 0 {
+			return false
+		}
+		tb := t[0]
+		if tb < utf8.RuneSelf {
+			if sb != tb {
+				sbUpper := sb & caseMask
+				if 'A' <= sbUpper && sbUpper <= 'Z' {
+					if sbUpper != tb&caseMask {
+						return false
+					}
+				} else {
+					return false
+				}
+			}
+			t = t[1:]
+			continue
+		}
+		// sb is ASCII and t is not. t must be either kelvin
+		// sign or long s; sb must be s, S, k, or K.
+		tr, size := utf8.DecodeRune(t)
+		switch sb {
+		case 's', 'S':
+			if tr != smallLongEss {
+				return false
+			}
+		case 'k', 'K':
+			if tr != kelvin {
+				return false
+			}
+		default:
+			return false
+		}
+		t = t[size:]
+
+	}
+	if len(t) > 0 {
+		return false
+	}
+	return true
+}
+
+// asciiEqualFold is a specialization of bytes.EqualFold for use when
+// s is all ASCII (but may contain non-letters) and contains no
+// special-folding letters.
+// See comments on foldFunc.
+func asciiEqualFold(s, t []byte) bool {
+	if len(s) != len(t) {
+		return false
+	}
+	for i, sb := range s {
+		tb := t[i]
+		if sb == tb {
+			continue
+		}
+		if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
+			if sb&caseMask != tb&caseMask {
+				return false
+			}
+		} else {
+			return false
+		}
+	}
+	return true
+}
+
+// simpleLetterEqualFold is a specialization of bytes.EqualFold for
+// use when s is all ASCII letters (no underscores, etc) and also
+// doesn't contain 'k', 'K', 's', or 'S'.
+// See comments on foldFunc.
+func simpleLetterEqualFold(s, t []byte) bool {
+	if len(s) != len(t) {
+		return false
+	}
+	for i, b := range s {
+		if b&caseMask != t[i]&caseMask {
+			return false
+		}
+	}
+	return true
+}
+
+func foldFunc(s []byte) func(s, t []byte) bool {
+	nonLetter := false
+	special := false // special letter
+	for _, b := range s {
+		if b >= utf8.RuneSelf {
+			return bytes.EqualFold
+		}
+		upper := b & caseMask
+		if upper < 'A' || upper > 'Z' {
+			nonLetter = true
+		} else if upper == 'K' || upper == 'S' {
+			// See above for why these letters are special.
+			special = true
+		}
+	}
+	if special {
+		return equalFoldRight
+	}
+	if nonLetter {
+		return asciiEqualFold
+	}
+	return simpleLetterEqualFold
+}
+
+// A field represents a single field found in a struct.
+type field struct {
+	name      string
+	nameBytes []byte                 // []byte(name)
+	equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
+
+	tag       bool
+	index     []int
+	typ       reflect.Type
+	omitEmpty bool
+	quoted    bool
+}
+
+func fillField(f field) field {
+	f.nameBytes = []byte(f.name)
+	f.equalFold = foldFunc(f.nameBytes)
+	return f
+}
+
+// byIndex sorts field by index sequence.
+type byIndex []field
+
+func (x byIndex) Len() int { return len(x) }
+
+func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+func (x byIndex) Less(i, j int) bool {
+	for k, xik := range x[i].index {
+		if k >= len(x[j].index) {
+			return false
+		}
+		if xik != x[j].index[k] {
+			return xik < x[j].index[k]
+		}
+	}
+	return len(x[i].index) < len(x[j].index)
+}
+
+func isValidTag(s string) bool {
+	if s == "" {
+		return false
+	}
+	for _, c := range s {
+		switch {
+		case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
+			// Backslash and quote chars are reserved, but
+			// otherwise any punctuation chars are allowed
+			// in a tag name.
+		default:
+			if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// tagOptions is the string following a comma in a struct field's "json"
+// tag, or the empty string. It does not include the leading comma.
+type tagOptions string
+
+// Contains reports whether a comma-separated list of options
+// contains a particular substr flag. substr must be surrounded by a
+// string boundary or commas.
+func (o tagOptions) Contains(optionName string) bool {
+	if len(o) == 0 {
+		return false
+	}
+	s := string(o)
+	for s != "" {
+		var next string
+		i := strings.Index(s, ",")
+		if i >= 0 {
+			s, next = s[:i], s[i+1:]
+		}
+		if s == optionName {
+			return true
+		}
+		s = next
+	}
+	return false
+}
+
+// parseTag splits a struct field's json tag into its name and
+// comma-separated options.
+func parseTag(tag string) (string, tagOptions) {
+	if idx := strings.Index(tag, ","); idx != -1 {
+		return tag[:idx], tagOptions(tag[idx+1:])
+	}
+	return tag, tagOptions("")
+}
+
+// typeFields returns a list of fields that JSON should recognize for the given type.
+// The algorithm is breadth-first search over the set of structs to include - the top struct
+// and then any reachable anonymous structs.
+func typeFields(t reflect.Type) []field {
+	// Anonymous fields to explore at the current level and the next.
+	current := []field{}
+	next := []field{{typ: t}}
+
+	// Count of queued names for current level and the next.
+	count := map[reflect.Type]int{}
+	nextCount := map[reflect.Type]int{}
+
+	// Types already visited at an earlier level.
+	visited := map[reflect.Type]bool{}
+
+	// Fields found.
+	var fields []field
+
+	for len(next) > 0 {
+		current, next = next, current[:0]
+		count, nextCount = nextCount, map[reflect.Type]int{}
+
+		for _, f := range current {
+			if visited[f.typ] {
+				continue
+			}
+			visited[f.typ] = true
+
+			// Scan f.typ for fields to include.
+			for i := 0; i < f.typ.NumField(); i++ {
+				sf := f.typ.Field(i)
+				isUnexported := sf.PkgPath != ""
+				if sf.Anonymous {
+					t := sf.Type
+					if t.Kind() == reflect.Ptr {
+						t = t.Elem()
+					}
+					if isUnexported && t.Kind() != reflect.Struct {
+						// Ignore embedded fields of unexported non-struct types.
+						continue
+					}
+					// Do not ignore embedded fields of unexported struct types
+					// since they may have exported fields.
+				} else if isUnexported {
+					// Ignore unexported non-embedded fields.
+					continue
+				}
+				tag := sf.Tag.Get("json")
+				if tag == "-" {
+					continue
+				}
+				name, opts := parseTag(tag)
+				if !isValidTag(name) {
+					name = ""
+				}
+				index := make([]int, len(f.index)+1)
+				copy(index, f.index)
+				index[len(f.index)] = i
+
+				ft := sf.Type
+				if ft.Name() == "" && ft.Kind() == reflect.Ptr {
+					// Follow pointer.
+					ft = ft.Elem()
+				}
+
+				// Only strings, floats, integers, and booleans can be quoted.
+				quoted := false
+				if opts.Contains("string") {
+					switch ft.Kind() {
+					case reflect.Bool,
+						reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+						reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
+						reflect.Float32, reflect.Float64,
+						reflect.String:
+						quoted = true
+					}
+				}
+
+				// Record found field and index sequence.
+				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+					tagged := name != ""
+					if name == "" {
+						name = sf.Name
+					}
+					fields = append(fields, fillField(field{
+						name:      name,
+						tag:       tagged,
+						index:     index,
+						typ:       ft,
+						omitEmpty: opts.Contains("omitempty"),
+						quoted:    quoted,
+					}))
+					if count[f.typ] > 1 {
+						// If there were multiple instances, add a second,
+						// so that the annihilation code will see a duplicate.
+						// It only cares about the distinction between 1 or 2,
+						// so don't bother generating any more copies.
+						fields = append(fields, fields[len(fields)-1])
+					}
+					continue
+				}
+
+				// Record new anonymous struct to explore in next round.
+				nextCount[ft]++
+				if nextCount[ft] == 1 {
+					next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
+				}
+			}
+		}
+	}
+
+	sort.Slice(fields, func(i, j int) bool {
+		x := fields
+		// sort field by name, breaking ties with depth, then
+		// breaking ties with "name came from json tag", then
+		// breaking ties with index sequence.
+		if x[i].name != x[j].name {
+			return x[i].name < x[j].name
+		}
+		if len(x[i].index) != len(x[j].index) {
+			return len(x[i].index) < len(x[j].index)
+		}
+		if x[i].tag != x[j].tag {
+			return x[i].tag
+		}
+		return byIndex(x).Less(i, j)
+	})
+
+	// Delete all fields that are hidden by the Go rules for embedded fields,
+	// except that fields with JSON tags are promoted.
+
+	// The fields are sorted in primary order of name, secondary order
+	// of field index length. Loop over names; for each name, delete
+	// hidden fields by choosing the one dominant field that survives.
+	out := fields[:0]
+	for advance, i := 0, 0; i < len(fields); i += advance {
+		// One iteration per name.
+		// Find the sequence of fields with the name of this first field.
+		fi := fields[i]
+		name := fi.name
+		for advance = 1; i+advance < len(fields); advance++ {
+			fj := fields[i+advance]
+			if fj.name != name {
+				break
+			}
+		}
+		if advance == 1 { // Only one field with this name
+			out = append(out, fi)
+			continue
+		}
+		dominant, ok := dominantField(fields[i : i+advance])
+		if ok {
+			out = append(out, dominant)
+		}
+	}
+
+	fields = out
+	sort.Sort(byIndex(fields))
+
+	return fields
+}
+
+// dominantField looks through the fields, all of which are known to
+// have the same name, to find the single field that dominates the
+// others using Go's embedding rules, modified by the presence of
+// JSON tags. If there are multiple top-level fields, the boolean
+// will be false: This condition is an error in Go and we skip all
+// the fields.
+func dominantField(fields []field) (field, bool) {
+	// The fields are sorted in increasing index-length order. The winner
+	// must therefore be one with the shortest index length. Drop all
+	// longer entries, which is easy: just truncate the slice.
+	length := len(fields[0].index)
+	tagged := -1 // Index of first tagged field.
+	for i, f := range fields {
+		if len(f.index) > length {
+			fields = fields[:i]
+			break
+		}
+		if f.tag {
+			if tagged >= 0 {
+				// Multiple tagged fields at the same level: conflict.
+				// Return no field.
+				return field{}, false
+			}
+			tagged = i
+		}
+	}
+	if tagged >= 0 {
+		return fields[tagged], true
+	}
+	// All remaining fields have the same length. If there's more than one,
+	// we have a conflict (two fields named "X" at the same level) and we
+	// return no field.
+	if len(fields) > 1 {
+		return field{}, false
+	}
+	return fields[0], true
+}
+
+func generatePenpalRemoteMethods(host string,
+	endPoints map[string]*types.EndPoint) string {
+	var keys []string
+	for k := range endPoints {
+		keys = append(keys, k)
+	}
+
+	sort.Strings(keys)
+
+	keysLen := len(keys)
+
+	methods := ""
+
+	privateCheckSnippet := `
+        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
+	
+	    if (authenticationPublicKey === null) {
+		    result({
+			    "data" : "",
+			    "code" : "400",
+			    "status" : "Identity not authenticated"
+		    });
+	    }
+	
+	    if (loadedIdentities[authenticationPublicKey] === null) {
+		    result({
+			    "data" : "",
+			    "code" : "400",
+			    "status" : "Identity not authenticated"
+		    });
+ 	    } 
+	
+	    success = extendPinCodeTtl(authenticationPublicKey)
+
+	    if(success == false) {
+		    result({"data" : "",
+			    "code" : "400",
+			    "status" : "Identity not authenticated"
+		    })
+		}
+		
+	`
+
+	for i := 0; i < keysLen; i++ {
+		url := keys[i]
+
+		if endPoints[url].ManuallyWritten == true {
+			continue
+		}
+
+		if url == "/identity/getIdentityProfileData" {
+			privateCheckSnippet = `
+				authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
+			
+				if (authenticationPublicKey === null) {
+					result({
+						"data" : "",
+						"code" : "400",
+						"status" : "Identity not authenticated"
+					});
+				}
+			
+				if (loadedIdentities[authenticationPublicKey] === null) {
+					result({
+						"data" : "",
+						"code" : "400",
+						"status" : "Identity not authenticated"
+					});
+				} 
+							
+			`
+		}
+
+		if endPoints[url].Form != nil {
+			splits := strings.Split(url, "/")
+
+			packageStr := splits[len(splits)-2]
+
+			/*if !packageCreated[packageStr] {
+				result += "ViamAPI.prototype.\"" + packageStr + "\" = {}\n\n"
+				packageCreated[packageStr] = true
+			}*/
+
+			methodStr := splits[len(splits)-1]
+
+			form := endPoints[url].Form
+			s := reflect.ValueOf(form)
+
+			typeOfT := s.Type()
+
+			fields := typeFields(typeOfT)
+
+			lenFields := len(fields)
+
+			args := ""
+
+			for i := 0; i < lenFields; i++ {
+				if i != lenFields-1 {
+					args += fmt.Sprintf("%sArg,", fields[i].name)
+				} else {
+					args += fmt.Sprintf("%sArg", fields[i].name)
+				}
+			}
+
+			/*identity_login(modeArg,codeArg,actionIDArg) {
+					return new Penpal.Promise(result => {
+						viamApi.identity_login(modeArg,codeArg,actionIDArg).then((response) => { result(response.data);});
+					});
+			}*/
+
+			snippet := ""
+
+			if endPoints[url].HandlerType == "private" {
+				snippet = privateCheckSnippet
+			}
+
+			lastComma := ","
+
+			if args == "" {
+				lastComma = ""
+			}
+
+			method := packageStr + strings.Title(methodStr) + ": function(" + args + ") {\n" +
+				"    return new Penpal.Promise(function(result) {\n" + snippet +
+				"    executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + lastComma + args + ").then(function(executeResult) {\n" +
+				"            result(executeResult);\n" +
+				"        });\n" +
+				"    });\n" +
+				"}"
+
+			methods += method
+
+			if i != keysLen-1 {
+				methods += ","
+			}
+
+			methods += "\n"
+		} else {
+			splits := strings.Split(url, "/")
+
+			packageStr := splits[len(splits)-2]
+
+			/*if !packageCreated[packageStr] {
+				result += "ViamAPI.prototype.\"" + packageStr + "\" = {}\n\n"
+				packageCreated[packageStr] = true
+			}*/
+
+			methodStr := splits[len(splits)-1]
+
+			snippet := ""
+
+			if endPoints[url].HandlerType == "private" {
+				snippet = privateCheckSnippet
+			}
+
+			method := packageStr + strings.Title(methodStr) + ": function() {\n" +
+				"    return new Penpal.Promise(function(result) {\n" + snippet +
+				"    executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + ").then(function(executeResult) {\n" +
+				"            result(executedResult);\n" +
+				"        });\n" +
+				"    });\n" +
+				"}"
+
+			methods += method
+
+			if i != keysLen-1 {
+				methods += ","
+			}
+
+			methods += "\n"
+
+		}
+	}
+
+	return methods
+}
+
+func getWopiAPIPenpalMethods() string {
+	return `getPassports: function(fileID) {
+		return new Penpal.Promise(function(result) {
+			authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
+			if (authenticationPublicKey === null) {
+				result({
+					"data" : "",
+					"code" : "400",
+					"status" : "Identity not authenticated"
+				});
+			}
+			
+			if (loadedIdentities[authenticationPublicKey] === null) {
+				result({
+					"data" : "",
+					"code" : "400",
+					"status" : "Identity not authenticated"
+				});
+			}
+			
+			success = extendPinCodeTtl(authenticationPublicKey)
+
+			if(success == false) {
+				result({"data" : "",
+					"code" : "400",
+					"status" : "Identity not authenticated"
+				})
+			}
+
+			wopiAPI.getPassports(
+				authenticationPublicKey, 
+				viamApi.getConfig().headers.uuid, 
+				viamApi.getConfig().headers.token, 
+				fileID
+			).then(function(response) {
+				result(response.data);
+			});
+		});
+	}`
+}
+
+func getWopiAPI(url string) string {
+	dat, err := ioutil.ReadFile("../vcl/javascript/dist/wopiapi-iframe.js")
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+
+	urlParts := strings.Split(url, ":")
+
+	wopiAPI := strings.Replace(string(dat), "{{host}}", urlParts[0]+":"+urlParts[1], -1)
+	wopiAPI = strings.Replace(wopiAPI, "{{port}}", "8787", -1)
+	wopiAPI += "\n\n"
+
+	return wopiAPI
+}
-- 
GitLab